class_name AIStrategy extends RefCounted ## Base class for AI decision-making strategies ## Subclasses implement different difficulty levels enum Difficulty { EASY, NORMAL, HARD } var difficulty: Difficulty var player_index: int var game_state: GameState func _init(p_difficulty: Difficulty, p_player_index: int) -> void: difficulty = p_difficulty player_index = p_player_index func set_game_state(state: GameState) -> void: game_state = state ## Returns the player this AI controls func get_player() -> Player: return game_state.get_player(player_index) ## Returns the opponent player func get_opponent() -> Player: return game_state.get_player(1 - player_index) ## Called during Main Phase - decide what card to play or pass ## Returns: { "action": "play", "card": CardInstance } or { "action": "pass" } func decide_main_phase_action() -> Dictionary: push_error("AIStrategy.decide_main_phase_action() must be overridden") return { "action": "pass" } ## Called when CP is needed - decide how to generate CP ## Returns: { "action": "discard", "card": CardInstance } or { "action": "dull_backup", "card": CardInstance } func decide_cp_generation(needed_cp: Dictionary) -> Dictionary: push_error("AIStrategy.decide_cp_generation() must be overridden") return {} ## Called during Attack Phase - decide which forward to attack with ## Returns: { "action": "attack", "card": CardInstance } or { "action": "end_attacks" } func decide_attack_action() -> Dictionary: push_error("AIStrategy.decide_attack_action() must be overridden") return { "action": "end_attacks" } ## Called during Block Declaration - decide how to block ## Returns: { "action": "block", "card": CardInstance } or { "action": "skip" } func decide_block_action(attacker: CardInstance) -> Dictionary: push_error("AIStrategy.decide_block_action() must be overridden") return { "action": "skip" } ## Get thinking delay range in seconds based on difficulty func get_thinking_delay() -> float: match difficulty: Difficulty.EASY: return randf_range(1.5, 2.5) Difficulty.NORMAL: return randf_range(1.0, 1.5) Difficulty.HARD: return randf_range(0.5, 1.0) return 1.0 # ============ HELPER METHODS FOR SUBCLASSES ============ ## Get all cards in hand that can be played (have enough CP or can generate CP) func get_playable_cards() -> Array[CardInstance]: var player := get_player() var playable: Array[CardInstance] = [] for card in player.hand.get_cards(): if _can_afford_card(card): playable.append(card) return playable ## Check if a card can be afforded (either have CP or can generate it) func _can_afford_card(card: CardInstance) -> bool: var player := get_player() var cost := card.card_data.cost var elements := card.card_data.elements # Check if we already have enough CP var current_cp := player.cp_pool.get_total_cp() if current_cp >= cost: # Check element requirements for element in elements: if player.cp_pool.get_cp(element) > 0 or player.cp_pool.get_cp(Enums.Element.NONE) > 0: return true # If no specific element needed (Light/Dark cards), any CP works if elements.is_empty(): return true # Check if we can generate enough CP var potential_cp := _calculate_potential_cp() return potential_cp >= cost ## Calculate total CP we could generate (hand discards + backup dulls) func _calculate_potential_cp() -> int: var player := get_player() var total := player.cp_pool.get_total_cp() # Each card in hand can be discarded for 2 CP total += player.hand.get_card_count() * 2 # Each active backup can be dulled for 1 CP for backup in player.field_backups.get_cards(): if backup.state == Enums.CardState.ACTIVE: total += 1 return total ## Get forwards that can attack func get_attackable_forwards() -> Array[CardInstance]: return get_player().get_attackable_forwards() ## Get forwards that can block func get_blockable_forwards() -> Array[CardInstance]: return get_player().get_blockable_forwards() ## Calculate a simple card value score func calculate_card_value(card: CardInstance) -> float: var data := card.card_data var value := 0.0 match data.type: Enums.CardType.FORWARD: # Forwards valued by power/cost ratio + abilities value = float(data.power) / float(max(data.cost, 1)) if data.has_ability("Brave"): value *= 1.3 if data.has_ability("First Strike"): value *= 1.2 if data.has_ability("Haste"): value *= 1.4 Enums.CardType.BACKUP: # Backups valued by utility (cost efficiency) value = 3.0 / float(max(data.cost, 1)) Enums.CardType.SUMMON: # Summons valued by effect strength (approximated by cost) value = float(data.cost) * 0.8 Enums.CardType.MONSTER: # Monsters similar to forwards value = float(data.power) / float(max(data.cost, 1)) return value ## Evaluate board advantage (positive = we're ahead) func evaluate_board_state() -> float: var player := get_player() var opponent := get_opponent() var score := 0.0 # Forward power advantage var our_power := 0 for forward in player.field_forwards.get_cards(): our_power += forward.get_power() var their_power := 0 for forward in opponent.field_forwards.get_cards(): their_power += forward.get_power() score += (our_power - their_power) / 1000.0 # Backup count advantage score += (player.field_backups.get_card_count() - opponent.field_backups.get_card_count()) * 2.0 # Hand size advantage score += (player.hand.get_card_count() - opponent.hand.get_card_count()) * 0.5 # Damage disadvantage (more damage = worse) score -= (player.get_damage_count() - opponent.get_damage_count()) * 3.0 return score