kards-env/interactive/interactive_test.py

283 lines
9.4 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
#!/usr/bin/env python3
"""
KARDS战斗引擎交互式测试环境
提供完整的命令行界面用于测试和调试战斗系统
"""
import sys
import os
import pickle
import readline
from pathlib import Path
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from kards_battle.core.battle_engine import BattleEngine
from kards_battle.units.unit_loader import load_unit, list_all_units
from interactive.battle_visualizer import BattleVisualizer
from interactive.command_parser import CommandParser
from interactive.test_scenarios import TestScenarios
class InteractiveTest:
"""交互式测试环境主类"""
def __init__(self):
self.engine = None
self.visualizer = None
self.parser = None
self.scenarios = TestScenarios()
self.running = True
self.save_dir = Path("interactive/saves")
self.save_dir.mkdir(exist_ok=True)
# 设置命令历史和自动补全
self.setup_readline()
# 初始化引擎
self.reset_engine()
def setup_readline(self):
"""设置readline自动补全和历史"""
# 历史文件
histfile = Path("interactive/.history")
try:
readline.read_history_file(histfile)
except FileNotFoundError:
pass
# 设置自动补全 - 兼容不同系统
try:
readline.set_completer(self.completer)
# 尝试不同的绑定方式以兼容WSL
if 'libedit' in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
else:
readline.parse_and_bind('tab: complete')
# WSL额外设置
readline.parse_and_bind('set show-all-if-ambiguous on')
readline.parse_and_bind('set completion-ignore-case on')
except:
# 如果自动补全失败,继续运行但不使用该功能
print("提示: Tab自动补全在当前环境不可用")
# 保存历史
import atexit
atexit.register(readline.write_history_file, histfile)
def completer(self, text, state):
"""自动补全函数"""
if state == 0:
# 生成补全选项
if self.parser:
self.matches = self.parser.get_command_suggestions(text)
else:
self.matches = []
try:
return self.matches[state]
except IndexError:
return None
def reset_engine(self, player1: str = "Germany", player2: str = "USA"):
"""重置战斗引擎"""
self.engine = BattleEngine(player1, player2, debug_mode=True)
self.visualizer = BattleVisualizer(self.engine)
self.parser = CommandParser(self.engine)
print(f"\n✨ 新游戏开始: {player1} vs {player2}")
def run(self):
"""主循环"""
self.show_welcome()
self.visualizer.display()
while self.running:
try:
# 获取输入
prompt = self.get_prompt()
user_input = input(prompt).strip()
# 快速退出
if user_input.lower() in ['q', 'quit', 'exit']:
self.running = False
continue
# 处理命令
if user_input:
self.process_command(user_input)
except KeyboardInterrupt:
print("\n\n使用 'quit' 命令退出")
continue
except EOFError:
self.running = False
break
self.show_goodbye()
def get_prompt(self) -> str:
"""生成命令提示符 - 无颜色版本以避免显示问题"""
player_id = self.engine.active_player
player_name = self.engine.player_names[player_id]
turn = self.engine.current_turn
kredits = self.engine.get_kredits(player_id)
kredits_slot = self.engine.get_kredits_slot(player_id)
# 使用简洁的无颜色格式
prompt = f"\nP{player_id+1}:{player_name} [回合{turn}] K:{kredits}/{kredits_slot} > "
return prompt
def process_command(self, user_input: str):
"""处理用户命令"""
# 特殊命令处理
if user_input.startswith('scenario'):
self.handle_scenario(user_input)
return
elif user_input == 'scenarios':
self.list_scenarios()
return
elif user_input.startswith('save'):
self.save_state(user_input)
return
elif user_input.startswith('load'):
self.load_state(user_input)
return
# 使用命令解析器
success, message = self.parser.parse(user_input)
# 处理特殊返回值
if message == "RESET_ENGINE":
self.reset_engine()
self.visualizer.display()
elif message == "SHOW_HELP":
self.visualizer.display_help()
elif message.startswith("detailed="):
detailed = message.split('=')[1] == 'True'
self.visualizer.display(detailed)
elif message.startswith("unit_info:"):
index = int(message.split(':')[1])
self.show_unit_info(index)
else:
# 显示命令结果
if message:
print(message)
# 如果命令成功,刷新显示
if success and user_input.split()[0] in ['deploy', 'd', 'dp', 'move', 'm', 'mv', 'attack', 'a', 'at', 'endturn', 'end']:
self.visualizer.display()
def handle_scenario(self, user_input: str):
"""处理场景命令"""
parts = user_input.split()
if len(parts) < 2:
print("用法: scenario <name>")
self.list_scenarios()
return
scenario_name = parts[1]
if self.scenarios.load_scenario(scenario_name, self.engine):
print(f"✅ 成功加载场景: {scenario_name}")
self.visualizer.display()
else:
print(f"❌ 未知场景: {scenario_name}")
self.list_scenarios()
def list_scenarios(self):
"""列出所有场景"""
print("\n=== 可用测试场景 ===")
for name, desc in self.scenarios.list_scenarios().items():
print(f" {name:12} - {desc}")
print("\n使用 'scenario <name>' 加载场景")
def show_unit_info(self, index: int):
"""显示单位信息"""
unit = self.parser._get_unit_by_index(index)
if unit:
self.visualizer.display_unit_info(unit)
else:
print(f"找不到索引为 {index} 的单位")
def save_state(self, user_input: str):
"""保存当前状态"""
parts = user_input.split()
if len(parts) < 2:
print("用法: save <filename>")
return
filename = self.save_dir / f"{parts[1]}.sav"
try:
with open(filename, 'wb') as f:
state = {
'engine': self.engine,
'history': self.parser.history
}
pickle.dump(state, f)
print(f"✅ 状态已保存到: {filename}")
except Exception as e:
print(f"❌ 保存失败: {str(e)}")
def load_state(self, user_input: str):
"""加载保存的状态"""
parts = user_input.split()
if len(parts) < 2:
print("用法: load <filename>")
# 列出可用的存档
saves = list(self.save_dir.glob("*.sav"))
if saves:
print("\n可用存档:")
for save in saves:
print(f" - {save.stem}")
return
filename = self.save_dir / f"{parts[1]}.sav"
try:
with open(filename, 'rb') as f:
state = pickle.load(f)
self.engine = state['engine']
self.visualizer = BattleVisualizer(self.engine)
self.parser = CommandParser(self.engine)
self.parser.history = state['history']
print(f"✅ 状态已加载: {filename}")
self.visualizer.display()
except FileNotFoundError:
print(f"❌ 文件不存在: {filename}")
except Exception as e:
print(f"❌ 加载失败: {str(e)}")
def show_welcome(self):
"""显示欢迎信息"""
c = BattleVisualizer.COLORS
welcome = f"""
{c['BOLD']}{c['CYAN']}{'='*60}{c['RESET']}
{c['BOLD']} KARDS 战斗引擎 - 交互式测试环境 v1.0{c['RESET']}
{c['CYAN']}{'='*60}{c['RESET']}
{c['GREEN']}欢迎使用交互式测试环境{c['RESET']}
快速开始:
输入 {c['YELLOW']}help{c['RESET']} 查看所有命令
输入 {c['YELLOW']}list{c['RESET']} 查看可用单位
输入 {c['YELLOW']}scenarios{c['RESET']} 查看测试场景
输入 {c['YELLOW']}quit{c['RESET']} {c['YELLOW']}q{c['RESET']} 退出
{c['CYAN']}提示: 使用 Tab 键自动补全命令{c['RESET']}
{c['CYAN']}{'='*60}{c['RESET']}
"""
print(welcome)
def show_goodbye(self):
"""显示退出信息"""
c = BattleVisualizer.COLORS
print(f"\n{c['GREEN']}感谢使用!再见!{c['RESET']}\n")
def main():
"""主函数"""
test = InteractiveTest()
test.run()
if __name__ == "__main__":
main()