Files
card/card_game/ai.py
2026-05-24 10:48:58 +08:00

161 lines
6.7 KiB
Python

"""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: attack enemy frontline > enemy support > capital
for unit in ai.get_frontline_units():
if not unit.can_attack or unit.has_attacked:
continue
enemy_front = player.get_frontline_units()
enemy_support = player.get_support_units()
all_enemy = enemy_front + enemy_support
if all_enemy:
killable = [u for u in all_enemy if u.current_hp <= unit.get_effective_attack()]
if killable:
target = min(killable, key=lambda u: u.current_hp)
else:
target = max(all_enemy, 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:
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