kards-env/kards_battle/units/unit.py

299 lines
11 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.

"""
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})"