"""AI player: simple rule-based decision making.""" from card_game.config import CARD_DATABASE class AIPlayer: def __init__(self, battlefield): self.battlefield = battlefield def execute_turn(self): actions = [] ai = self.battlefield.ai actions.extend(self._play_untargeted_orders(ai)) actions.extend(self._deploy_units(ai)) actions.extend(self._play_targeted_orders(ai)) actions.extend(self._move_units(ai)) actions.extend(self._attack(ai)) return actions def _play_untargeted_orders(self, ai): actions = [] for card in ai.hand[:]: if card.card_type != "order": continue if card.cost > ai.provisions: continue if not self.battlefield.needs_target(card): self.battlefield.apply_order_effect(card, ai) ai.play_order(card) actions.append(("order", card)) return actions def _deploy_units(self, ai): actions = [] units = [c for c in ai.hand if c.card_type == "unit" and ai.can_play_card(c)] units.sort(key=lambda c: c.cost) for card in units: if not ai.can_play_card(card): continue from card_game.factions import apply_faction_passive apply_faction_passive(card, ai.faction_id) slot = ai.deploy_unit(card) if slot >= 0: actions.append(("deploy", card, slot)) self._handle_deploy(card, ai) return actions def _play_targeted_orders(self, ai): actions = [] for card in ai.hand[:]: if card.card_type != "order": continue if card.cost > ai.provisions: continue if not self.battlefield.needs_target(card): continue targets = self.battlefield.get_valid_targets(card, ai) if targets: target = self._pick_best_target(card, targets, ai) if target: self.battlefield.apply_order_effect(card, ai, target) ai.play_order(card) actions.append(("order_targeted", card, target)) return actions def _move_units(self, ai): actions = [] if not self.battlefield.can_move_to_frontline(ai): return actions for unit in ai.get_support_units(): op_cost = ai._get_op_cost(unit) if ai.provisions >= op_cost: slot = ai.move_to_frontline(unit) if slot >= 0: if self.battlefield.frontline_controller is None: self.battlefield.claim_frontline(ai) actions.append(("move", unit, slot)) return actions def _attack(self, ai): actions = [] player = self.battlefield.player # Frontline units: non-archer attacks enemy support only, archer attacks all for unit in ai.get_frontline_units(): if not unit.can_attack or unit.has_attacked or not ai.can_afford_attack(unit): continue if unit.is_ranged(): targets = player.get_frontline_units() + player.get_support_units() else: targets = player.get_support_units() if targets: killable = [u for u in targets if u.current_hp <= unit.get_effective_attack()] if killable: target = min(killable, key=lambda u: u.current_hp) else: target = max(targets, key=lambda u: u.get_effective_attack()) self.battlefield.resolve_attack(unit, target) actions.append(("attack_unit", unit, target)) else: self.battlefield.attack_capital(unit) actions.append(("attack_capital", unit)) # Support units: attack enemy frontline > enemy support > capital (ranged only) for unit in ai.get_support_units(): if not unit.can_attack or unit.has_attacked or not ai.can_afford_attack(unit): continue enemy_front = player.get_frontline_units() enemy_support = player.get_support_units() if unit.is_ranged(): all_enemy = enemy_front + enemy_support if all_enemy: killable = [u for u in all_enemy if u.current_hp <= unit.get_effective_attack()] target = min(killable, key=lambda u: u.current_hp) if killable else min(all_enemy, key=lambda u: u.current_hp) self.battlefield.resolve_attack(unit, target) actions.append(("attack_unit", unit, target)) else: self.battlefield.attack_capital(unit) actions.append(("attack_capital", unit)) elif enemy_front: killable = [u for u in enemy_front if u.current_hp <= unit.get_effective_attack()] target = min(killable, key=lambda u: u.current_hp) if killable else min(enemy_front, key=lambda u: u.current_hp) self.battlefield.resolve_attack(unit, target) actions.append(("attack_unit", unit, target)) return actions def _handle_deploy(self, card, ai): for ability in card.abilities: if ability.startswith("draw_on_deploy:"): count = int(ability.split(":")[1]) for _ in range(count): ai.draw_card() elif ability.startswith("damage_on_deploy:"): dmg = int(ability.split(":")[1]) opponent = self.battlefield.get_opponent(ai) targets = opponent.get_all_units() if targets: import random target = random.choice(targets) target.take_damage(dmg) def _pick_best_target(self, card, targets, ai): if not targets: return None if card.effect_type == "destroy_damaged": dmg_targets = [t for t in targets if hasattr(t, 'current_hp') and t.current_hp < t.max_hp] if dmg_targets: return min(dmg_targets, key=lambda u: u.current_hp) return None if card.effect_type == "bounce": return max(targets, key=lambda u: u.get_effective_attack() if hasattr(u, 'get_effective_attack') else 0) if card.effect_type in ("buff_single", "move_to_front"): return max(targets, key=lambda u: u.get_effective_attack() if hasattr(u, 'get_effective_attack') else 0) if card.effect_type == "damage": unit_targets = [t for t in targets if hasattr(t, 'get_effective_attack')] if unit_targets: return max(unit_targets, key=lambda u: u.get_effective_attack()) return targets[0] if targets else None