kards-env/kards_battle/battlefield/battlefield.py

410 lines
14 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
"""
重新设计的战场系统
符合KARDS真实的3条战线布局玩家1支援线 - 共用前线 - 玩家2支援线
"""
from typing import List, Optional, Dict, Tuple, Union
from dataclasses import dataclass
from uuid import UUID
from ..core.enums import LineType
from ..units.unit import Unit
@dataclass
class HQ:
"""总部类 - 作为支援线上的特殊目标"""
defense: int = 20
current_defense: int = 20
player_id: str = ""
position_index: int = 2 # HQ默认在支援线中间位置
def take_damage(self, damage: int) -> int:
"""总部受到伤害"""
actual_damage = min(damage, self.current_defense)
self.current_defense -= actual_damage
return actual_damage
def is_destroyed(self) -> bool:
"""判断总部是否被摧毁"""
return self.current_defense <= 0
def __str__(self) -> str:
return f"HQ ({self.current_defense}/{self.defense})"
class BattleLine:
"""战线类 - 支持单位和HQ的紧凑排列"""
def __init__(self, line_type: LineType, max_objectives: int = 5, hq: Optional[HQ] = None):
self.line_type = line_type
self.max_objectives = max_objectives
self.objectives: List[Optional[Union[Unit, HQ]]] = [] # 紧凑排列的目标
# 如果有HQ放在中间位置
if hq:
self.objectives.append(hq)
hq.position_index = 0
def add_unit(self, unit: Unit) -> bool:
"""添加单位到战线(自动紧凑排列)"""
if len(self.objectives) >= self.max_objectives:
return False # 战线已满
self.objectives.append(unit)
unit.position = (self.line_type, len(self.objectives) - 1)
# 检查烟幕条件
unit.check_smokescreen_conditions()
return True
def get_hq(self) -> Optional[HQ]:
"""获取战线上的HQ"""
for obj in self.objectives:
if isinstance(obj, HQ):
return obj
return None
def get_units(self) -> List[Unit]:
"""获取战线上的所有单位不包括HQ"""
units = []
for obj in self.objectives:
if isinstance(obj, Unit):
units.append(obj)
return units
def remove_unit(self, unit: Unit) -> bool:
"""移除单位并重新排列"""
try:
index = self.objectives.index(unit)
self.objectives.pop(index)
unit.position = None
# 重新分配位置索引
self._reindex_objectives()
return True
except ValueError:
return False
def _reindex_objectives(self) -> None:
"""重新分配所有目标的位置索引"""
for i, obj in enumerate(self.objectives):
if isinstance(obj, Unit):
obj.position = (self.line_type, i)
# 重新索引后检查烟幕条件
obj.check_smokescreen_conditions()
elif isinstance(obj, HQ):
obj.position_index = i
def get_unit_at_index(self, index: int) -> Optional[Union[Unit, HQ]]:
"""获取指定索引位置的目标"""
if 0 <= index < len(self.objectives):
return self.objectives[index]
return None
def get_all_units(self) -> List[Unit]:
"""获取战线上所有单位不包括HQ"""
return [obj for obj in self.objectives if isinstance(obj, Unit)]
def get_all_objectives(self) -> List[Union[Unit, HQ]]:
"""获取战线上所有目标包括单位和HQ"""
return self.objectives.copy()
def find_unit_index(self, unit: Unit) -> Optional[int]:
"""查找单位的索引位置"""
try:
return self.objectives.index(unit)
except ValueError:
return None
def get_adjacent_indices(self, index: int) -> List[int]:
"""获取相邻位置的索引"""
adjacent = []
if index > 0:
adjacent.append(index - 1)
if index < len(self.objectives) - 1:
adjacent.append(index + 1)
return adjacent
def get_adjacent_objectives(self, index: int) -> List[Union[Unit, HQ]]:
"""获取相邻的目标"""
adjacent_indices = self.get_adjacent_indices(index)
return [self.objectives[i] for i in adjacent_indices]
def is_full(self) -> bool:
"""检查战线是否已满"""
return len(self.objectives) >= self.max_objectives
def is_empty(self) -> bool:
"""检查战线是否为空"""
return len(self.objectives) == 0
def get_objective_count(self) -> int:
"""获取目标数量"""
return len(self.objectives)
def __str__(self) -> str:
if not self.objectives:
return f"{self.line_type}: [空]"
obj_strs = []
for i, obj in enumerate(self.objectives):
if isinstance(obj, Unit):
obj_strs.append(f"{i}:{obj.name}")
elif isinstance(obj, HQ):
obj_strs.append(f"{i}:HQ")
else:
obj_strs.append(f"{i}:未知")
return f"{self.line_type}: [{', '.join(obj_strs)}]"
class Battlefield:
"""重新设计的战场类 - 3条战线布局"""
def __init__(self, player1_id: str, player2_id: str):
# 玩家信息
self.player1_id = player1_id
self.player2_id = player2_id
# 创建HQ
self.player1_hq = HQ(player_id=player1_id)
self.player2_hq = HQ(player_id=player2_id)
# 创建3条战线
self.player1_support = BattleLine(LineType.PLAYER1_SUPPORT, hq=self.player1_hq)
self.front_line = BattleLine(LineType.FRONT) # 共用前线无HQ
self.player2_support = BattleLine(LineType.PLAYER2_SUPPORT, hq=self.player2_hq)
# 单位快速查找索引
self.unit_registry: Dict[UUID, Unit] = {}
# 前线控制权None表示无人控制
self.front_line_controller: Optional[str] = None
def get_player_hq(self, player_id: str) -> Optional[HQ]:
"""获取玩家的HQ"""
if player_id == self.player1_id:
return self.player1_hq
elif player_id == self.player2_id:
return self.player2_hq
return None
def get_player_support_line(self, player_id: str) -> Optional[BattleLine]:
"""获取玩家的支援线"""
if player_id == self.player1_id:
return self.player1_support
elif player_id == self.player2_id:
return self.player2_support
return None
def get_line_by_type(self, line_type: LineType) -> Optional[BattleLine]:
"""根据类型获取战线"""
if line_type == LineType.PLAYER1_SUPPORT:
return self.player1_support
elif line_type == LineType.FRONT:
return self.front_line
elif line_type == LineType.PLAYER2_SUPPORT:
return self.player2_support
return None
def deploy_unit_to_support(self, unit: Unit, player_id: str) -> bool:
"""部署单位到玩家支援线"""
support_line = self.get_player_support_line(player_id)
if not support_line:
return False
if support_line.add_unit(unit):
unit.owner = player_id
self.unit_registry[unit.id] = unit
return True
return False
def deploy_unit_to_front(self, unit: Unit, player_id: str) -> bool:
"""部署单位到前线"""
if self.front_line.add_unit(unit):
unit.owner = player_id
self.unit_registry[unit.id] = unit
# 更新前线控制权
self._update_front_line_control()
return True
return False
def _update_front_line_control(self) -> None:
"""更新前线控制权"""
units = self.front_line.get_all_units()
if not units:
self.front_line_controller = None
return
# 检查前线上的单位是否都属于同一玩家
owners = set(unit.owner for unit in units)
if len(owners) == 1:
self.front_line_controller = list(owners)[0]
else:
self.front_line_controller = None # 混合控制或争夺中
def remove_unit(self, unit: Unit) -> bool:
"""从战场移除单位"""
if not unit.position:
return False
line_type, _ = unit.position
battle_line = self.get_line_by_type(line_type)
if battle_line and battle_line.remove_unit(unit):
if unit.id in self.unit_registry:
del self.unit_registry[unit.id]
# 如果是前线单位,更新控制权
if line_type == LineType.FRONT:
self._update_front_line_control()
return True
return False
def get_all_units(self, player_id: Optional[str] = None) -> List[Unit]:
"""获取所有单位,可按玩家筛选"""
all_units = []
# 收集所有战线的单位
for line in [self.player1_support, self.front_line, self.player2_support]:
all_units.extend(line.get_all_units())
# 按玩家筛选
if player_id:
all_units = [unit for unit in all_units if unit.owner == player_id]
return all_units
def find_unit(self, unit_id: UUID) -> Optional[Unit]:
"""通过ID查找单位"""
return self.unit_registry.get(unit_id)
def get_units_with_keyword(self, keyword: str, player_id: Optional[str] = None) -> List[Unit]:
"""获取具有特定关键词的所有单位"""
units = self.get_all_units(player_id)
return [unit for unit in units if unit.has_keyword(keyword)]
def is_game_over(self) -> Tuple[bool, Optional[str]]:
"""检查游戏是否结束,返回(是否结束, 胜利者ID)"""
if self.player1_hq.is_destroyed():
return True, self.player2_id
elif self.player2_hq.is_destroyed():
return True, self.player1_id
return False, None
def get_valid_attack_targets_for_unit(self, attacker: Unit) -> List[Union[Unit, HQ]]:
"""获取单位可以攻击的所有目标"""
if not attacker.owner or not attacker.position:
return []
valid_targets = []
attacker_line, attacker_index = attacker.position
# 根据单位类型和位置确定攻击规则
if attacker.unit_type.name == "ARTILLERY":
# 火炮可以攻击任意敌方目标
valid_targets = self._get_all_enemy_objectives(attacker.owner)
elif attacker.unit_type.name in ["FIGHTER", "BOMBER"]:
# 飞机可以攻击任意敌方目标
valid_targets = self._get_all_enemy_objectives(attacker.owner)
else:
# 常规单位攻击规则
if attacker_line == LineType.FRONT:
# 前线单位可以攻击敌方支援线
enemy_id = self.get_enemy_player_id(attacker.owner)
enemy_support = self.get_player_support_line(enemy_id)
if enemy_support:
valid_targets = enemy_support.get_all_objectives()
else:
# 支援线单位只能攻击前线单位
front_units = self.front_line.get_all_units()
enemy_units = [u for u in front_units if u.owner != attacker.owner]
valid_targets = enemy_units
# 应用守护等规则过滤目标
return self._filter_protected_targets(valid_targets, attacker)
def _get_all_enemy_objectives(self, player_id: str) -> List[Union[Unit, HQ]]:
"""获取所有敌方目标(单位+HQ"""
enemy_id = self.get_enemy_player_id(player_id)
if not enemy_id:
return []
targets = []
# 敌方支援线目标
enemy_support = self.get_player_support_line(enemy_id)
if enemy_support:
targets.extend(enemy_support.get_all_objectives())
# 前线敌方单位
front_units = self.front_line.get_all_units()
enemy_front_units = [u for u in front_units if u.owner == enemy_id]
targets.extend(enemy_front_units)
return targets
def _filter_protected_targets(self, targets: List[Union[Unit, HQ]],
attacker: Unit) -> List[Union[Unit, HQ]]:
"""过滤受保护的目标"""
valid_targets = []
for target in targets:
can_attack = True
if isinstance(target, Unit):
# 检查烟幕保护
if target.has_keyword("SMOKESCREEN"):
can_attack = False
# 检查守护保护
if self._is_target_guarded(target) and attacker.unit_type.name not in ["BOMBER", "ARTILLERY"]:
can_attack = False
if can_attack:
valid_targets.append(target)
return valid_targets
def _is_target_guarded(self, target: Union[Unit, HQ]) -> bool:
"""检查目标是否被守护"""
if not isinstance(target, Unit) or not target.position:
return False
line_type, target_index = target.position
battle_line = self.get_line_by_type(line_type)
if not battle_line:
return False
# 检查相邻位置是否有守护单位
adjacent_objectives = battle_line.get_adjacent_objectives(target_index)
for obj in adjacent_objectives:
if isinstance(obj, Unit) and obj.has_keyword("GUARD"):
return True
return False
def get_enemy_player_id(self, player_id: str) -> Optional[str]:
"""获取敌方玩家ID"""
if player_id == self.player1_id:
return self.player2_id
elif player_id == self.player2_id:
return self.player1_id
return None
def __str__(self) -> str:
front_control = f" (控制: {self.front_line_controller})" if self.front_line_controller else " (无人控制)"
return f"""
=== 战场状态 ===
{self.player1_support}
{self.front_line}{front_control}
{self.player2_support}
==================
"""