kards-env/kards_battle/battlefield/battlefield.py

410 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
重新设计的战场系统
符合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}
==================
"""