365 lines
14 KiB
Python
365 lines
14 KiB
Python
"""
|
|
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())
|
|
}
|
|
} |