kards-env/interactive/battle_visualizer.py

323 lines
13 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.

"""
战场可视化模块
提供彩色终端显示和详细的战场状态展示
"""
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)