"""Battlefield: core game logic — combat, orders, turn management.""" from card_game.player import Player from card_game.factions import apply_faction_passive class Battlefield: def __init__(self, player_faction, ai_faction): self.player = Player(player_faction, is_ai=False) self.ai = Player(ai_faction, is_ai=True) self.turn_number = 0 self.current_turn = "player" self.game_over = False self.winner = None self.log = [] self.pending_order = None self.effects = [] self.frontline_controller = None def start_game(self): self.player.start_game() self.ai.start_game() self.turn_number = 1 self.current_turn = "player" self.frontline_controller = None self.player.start_turn(self.turn_number) def get_active_player(self): return self.player if self.current_turn == "player" else self.ai def get_opponent(self, player): return self.ai if player == self.player else self.player # --- Frontline Control --- def can_move_to_frontline(self, player): if self.frontline_controller is None: return True if player == self.player and self.frontline_controller == "player": return True if player == self.ai and self.frontline_controller == "ai": return True return False def claim_frontline(self, player): if player == self.player: self.frontline_controller = "player" else: self.frontline_controller = "ai" def _update_frontline_control(self): if self.frontline_controller == "player": if not self.player.get_frontline_units(): self.frontline_controller = None elif self.frontline_controller == "ai": if not self.ai.get_frontline_units(): self.frontline_controller = None # --- Turn Management --- def end_player_turn(self): if self.current_turn != "player" or self.game_over: return self.current_turn = "ai" def start_ai_turn(self): self.turn_number += 1 self.ai.start_turn(self.turn_number) self.current_turn = "ai" def end_ai_turn(self): if self.game_over: return self.current_turn = "player" self.turn_number += 1 self.player.start_turn(self.turn_number) # --- Combat --- def resolve_attack(self, attacker, defender): dead = [] owner = self._get_unit_owner(attacker) if owner: attack_cost = owner.get_attack_cost(attacker) if owner.provisions < attack_cost: return dead owner.provisions -= attack_cost atk = attacker.get_effective_attack() if "siege" in attacker.abilities and defender == "capital": atk *= 2 if (owner and owner.faction_id == "han" and attacker.unit_type == "archer" and defender == "capital"): atk += 1 if isinstance(defender, str) and defender == "capital": target_player = self.get_opponent(owner) target_player.capital_hp -= atk self._add_effect("damage", target_player, "capital", atk) self._check_game_over() attacker.has_attacked = True attacker.can_attack = False return dead defender.take_damage(atk) self._add_effect("damage", self._get_unit_owner(defender), defender, atk) if not defender.is_alive(): dead.append(defender) if owner and owner.faction_id == "qin": owner.provisions += 1 if "no_retaliation" not in attacker.abilities and not attacker.is_ranged(): retal = defender.get_effective_defense() if defender.is_alive() else 0 if retal > 0: attacker.take_damage(retal) self._add_effect("damage", self._get_unit_owner(attacker), attacker, retal) if not attacker.is_alive(): dead.append(attacker) attacker.has_attacked = True attacker.can_attack = False self._cleanup_dead() self._check_game_over() return dead def attack_capital(self, attacker): owner = self._get_unit_owner(attacker) if not owner: return self.resolve_attack(attacker, "capital") # --- Order Effects --- def apply_order_effect(self, card, caster, target=None): etype = card.effect_type params = card.effect_params opponent = self.get_opponent(caster) if etype == "damage": dmg = params["damage"] if target and hasattr(target, 'take_damage'): target.take_damage(dmg) self._add_effect("damage", self._get_unit_owner(target), target, dmg) self._cleanup_dead() return True elif etype == "damage_hq": dmg = params["damage"] opponent.capital_hp -= dmg self._add_effect("damage", opponent, "capital", dmg) self._check_game_over() return True elif etype == "damage_all_front": dmg = params["damage"] tgt = params.get("target", "enemy") if tgt == "enemy": for u in opponent.get_frontline_units(): u.take_damage(dmg) self._add_effect("damage", opponent, u, dmg) else: for u in caster.get_frontline_units(): u.take_damage(dmg) self._add_effect("damage", caster, u, dmg) self._cleanup_dead() return True elif etype == "draw": count = params["count"] for _ in range(count): caster.draw_card() return True elif etype == "gain_provisions": caster.provisions += params["amount"] return True elif etype == "buff_all": atk_b = params.get("attack_bonus", 0) def_b = params.get("defense_bonus", 0) dur = params.get("duration", 1) for u in caster.get_all_units(): u.buffs.append((atk_b, def_b, dur)) return True elif etype == "buff_type": unit_type = params["unit_type"] atk_b = params.get("attack_bonus", 0) def_b = params.get("defense_bonus", 0) dur = params.get("duration", 1) for u in caster.get_all_units(): if u.unit_type == unit_type: u.buffs.append((atk_b, def_b, dur)) return True elif etype == "buff_single": if target and hasattr(target, 'buffs'): atk_b = params.get("attack_bonus", 0) def_b = params.get("defense_bonus", 0) dur = params.get("duration", 1) target.buffs.append((atk_b, def_b, dur)) return True elif etype == "heal_hq": amount = params["amount"] caster.capital_hp = min(caster.max_capital_hp, caster.capital_hp + amount) return True elif etype == "bounce": if target and hasattr(target, 'zone'): owner = self._get_unit_owner(target) if owner: owner.remove_unit(target) target.zone = "hand" if len(owner.hand) < 8: owner.hand.append(target) return True elif etype == "destroy_damaged": if target and hasattr(target, 'is_alive'): if target.current_hp < target.max_hp: owner = self._get_unit_owner(target) if owner: owner.remove_unit(target) return True elif etype == "move_to_front": if target and hasattr(target, 'zone') and target.zone == "support": caster.move_to_frontline_free(target) return True elif etype == "summon": from card_game.card import Card count = params.get("count", 1) unit_id = params["unit_id"] for _ in range(count): new_card = Card(unit_id) new_card.turn_played = caster.turn_number placed = False for i, s in enumerate(caster.support_line): if s is None: caster.support_line[i] = new_card new_card.zone = "support" new_card.slot = i placed = True break if not placed: break return True elif etype == "heal_all": amount = params.get("amount", 1) for u in caster.get_all_units(): u.current_hp = min(u.max_hp, u.current_hp + amount) return True elif etype == "draw_self_damage": count = params.get("count", 1) self_damage = params.get("self_damage", 0) for _ in range(count): caster.draw_card() if self_damage > 0: caster.capital_hp -= self_damage return True return False def needs_target(self, card): etype = card.effect_type if etype in ("damage", "bounce", "destroy_damaged", "move_to_front", "buff_single"): return True return False def get_valid_targets(self, card, caster): etype = card.effect_type opponent = self.get_opponent(caster) params = card.effect_params if etype == "damage": tt = params.get("target_type", "any") targets = [] if tt in ("any", "enemy_unit"): targets.extend(opponent.get_all_units()) if tt == "any": targets.append(("capital", opponent)) return targets elif etype == "bounce": return opponent.get_frontline_units() elif etype == "destroy_damaged": return [u for u in opponent.get_all_units() if u.current_hp < u.max_hp] elif etype == "move_to_front": return [u for u in caster.get_support_units()] elif etype == "buff_single": return caster.get_all_units() return [] # --- Helpers --- def _get_unit_owner(self, unit): for u in self.player.get_all_units(): if u is unit: return self.player for u in self.ai.get_all_units(): if u is unit: return self.ai return None def _cleanup_dead(self): self.player.cleanup_dead() self.ai.cleanup_dead() self._update_frontline_control() def _check_game_over(self): if self.player.capital_hp <= 0: self.game_over = True self.winner = "ai" elif self.ai.capital_hp <= 0: self.game_over = True self.winner = "player" def _add_effect(self, etype, target_player, target, value): self.effects.append({ "type": etype, "target_player": target_player, "target": target, "value": value, "timer": 60, }) def update_effects(self): for eff in self.effects[:]: eff["timer"] -= 1 if eff["timer"] <= 0: self.effects.remove(eff)