""" 战场可视化模块 提供彩色终端显示和详细的战场状态展示 """ from typing import Optional, List from kards_battle.core.battle_engine import BattleEngine from kards_battle.units.unit import Unit from kards_battle.core.enums import LineType class BattleVisualizer: """战场可视化器""" # ANSI颜色代码 COLORS = { 'RED': '\033[91m', 'GREEN': '\033[92m', 'YELLOW': '\033[93m', 'BLUE': '\033[34m', # 使用标准蓝色而非亮蓝色 'MAGENTA': '\033[95m', 'CYAN': '\033[96m', 'WHITE': '\033[97m', 'BOLD': '\033[1m', 'UNDERLINE': '\033[4m', 'RESET': '\033[0m' } def __init__(self, engine: BattleEngine): self.engine = engine self.battlefield = engine.battlefield def display(self, detailed: bool = False): """显示战场状态""" print(self._format_header()) print(self._format_resources()) print(self._format_battlefield(detailed)) if detailed: print(self._format_units_detail()) def _format_header(self) -> str: """格式化头部信息""" c = self.COLORS active = self.engine.active_player player_name = self.engine.player_names[active] header = f"\n{c['BOLD']}{'='*60}{c['RESET']}\n" header += f"{c['CYAN']}回合 {self.engine.current_turn}.{self.engine.turn_phase}{c['RESET']} | " header += f"{c['YELLOW']}当前玩家: {player_name}{c['RESET']}\n" header += f"{c['BOLD']}{'='*60}{c['RESET']}" return header def _format_resources(self) -> str: """格式化资源信息 - 简化版本""" # 不再显示资源状态部分,因为提示符已经显示当前玩家Kredits # 敌方资源会在战场显示中展示 return "" def _draw_resource_bar(self, current: int, maximum: int) -> str: """绘制资源条""" if maximum == 0: return "" bar_length = 20 filled = int((current / maximum) * bar_length) empty = bar_length - filled bar = "[" bar += "█" * filled bar += "░" * empty bar += "]" return bar def _format_battlefield(self, detailed: bool) -> str: """格式化战场显示 - 己方在下,敌方在上""" c = self.COLORS bf = f"\n{c['GREEN']}🎮 战场状态:{c['RESET']}\n\n" # 根据当前玩家决定显示顺序 current_player = self.engine.active_player p1_name = self.engine.player_names[0] p2_name = self.engine.player_names[1] if current_player == 0: # 玩家1视角:P2在上,前线在中,P1在下 # 敌方支援线(上方) enemy_kredits = self.engine.player2_kredits enemy_slot = self.engine.player2_kredits_slot bf += f"{c['RED']}{p2_name} 支援线 (敌方): {c['YELLOW']}K:{enemy_kredits}/{enemy_slot}{c['RESET']}\n" bf += self._format_line(self.battlefield.player2_support.get_all_objectives(), detailed) bf += "\n" # 前线(中间) bf += f"{c['BOLD']}{c['YELLOW']}━━━ 前线战场 ━━━{c['RESET']}\n" front_objectives = self.battlefield.front_line.get_all_objectives() if not front_objectives: bf += f" {c['WHITE']}[空] (无人控制){c['RESET']}\n" else: controller = self.battlefield.front_line_controller if controller: color = c['BLUE'] if controller == p1_name else c['RED'] bf += f" {color}[{controller} 控制]{c['RESET']}\n" bf += self._format_line(front_objectives, detailed) bf += "\n" # 己方支援线(下方) bf += f"{c['BLUE']}{p1_name} 支援线 (己方):{c['RESET']}\n" bf += self._format_line(self.battlefield.player1_support.get_all_objectives(), detailed) else: # 玩家2视角:P1在上,前线在中,P2在下 # 敌方支援线(上方) enemy_kredits = self.engine.player1_kredits enemy_slot = self.engine.player1_kredits_slot bf += f"{c['BLUE']}{p1_name} 支援线 (敌方): {c['YELLOW']}K:{enemy_kredits}/{enemy_slot}{c['RESET']}\n" bf += self._format_line(self.battlefield.player1_support.get_all_objectives(), detailed) bf += "\n" # 前线(中间) bf += f"{c['BOLD']}{c['YELLOW']}━━━ 前线战场 ━━━{c['RESET']}\n" front_objectives = self.battlefield.front_line.get_all_objectives() if not front_objectives: bf += f" {c['WHITE']}[空] (无人控制){c['RESET']}\n" else: controller = self.battlefield.front_line_controller if controller: color = c['BLUE'] if controller == p1_name else c['RED'] bf += f" {color}[{controller} 控制]{c['RESET']}\n" bf += self._format_line(front_objectives, detailed) bf += "\n" # 己方支援线(下方) bf += f"{c['RED']}{p2_name} 支援线 (己方):{c['RESET']}\n" bf += self._format_line(self.battlefield.player2_support.get_all_objectives(), detailed) return bf def _format_line(self, objectives, detailed: bool) -> str: """格式化一条战线(包括单位和HQ)""" if not objectives: return f" {self.COLORS['WHITE']}[空]{self.COLORS['RESET']}\n" line = "" for i, obj in enumerate(objectives): if obj is None: continue if hasattr(obj, 'name'): # Unit对象 line += self._format_unit(i, obj, detailed) + "\n" else: # HQ对象 line += self._format_hq(i, obj, detailed) + "\n" return line def _format_hq(self, index: int, hq, detailed: bool) -> str: """格式化HQ显示""" c = self.COLORS # HQ图标和基础信息 hq_str = f" [{index}] 🏛️ " hq_str += f"{c['BOLD']}HQ{c['RESET']} " hq_str += f"({c['GREEN']}{hq.current_defense}/{hq.defense}{c['RESET']})" return hq_str def _format_unit(self, index: int, unit: Unit, detailed: bool) -> str: """格式化单位显示""" c = self.COLORS # 单位类型图标 type_icons = { 'INFANTRY': '🚶', 'TANK': '🚗', 'ARTILLERY': '🎯', 'FIGHTER': '✈️', 'BOMBER': '💣' } icon = type_icons.get(unit.unit_type.name, '❓') # 基础信息 - 在index后显示激活费用 unit_str = f" [{index}:{c['MAGENTA']}{unit.stats.operation_cost}K{c['RESET']}] {icon} " # 单位名称(带国家) if unit.nation: nation_color = c['BLUE'] if unit.nation == 'GERMANY' else c['RED'] unit_str += f"{nation_color}{unit.name}{c['RESET']} " else: unit_str += f"{unit.name} " # 属性 unit_str += f"({c['YELLOW']}{unit.stats.attack}/{unit.stats.current_defense}{c['RESET']})" # 关键词 if unit.keywords: keywords_str = ', '.join(unit.keywords) unit_str += f" [{c['CYAN']}{keywords_str}{c['RESET']}]" # 状态指示器 if unit.has_moved_this_turn or unit.has_attacked_this_turn: actions = [] if unit.has_moved_this_turn: actions.append("移动") if unit.has_attacked_this_turn: actions.append(f"攻击{unit.attacks_this_turn}次") unit_str += f" {c['WHITE']}[{'/'.join(actions)}]{c['RESET']}" elif unit.deployed_this_turn and not unit.can_operate_immediately: unit_str += f" {c['YELLOW']}[刚部署]{c['RESET']}" # 费用(详细模式) if detailed: unit_str += f" {c['MAGENTA']}(费用:{unit.stats.operation_cost}){c['RESET']}" return unit_str def _format_units_detail(self) -> str: """格式化详细单位信息""" c = self.COLORS detail = f"\n{c['GREEN']}📋 单位详情:{c['RESET']}\n" all_units = [] # 收集所有单位 for obj in self.battlefield.player1_support.get_all_objectives(): if obj and hasattr(obj, 'name'): # 只处理单位,不处理HQ all_units.append(('P1支援', obj)) for obj in self.battlefield.front_line.get_all_objectives(): if obj and hasattr(obj, 'name'): # 只处理单位,不处理HQ all_units.append(('前线', obj)) for obj in self.battlefield.player2_support.get_all_objectives(): if obj and hasattr(obj, 'name'): # 只处理单位,不处理HQ all_units.append(('P2支援', obj)) # 显示详细信息 for location, unit in all_units: detail += f"\n {c['BOLD']}{unit.name}{c['RESET']} @ {location}\n" detail += f" ID: {unit.id.hex[:8]}\n" detail += f" 类型: {unit.unit_type.name}\n" detail += f" 属性: {unit.stats.attack}/{unit.stats.current_defense} (最大:{unit.stats.defense})\n" detail += f" 费用: {unit.stats.operation_cost}\n" if unit.keywords: detail += f" 关键词: {', '.join(unit.keywords)}\n" if unit.abilities: detail += f" 能力: {len(unit.abilities)}个\n" return detail def display_unit_info(self, unit: Unit): """显示单位详细信息""" c = self.COLORS print(f"\n{c['BOLD']}=== 单位信息 ==={c['RESET']}") print(f"名称: {c['YELLOW']}{unit.name}{c['RESET']}") if unit.nation: print(f"国家: {unit.nation}") if unit.definition_id: print(f"定义ID: {unit.definition_id}") print(f"类型: {unit.unit_type.name}") print(f"攻击/防御: {c['RED']}{unit.stats.attack}{c['RESET']}/{c['GREEN']}{unit.stats.current_defense}/{unit.stats.defense}{c['RESET']}") print(f"操作费用: {c['YELLOW']}{unit.stats.operation_cost}{c['RESET']}") if unit.keywords: print(f"关键词: {c['CYAN']}{', '.join(unit.keywords)}{c['RESET']}") if unit.abilities: print(f"能力数量: {len(unit.abilities)}") if unit.position: line_type, index = unit.position print(f"位置: {line_type} [{index}]") if unit.owner: print(f"拥有者: {unit.owner}") actions_status = [] if unit.has_moved_this_turn: actions_status.append("已移动") if unit.has_attacked_this_turn: actions_status.append(f"已攻击{unit.attacks_this_turn}次") if unit.deployed_this_turn and not unit.can_operate_immediately: actions_status.append("刚部署") status_str = "、".join(actions_status) if actions_status else "可行动" print(f"状态: {status_str}") def display_help(self): """显示帮助信息""" c = self.COLORS help_text = f""" {c['BOLD']}=== 交互式测试环境帮助 ==={c['RESET']} {c['YELLOW']}基础命令:{c['RESET']} {c['CYAN']}show{c['RESET']} [detailed] - 显示战场状态 (简写: s) {c['CYAN']}list{c['RESET']} [nation] - 列出可用单位 (germany/usa/all) (简写: ls) {c['CYAN']}help{c['RESET']} [command] - 显示帮助信息 (简写: h, ?) {c['CYAN']}quit{c['RESET']} - 退出程序 (简写: q) {c['YELLOW']}单位操作:{c['RESET']} {c['CYAN']}deploy{c['RESET']} [pos] - 部署单位到支援线 (简写: d, dp) {c['CYAN']}move{c['RESET']} - 移动单位到前线 (简写: m, mv) {c['CYAN']}attack{c['RESET']} - 攻击目标 (如: F0 S1) (简写: a, at) {c['CYAN']}info{c['RESET']} - 查看单位详情 (简写: i) {c['YELLOW']}游戏控制:{c['RESET']} {c['CYAN']}endturn{c['RESET']} - 结束当前回合 (简写: end) {c['CYAN']}reset{c['RESET']} - 重置战场 {c['CYAN']}switch{c['RESET']} - 切换活动玩家 {c['YELLOW']}资源管理:{c['RESET']} {c['CYAN']}kredits{c['RESET']} - 查看资源状态 (简写: k) {c['CYAN']}setk{c['RESET']} - 设置Kredits (0=P1, 1=P2) {c['CYAN']}sets{c['RESET']} - 设置Kredits Slot {c['YELLOW']}测试场景:{c['RESET']} {c['CYAN']}scenario{c['RESET']} - 加载预设场景 {c['CYAN']}scenarios{c['RESET']} - 列出所有场景 {c['YELLOW']}保存/加载:{c['RESET']} {c['CYAN']}save{c['RESET']} - 保存当前状态 {c['CYAN']}load{c['RESET']} - 加载保存的状态 {c['GREEN']}提示:{c['RESET']} - 使用 Tab 键自动补全命令 - 输入 'q' 快速退出 - 战线类型: SUPPORT1, FRONT, SUPPORT2 """ print(help_text)