kards-env/kards_battle/units/unit.py

299 lines
11 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
"""
Unit基类定义
KARDS游戏中的单位基础实现
"""
from typing import Set, List, Optional, Dict, Any
from dataclasses import dataclass, field
from uuid import uuid4, UUID
from ..core.enums import UnitType, LineType
@dataclass
class UnitStats:
"""单位属性数据类"""
attack: int
defense: int
current_defense: int
operation_cost: int
def __post_init__(self):
if self.current_defense > self.defense:
self.current_defense = self.defense
def take_damage(self, damage: int) -> int:
"""受到伤害,返回实际受到的伤害"""
if damage <= 0:
return 0
# 记录伤害前的血量
old_defense = self.current_defense
# 直接扣除伤害血量可以降到0或更低
self.current_defense -= damage
# 但不能低于0
if self.current_defense < 0:
self.current_defense = 0
# 返回实际造成的伤害
return old_defense - self.current_defense
def heal(self, amount: int) -> int:
"""恢复防御值,返回实际恢复的量"""
max_heal = self.defense - self.current_defense
actual_heal = min(amount, max_heal)
self.current_defense += actual_heal
return actual_heal
def is_alive(self) -> bool:
"""判断单位是否存活"""
return self.current_defense > 0
class Unit:
"""KARDS单位基类"""
def __init__(
self,
name: str,
unit_type: UnitType,
attack: int,
defense: int,
operation_cost: int,
keywords: Optional[Set[str]] = None,
abilities: Optional[List] = None
):
self.id: UUID = uuid4()
self.name = name
self.unit_type = unit_type
self.stats = UnitStats(attack, defense, defense, operation_cost)
# 关键词集合
self.keywords: Set[str] = keywords or set()
# 能力列表稍后实现具体的Ability类
self.abilities: List = abilities or []
# 单位状态
self.position: Optional[tuple] = None # (line_type, index)
self.owner: Optional[str] = None # 玩家ID
# 回合制行动限制
self.deployed_this_turn: bool = True # 刚部署的单位当回合不能行动
self.has_moved_this_turn: bool = False # 本回合是否已移动
self.has_attacked_this_turn: bool = False # 本回合是否已攻击
self.attacks_this_turn: int = 0 # 本回合攻击次数
self.max_attacks_per_turn: int = 1 # 每回合最大攻击次数
# 特殊能力标记
self.can_operate_immediately: bool = False # Blitz关键词 - 部署当回合就能行动
self.can_move_and_attack: bool = False # Tank关键词 - 可以移动后攻击
self.can_attack_multiple_times: bool = False # Fury等关键词
# YAML加载的额外属性
self.nation: Optional[str] = None # 单位所属国家
self.definition_id: Optional[str] = None # YAML定义中的ID
# 临时效果和修正器
self.temporary_modifiers: Dict[str, Any] = {}
self.status_effects: Set[str] = set() # 如"Pinned"等状态
# 应用关键词能力
self.apply_keyword_abilities()
def has_keyword(self, keyword: str) -> bool:
"""检查单位是否具有特定关键词"""
return keyword in self.keywords
def add_keyword(self, keyword: str) -> None:
"""添加关键词"""
self.keywords.add(keyword)
# 添加守护关键词时移除烟幕
if keyword == "GUARD" and self.has_keyword("SMOKESCREEN"):
self.remove_keyword("SMOKESCREEN")
def remove_keyword(self, keyword: str) -> None:
"""移除关键词"""
self.keywords.discard(keyword)
def check_smokescreen_conditions(self) -> None:
"""检查并移除不符合条件的烟幕"""
if not self.has_keyword("SMOKESCREEN"):
return
# 检查是否具有守护关键词
if self.has_keyword("GUARD"):
self.remove_keyword("SMOKESCREEN")
return
# 检查是否在前线(前线单位自动失去烟幕)
if self.position and self.position[0] == LineType.FRONT:
self.remove_keyword("SMOKESCREEN")
return
# 检查是否不在己方支援线
if self.position and self.owner:
current_line = self.position[0]
# 如果不在支援线,失去烟幕
if current_line != LineType.PLAYER1_SUPPORT and current_line != LineType.PLAYER2_SUPPORT:
self.remove_keyword("SMOKESCREEN")
return
def get_effective_attack(self) -> int:
"""获取有效攻击力(包含修正器)"""
base_attack = self.stats.attack
# TODO: 应用临时修正器
return base_attack
def get_effective_defense(self) -> int:
"""获取有效防御力上限(包含修正器)"""
base_defense = self.stats.defense
# TODO: 应用临时修正器
return base_defense
def can_move(self) -> bool:
"""检查单位是否可以移动"""
# 刚部署的单位不能移动除非有Blitz
if self.deployed_this_turn and not self.can_operate_immediately:
return False
# 已经移动过的单位不能再移动
if self.has_moved_this_turn:
return False
# 已经攻击过的单位不能移动除非有Tank能力
if self.has_attacked_this_turn and not self.can_move_and_attack:
return False
return True
def can_attack(self) -> bool:
"""检查单位是否可以攻击"""
# 被压制的单位不能主动攻击
if self.has_keyword("PINNED"):
return False
# 刚部署的单位不能攻击除非有Blitz
if self.deployed_this_turn and not self.can_operate_immediately:
return False
# 已经达到最大攻击次数
if self.attacks_this_turn >= self.max_attacks_per_turn:
return False
# 已经移动过的单位不能攻击除非有Tank能力
if self.has_moved_this_turn and not self.can_move_and_attack:
return False
return True
def perform_move(self):
"""执行移动行动"""
self.has_moved_this_turn = True
# 移动后失去烟幕
if self.has_keyword("SMOKESCREEN"):
self.remove_keyword("SMOKESCREEN")
def perform_attack(self):
"""执行攻击行动"""
self.has_attacked_this_turn = True
self.attacks_this_turn += 1
# 攻击后失去烟幕
if self.has_keyword("SMOKESCREEN"):
self.remove_keyword("SMOKESCREEN")
def start_new_turn(self):
"""开始新回合时重置状态"""
self.deployed_this_turn = False
self.has_moved_this_turn = False
self.has_attacked_this_turn = False
self.attacks_this_turn = 0
def apply_keyword_abilities(self):
"""根据关键词设置特殊能力"""
if self.has_keyword("BLITZ"):
self.can_operate_immediately = True
if self.has_keyword("TANK") or self.unit_type == UnitType.TANK:
self.can_move_and_attack = True
if self.has_keyword("FURY"):
self.can_attack_multiple_times = True
self.max_attacks_per_turn = 2
def can_attack_target(self, target: 'Unit', battlefield) -> bool:
"""判断是否能攻击目标单位"""
# 基础规则:前线只能攻击对方前线,除非有特殊能力
if self.position is None or target.position is None:
return False
my_line, my_pos = self.position
target_line, target_pos = target.position
# 特殊单位类型的攻击规则
if self.unit_type == UnitType.ARTILLERY:
# 火炮可以跨战线攻击
return True
elif self.unit_type == UnitType.FIGHTER:
# 战斗机可以攻击任意位置,但不能攻击被守护的目标
return not self._is_target_guarded(target, battlefield)
elif self.unit_type == UnitType.BOMBER:
# 轰炸机可以攻击任意位置,但不能攻击与战斗机同排的目标
return not self._is_target_protected_by_fighter(target, battlefield)
else:
# 常规单位攻击规则:支援线攻击敌方前线,前线攻击敌方支援线
if my_line == LineType.FRONT:
# 前线单位攻击敌方支援线
return target_line in [LineType.PLAYER1_SUPPORT, LineType.PLAYER2_SUPPORT]
elif my_line in [LineType.PLAYER1_SUPPORT, LineType.PLAYER2_SUPPORT]:
# 支援线单位攻击敌方前线
return target_line == LineType.FRONT
else:
return False
def _is_target_guarded(self, target: 'Unit', battlefield) -> bool:
"""检查目标是否被守护"""
# TODO: 实现守护逻辑检查
return False
def _is_target_protected_by_fighter(self, target: 'Unit', battlefield) -> bool:
"""检查目标是否被战斗机保护"""
# TODO: 实现战斗机保护逻辑
return False
def can_be_attacked_by(self, attacker: 'Unit', battlefield) -> bool:
"""判断是否能被指定单位攻击"""
# 烟幕保护
if self.has_keyword("SMOKESCREEN"):
return False
# 守护保护
if self._is_guarded(battlefield):
# 只有轰炸机和火炮能攻击被守护的单位
return attacker.unit_type in [UnitType.BOMBER, UnitType.ARTILLERY]
return True
def _is_guarded(self, battlefield) -> bool:
"""检查自身是否被守护"""
# TODO: 实现守护检查逻辑
return False
def reset_operation_status(self) -> None:
"""重置行动状态(回合开始时调用)"""
# 已移除 has_operated_this_turn使用新的细分状态
# 移除Blitz状态如果是临时的
if "BLITZ" in self.temporary_modifiers:
self.can_operate_immediately = False
def __str__(self) -> str:
keywords_str = f" [{', '.join(self.keywords)}]" if self.keywords else ""
nation_str = f" ({self.nation})" if self.nation else ""
return f"{self.name}{nation_str} ({self.unit_type}) {self.stats.attack}/{self.stats.current_defense}{keywords_str}"
def __repr__(self) -> str:
return f"Unit(id={self.id.hex[:8]}, name='{self.name}', type={self.unit_type}, stats={self.stats.attack}/{self.stats.current_defense})"