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