游戏可以运行

This commit is contained in:
2026-05-24 08:10:22 +08:00
commit 035b2f7af9
28 changed files with 4222 additions and 0 deletions

337
card_game/battlefield.py Normal file
View File

@@ -0,0 +1,337 @@
"""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 = []
atk = attacker.get_effective_attack()
if "siege" in attacker.abilities and defender == "capital":
atk *= 2
owner = self._get_unit_owner(attacker)
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:
if not (attacker.is_ranged() and attacker.zone == "support"):
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)