class_name NormalAI extends AIStrategy ## Normal AI - Balanced play using cost/power heuristics ## Makes generally good decisions but doesn't deeply analyze func _init(p_player_index: int) -> void: super._init(Difficulty.NORMAL, p_player_index) func decide_main_phase_action() -> Dictionary: var playable := get_playable_cards() if playable.is_empty(): return { "action": "pass" } # Sort by value (best cards first) playable.sort_custom(_compare_card_value) # Consider board state - prioritize forwards if we're behind var board_eval := evaluate_board_state() for card in playable: var card_type := card.card_data.type # If behind on board, prioritize forwards if board_eval < -5.0 and card_type == Enums.CardType.FORWARD: return { "action": "play", "card": card } # If ahead, might want backups for sustainability if board_eval > 5.0 and card_type == Enums.CardType.BACKUP: if get_player().field_backups.get_card_count() < 5: return { "action": "play", "card": card } # Default: play the highest value card we can afford return { "action": "play", "card": playable[0] } func decide_cp_generation(needed_cp: Dictionary) -> Dictionary: var player := get_player() # First, dull backups (they refresh next turn) for backup in player.field_backups.get_cards(): if backup.state == Enums.CardState.ACTIVE: return { "action": "dull_backup", "card": backup } # Then, discard lowest value card from hand var hand_cards := player.hand.get_cards() if hand_cards.is_empty(): return {} # Sort by value (lowest first for discard) var sorted_hand := hand_cards.duplicate() sorted_hand.sort_custom(_compare_card_value_reverse) return { "action": "discard", "card": sorted_hand[0] } func decide_attack_action() -> Dictionary: var attackers := get_attackable_forwards() var opponent := get_opponent() if attackers.is_empty(): return { "action": "end_attacks" } # Get opponent's potential blockers var opponent_blockers := opponent.get_blockable_forwards() # Evaluate each potential attacker var best_attacker: CardInstance = null var best_score := -999.0 for attacker in attackers: var score := _evaluate_attack(attacker, opponent_blockers, opponent) if score > best_score: best_score = score best_attacker = attacker # Only attack if the score is positive (favorable) if best_score > 0 and best_attacker: return { "action": "attack", "card": best_attacker } return { "action": "end_attacks" } func decide_block_action(attacker: CardInstance) -> Dictionary: var blockers := get_blockable_forwards() var player := get_player() if blockers.is_empty(): return { "action": "skip" } var attacker_power := attacker.get_power() # Check if this attack would be lethal var current_damage := player.get_damage_count() var would_be_lethal := current_damage >= 6 # 7th damage loses # Find best blocker var best_blocker: CardInstance = null var best_score := -999.0 for blocker in blockers: var blocker_power := blocker.get_power() var score := 0.0 # Would we win the trade? if blocker_power >= attacker_power: score += 5.0 # We kill their forward if attacker_power >= blocker_power: score -= calculate_card_value(blocker) # We lose our blocker # If lethal, blocking is very important if would_be_lethal: score += 10.0 if score > best_score: best_score = score best_blocker = blocker # Block if favorable or if lethal if best_score > 0 or would_be_lethal: if best_blocker: return { "action": "block", "card": best_blocker } return { "action": "skip" } func _evaluate_attack(attacker: CardInstance, opponent_blockers: Array[CardInstance], opponent: Player) -> float: var score := 0.0 var attacker_power := attacker.get_power() # Base value: dealing damage is good score += 2.0 # Check if opponent can block profitably var can_be_blocked := false for blocker in opponent_blockers: if blocker.get_power() >= attacker_power: can_be_blocked = true score -= 3.0 # Likely to lose our forward break # If unblockable damage, more valuable if not can_be_blocked: score += 3.0 # If this would be lethal damage (7th), very valuable if opponent.get_damage_count() >= 6: score += 10.0 return score func _compare_card_value(a: CardInstance, b: CardInstance) -> bool: return calculate_card_value(a) > calculate_card_value(b) func _compare_card_value_reverse(a: CardInstance, b: CardInstance) -> bool: return calculate_card_value(a) < calculate_card_value(b)