kards-env/interactive/battle_visualizer.py

323 lines
13 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
"""
战场可视化模块
提供彩色终端显示和详细的战场状态展示
"""
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']} <unit_id> [pos] - 部署单位到支援线 (简写: d, dp)
{c['CYAN']}move{c['RESET']} <from> <to> - 移动单位到前线 (简写: m, mv)
{c['CYAN']}attack{c['RESET']} <from> <to> - 攻击目标 (: F0 S1) (简写: a, at)
{c['CYAN']}info{c['RESET']} <index> - 查看单位详情 (简写: 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']} <player> <amt> - 设置Kredits (0=P1, 1=P2)
{c['CYAN']}sets{c['RESET']} <player> <amt> - 设置Kredits Slot
{c['YELLOW']}测试场景:{c['RESET']}
{c['CYAN']}scenario{c['RESET']} <name> - 加载预设场景
{c['CYAN']}scenarios{c['RESET']} - 列出所有场景
{c['YELLOW']}保存/加载:{c['RESET']}
{c['CYAN']}save{c['RESET']} <filename> - 保存当前状态
{c['CYAN']}load{c['RESET']} <filename> - 加载保存的状态
{c['GREEN']}提示:{c['RESET']}
- 使用 Tab 键自动补全命令
- 输入 'q' 快速退出
- 战线类型: SUPPORT1, FRONT, SUPPORT2
"""
print(help_text)