kards-env/archive/game_engine_v3.py

365 lines
14 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
"""
KARDS游戏引擎 v3 - 真实的拖拽操作系统
玩家只能进行两种操作
1. 使用卡牌部署单位/执行指令
2. 拖动单位移动/攻击的统一操作
"""
from typing import Dict, List, Optional, Any, Union
from uuid import UUID
from ..battlefield.battlefield_v2 import Battlefield, HQ
from ..units.unit import Unit
from ..abilities.keywords import TriggerTiming, KeywordRegistry
from ..abilities.abilities import AbilityManager
from .enums import GamePhase, LineType
from .unit_combat_rules import UnitCombatRules
class GameEngineV3:
"""KARDS游戏引擎 v3"""
def __init__(self, player1_id: str, player2_id: str):
self.battlefield = Battlefield(player1_id, player2_id)
self.current_turn = 1
self.active_player = player1_id
# 指挥点资源管理
self.player1_command_points = 1 # 第一回合1点
self.player2_command_points = 1
self.max_command_points = 12 # 最大12点
# 事件系统
self.event_history = []
def get_command_points(self, player_id: str) -> int:
"""获取玩家当前指挥点"""
if player_id == self.battlefield.player1_id:
return self.player1_command_points
elif player_id == self.battlefield.player2_id:
return self.player2_command_points
return 0
def spend_command_points(self, player_id: str, cost: int) -> bool:
"""消耗指挥点"""
current_points = self.get_command_points(player_id)
if current_points >= cost:
if player_id == self.battlefield.player1_id:
self.player1_command_points -= cost
elif player_id == self.battlefield.player2_id:
self.player2_command_points -= cost
return True
return False
# ===== 操作1: 使用卡牌 =====
def play_unit_card(self, unit: Unit, player_id: str) -> Dict[str, Any]:
"""使用单位卡牌 - 部署到支援线空位"""
if player_id != self.active_player:
return {"success": False, "reason": "Not your turn"}
# 检查指挥点
if not self.spend_command_points(player_id, unit.stats.operation_cost):
return {"success": False, "reason": "Insufficient command points"}
# 部署到支援线
support_line = self.battlefield.get_player_support_line(player_id)
if not support_line or support_line.is_full():
return {"success": False, "reason": "Support line is full"}
success = support_line.add_unit(unit)
if success:
unit.owner = player_id
self.battlefield.unit_registry[unit.id] = unit
# 触发部署效果
self._trigger_deploy_abilities(unit)
return {
"success": True,
"action": "deploy",
"unit_id": unit.id,
"location": "support"
}
return {"success": False, "reason": "Failed to deploy"}
# ===== 操作2: 拖动单位 =====
def drag_unit(self, unit_id: UUID, target_position: tuple, player_id: str) -> Dict[str, Any]:
"""
拖动单位 - 统一的移动/攻击操作
target_position: (line_type, index) 或者 target_unit_id
"""
if player_id != self.active_player:
return {"success": False, "reason": "Not your turn"}
unit = self.battlefield.find_unit(unit_id)
if not unit or unit.owner != player_id:
return {"success": False, "reason": "Unit not found or not yours"}
# 检查单位激活点数(油费)- 每个单位必须有此属性
activation_cost = unit.activation_cost
if not self.spend_command_points(player_id, activation_cost):
return {"success": False, "reason": "Insufficient command points for activation"}
# 判断拖拽目标类型
if isinstance(target_position, tuple) and len(target_position) == 2:
# 拖拽到位置 - 可能是移动
return self._handle_position_drag(unit, target_position)
else:
# 拖拽到目标单位/HQ - 攻击
return self._handle_target_drag(unit, target_position)
def _handle_position_drag(self, unit: Unit, target_pos: tuple) -> Dict[str, Any]:
"""处理拖拽到位置的操作(移动)"""
target_line_type, target_index = target_pos
# 只能从支援线移动到前线
if not unit.position:
return {"success": False, "reason": "Unit not on battlefield"}
current_line, current_index = unit.position
# 检查移动规则:只能从支援线移动到前线
if target_line_type != LineType.FRONT:
return {"success": False, "reason": "Can only move to front line"}
if current_line == LineType.FRONT:
# 前线单位不能移动
return {"success": False, "reason": "Units on front line cannot move"}
else:
# 从支援线移动到前线(包括挤位置)
return self._move_to_front_line(unit, target_index)
def _move_to_front_line(self, unit: Unit, target_index: int) -> Dict[str, Any]:
"""从支援线移动到前线(包括挤位置机制)"""
front_line = self.battlefield.front_line
# 检查前线控制权 - 必须是空的或己方控制
if (self.battlefield.front_line_controller is not None and
self.battlefield.front_line_controller != unit.owner):
return {"success": False, "reason": "Front line controlled by enemy"}
# 检查是否要挤位置(目标位置非空)
if target_index < len(front_line.objectives):
# 挤位置:如果前线满了则失败
if front_line.is_full():
return {"success": False, "reason": "Front line is full, cannot squeeze"}
else:
# 添加到末尾:检查前线是否满
if front_line.is_full():
return {"success": False, "reason": "Front line is full"}
# 从支援线移除
support_line = self.battlefield.get_player_support_line(unit.owner)
if not support_line.remove_unit(unit):
return {"success": False, "reason": "Failed to remove from support line"}
# 执行移动
if target_index >= len(front_line.objectives):
# 添加到末尾
front_line.add_unit(unit)
actual_position = len(front_line.objectives) - 1
else:
# 挤位置:插入到指定位置,后续单位顺位移动
front_line.objectives.insert(target_index, unit)
front_line._reindex_objectives()
actual_position = target_index
# 更新前线控制权
self.battlefield._update_front_line_control()
return {
"success": True,
"action": "move_to_front",
"unit_id": unit.id,
"to_position": actual_position,
"front_line_controller": self.battlefield.front_line_controller
}
def _handle_target_drag(self, unit: Unit, target_id: Union[UUID, str]) -> Dict[str, Any]:
"""处理拖拽到目标的操作(攻击)"""
# 查找目标
target = None
if isinstance(target_id, str) and target_id.startswith("hq"):
# HQ目标
if target_id == "hq1":
target = self.battlefield.player1_hq
elif target_id == "hq2":
target = self.battlefield.player2_hq
else:
# 单位目标
target = self.battlefield.find_unit(target_id)
if not target:
return {"success": False, "reason": "Target not found"}
# 检查是否能攻击该目标
if not self._can_attack_target(unit, target):
return {"success": False, "reason": "Cannot attack this target"}
# 执行攻击
return self._execute_attack(unit, target)
def _can_attack_target(self, attacker: Unit, target: Union[Unit, HQ]) -> bool:
"""检查是否能攻击目标"""
# 不能攻击己方目标
if isinstance(target, Unit) and target.owner == attacker.owner:
return False
elif isinstance(target, HQ) and target.player_id == attacker.owner:
return False
# 使用详细的战斗规则检查
return UnitCombatRules.can_attack_target(attacker, target, self.battlefield)
def _execute_attack(self, attacker: Unit, target: Union[Unit, HQ]) -> Dict[str, Any]:
"""执行攻击"""
# 计算伤害
damage = attacker.get_effective_attack()
# 应用伤害
if isinstance(target, Unit):
# 单位战斗
return self._unit_combat(attacker, target)
else:
# 攻击HQ
actual_damage = target.take_damage(damage)
return {
"success": True,
"action": "attack_hq",
"attacker_id": attacker.id,
"target": "HQ",
"damage_dealt": actual_damage,
"hq_destroyed": target.is_destroyed()
}
def _unit_combat(self, attacker: Unit, target: Unit) -> Dict[str, Any]:
"""单位间战斗"""
# 处理伏击
if target.has_keyword("AMBUSH"):
ambush_damage = target.get_effective_attack()
attacker_damage = attacker.stats.take_damage(ambush_damage)
if not attacker.stats.is_alive():
self.battlefield.remove_unit(attacker)
return {
"success": True,
"action": "attack_unit",
"ambush_triggered": True,
"attacker_destroyed": True,
"ambush_damage": attacker_damage
}
# 计算攻击伤害
attack_damage = attacker.get_effective_attack()
# 应用重甲减伤
for keyword in target.keywords:
if keyword.startswith("HEAVY_ARMOR_"):
armor_value = int(keyword.split("_")[-1])
attack_damage = max(0, attack_damage - armor_value)
break
# 造成伤害
actual_damage = target.stats.take_damage(attack_damage)
# 反击(使用详细的反击规则)
counter_damage = 0
if target.stats.is_alive() and UnitCombatRules.can_counter_attack(target, attacker):
counter_attack = target.get_effective_attack()
counter_damage = attacker.stats.take_damage(counter_attack)
# 检查单位死亡
units_destroyed = []
if not target.stats.is_alive():
self.battlefield.remove_unit(target)
units_destroyed.append(target.id)
# 触发死亡能力
self._trigger_death_abilities(target)
if not attacker.stats.is_alive():
self.battlefield.remove_unit(attacker)
units_destroyed.append(attacker.id)
self._trigger_death_abilities(attacker)
return {
"success": True,
"action": "attack_unit",
"attacker_id": attacker.id,
"target_id": target.id,
"damage_dealt": actual_damage,
"counter_damage": counter_damage,
"units_destroyed": units_destroyed
}
def _trigger_deploy_abilities(self, unit: Unit) -> None:
"""触发部署能力"""
for ability in unit.abilities:
if hasattr(ability, 'ability_type') and ability.ability_type.name == "DEPLOY":
# 简化的能力触发
valid_targets = ability.get_valid_targets(unit, self.battlefield)
if valid_targets:
ability.execute(unit, self.battlefield, valid_targets[0])
def _trigger_death_abilities(self, unit: Unit) -> None:
"""触发死亡能力"""
for ability in unit.abilities:
if hasattr(ability, 'ability_type') and ability.ability_type.name == "DEATH":
ability.execute(unit, self.battlefield)
def end_turn(self) -> Dict[str, Any]:
"""结束回合"""
old_player = self.active_player
# 切换玩家
self.active_player = (self.battlefield.player2_id
if self.active_player == self.battlefield.player1_id
else self.battlefield.player1_id)
# 增加指挥点
if self.active_player == self.battlefield.player1_id:
self.player1_command_points = min(self.max_command_points, self.current_turn + 1)
else:
self.player2_command_points = min(self.max_command_points, self.current_turn + 1)
# 重置单位状态
for unit in self.battlefield.get_all_units(self.active_player):
unit.has_operated_this_turn = False
self.current_turn += 1
# 检查游戏是否结束
game_over, winner = self.battlefield.is_game_over()
return {
"turn_ended": True,
"previous_player": old_player,
"new_active_player": self.active_player,
"turn_number": self.current_turn,
"command_points": self.get_command_points(self.active_player),
"game_over": game_over,
"winner": winner
}
def get_game_state(self) -> Dict[str, Any]:
"""获取游戏状态"""
return {
"turn": self.current_turn,
"active_player": self.active_player,
"command_points": {
"player1": self.player1_command_points,
"player2": self.player2_command_points,
"active_player": self.get_command_points(self.active_player)
},
"battlefield": str(self.battlefield),
"player1_hq": self.battlefield.player1_hq.current_defense,
"player2_hq": self.battlefield.player2_hq.current_defense,
"front_line_controller": self.battlefield.front_line_controller,
"units_count": {
"player1_total": len(self.battlefield.get_all_units(self.battlefield.player1_id)),
"player2_total": len(self.battlefield.get_all_units(self.battlefield.player2_id)),
"front_line": len(self.battlefield.front_line.get_all_units())
}
}