kards-env/interactive/command_parser.py

445 lines
17 KiB
Python
Raw Normal View History

2025-09-05 17:05:43 +08:00
"""
命令解析器模块
处理用户输入的命令并执行相应操作
"""
from typing import List, Tuple, Optional, Dict, Any
from kards_battle.core.battle_engine import BattleEngine
from kards_battle.core.enums import LineType
from kards_battle.units.unit_loader import load_unit, list_all_units
class CommandParser:
"""命令解析器"""
def __init__(self, engine: BattleEngine):
self.engine = engine
self.commands = {
# 显示命令
'show': self.cmd_show,
's': self.cmd_show,
'list': self.cmd_list,
'ls': self.cmd_list,
'info': self.cmd_info,
'i': self.cmd_info,
# 单位操作
'deploy': self.cmd_deploy,
'd': self.cmd_deploy,
'dp': self.cmd_deploy,
'move': self.cmd_move,
'm': self.cmd_move,
'mv': self.cmd_move,
'attack': self.cmd_attack,
'a': self.cmd_attack,
'at': self.cmd_attack,
# 游戏控制
'endturn': self.cmd_endturn,
'end': self.cmd_endturn,
'reset': self.cmd_reset,
'switch': self.cmd_switch,
# 资源管理
'kredits': self.cmd_kredits,
'k': self.cmd_kredits,
'setk': self.cmd_set_kredits,
'sets': self.cmd_set_slot,
# 帮助
'help': self.cmd_help,
'h': self.cmd_help,
'?': self.cmd_help,
}
# 命令历史
self.history = []
self.last_result = None
def parse(self, input_str: str) -> Tuple[bool, str]:
"""解析并执行命令,返回(成功, 消息)"""
input_str = input_str.strip()
if not input_str:
return True, ""
# 保存到历史
self.history.append(input_str)
# 分割命令和参数
parts = input_str.split()
cmd = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []
# 查找并执行命令
if cmd in self.commands:
try:
success, message = self.commands[cmd](args)
self.last_result = (success, message)
return success, message
except Exception as e:
return False, f"命令执行错误: {str(e)}"
else:
return False, f"未知命令: {cmd}. 输入 'help' 查看帮助"
def cmd_show(self, args: List[str]) -> Tuple[bool, str]:
"""显示战场状态"""
detailed = 'detailed' in args or 'd' in args
return True, f"detailed={detailed}"
def cmd_list(self, args: List[str]) -> Tuple[bool, str]:
"""列出可用单位"""
if not args:
nation = 'all'
else:
nation = args[0].lower()
all_units = list_all_units()
if nation == 'germany' or nation == 'ger':
units = [u for u in all_units if u.startswith('ger_')]
title = "德军单位"
elif nation == 'usa' or nation == 'us':
units = [u for u in all_units if u.startswith('usa_')]
title = "美军单位"
else:
units = all_units
title = "所有单位"
result = f"\n=== {title} ({len(units)}个) ===\n"
for unit_id in sorted(units):
unit = load_unit(unit_id)
result += f" {unit_id:30} - {unit.name} ({unit.unit_type.name}) {unit.stats.attack}/{unit.stats.defense}\n"
return True, result
def cmd_info(self, args: List[str]) -> Tuple[bool, str]:
"""查看单位详细信息"""
if not args:
return False, "用法: info <unit_index>"
try:
index = int(args[0])
# 查找单位
all_units = []
for unit in self.engine.battlefield.player1_support.get_all_units():
if unit:
all_units.append(unit)
for unit in self.engine.battlefield.front_line.get_all_units():
if unit:
all_units.append(unit)
for unit in self.engine.battlefield.player2_support.get_all_units():
if unit:
all_units.append(unit)
if index >= len(all_units):
return False, f"单位索引 {index} 不存在"
return True, f"unit_info:{index}"
except ValueError:
return False, "索引必须是数字"
def cmd_deploy(self, args: List[str]) -> Tuple[bool, str]:
"""部署单位"""
if not args:
return False, "用法: deploy <unit_id> [position]"
unit_id = args[0]
position = int(args[1]) if len(args) > 1 else None
try:
# 加载单位
unit = load_unit(unit_id)
# 部署到当前玩家的支援线
player_id = self.engine.active_player
result = self.engine.deploy_unit_to_support(unit, player_id, position)
if result['success']:
return True, f"✅ 成功部署 {unit.name} 到位置 {result['position']}"
else:
return False, f"❌ 部署失败: {result['reason']}"
except ValueError as e:
return False, f"无法加载单位 {unit_id}: {str(e)}"
def cmd_move(self, args: List[str]) -> Tuple[bool, str]:
"""移动单位 - 从己方支援线移动到前线"""
if len(args) < 2:
return False, "用法: move <from_pos> <to_pos> (从支援线位置移动到前线位置)"
try:
from_pos = int(args[0])
target_pos = int(args[1])
# 获取己方支援线上的单位
player_name = self.engine.player_names[self.engine.active_player]
if self.engine.active_player == 0:
support_line = self.engine.battlefield.player1_support
else:
support_line = self.engine.battlefield.player2_support
# 找到要移动的单位
unit = support_line.get_unit_at_index(from_pos)
if not unit:
return False, f"在支援线位置 {from_pos} 没有找到单位"
# 检查是否是单位而不是HQ或其他对象
from kards_battle.units.unit import Unit
if not isinstance(unit, Unit):
return False, f"只有单位才能移动,{type(unit).__name__}无法移动"
# 检查单位所有权
if unit.owner != player_name:
return False, f"单位 {unit.name} 不属于你"
# 目标始终是前线
line_type = LineType.FRONT
# 执行移动
result = self.engine.move_unit(unit.id, (line_type, target_pos), self.engine.active_player)
if result['success']:
return True, f"✅ 成功移动 {unit.name} 到前线[{target_pos}]"
else:
return False, f"❌ 移动失败: {result['reason']}"
except (ValueError, IndexError) as e:
return False, f"参数错误: {str(e)}"
def _parse_position(self, pos_str: str) -> Tuple[str, int]:
"""解析位置字符串,支持 F0, S1, FRONT, SUPPORT 等格式"""
pos_str = pos_str.upper()
# 简化格式: F0, S1, F2, S3 等
if len(pos_str) >= 2 and pos_str[0] in ['F', 'S'] and pos_str[1:].isdigit():
line = 'FRONT' if pos_str[0] == 'F' else 'SUPPORT'
pos = int(pos_str[1:])
return line, pos
# 完整格式处理会在调用方进行
raise ValueError(f"无效的位置格式: {pos_str}")
def _convert_to_line_type(self, line: str, pos: int, is_source: bool = True):
"""将字符串战线转换为LineType枚举"""
from kards_battle.core.enums import LineType
if line == 'FRONT':
return (LineType.FRONT, pos)
elif line == 'SUPPORT':
# 根据攻击方向确定具体的支援线
if is_source:
# 攻击来源:己方支援线
if self.engine.active_player == 0:
return (LineType.PLAYER1_SUPPORT, pos)
else:
return (LineType.PLAYER2_SUPPORT, pos)
else:
# 攻击目标:敌方支援线
if self.engine.active_player == 0:
return (LineType.PLAYER2_SUPPORT, pos)
else:
return (LineType.PLAYER1_SUPPORT, pos)
else:
raise ValueError(f"未知的战线类型: {line}")
def cmd_attack(self, args: List[str]) -> Tuple[bool, str]:
"""攻击目标 - 支持简化格式 F0, S1 等"""
if len(args) < 2:
return False, "用法: attack <from> <to> (如: attack F0 S1 或 attack FRONT 0 SUPPORT 1)"
try:
if len(args) == 2:
# 简化格式: attack F0 S1
from_line, from_pos = self._parse_position(args[0])
to_line, to_pos = self._parse_position(args[1])
elif len(args) == 4:
# 完整格式: attack FRONT 0 SUPPORT 1
from_line = args[0].upper()
from_pos = int(args[1])
to_line = args[2].upper()
to_pos = int(args[3])
# 验证完整格式
if from_line not in ['SUPPORT', 'FRONT'] or to_line not in ['SUPPORT', 'FRONT']:
return False, "战线必须是 SUPPORT/S 或 FRONT/F"
else:
return False, "用法: attack <from> <to> (如: F0 S1) 或 attack <from_line> <from_pos> <to_line> <to_pos>"
# 获取攻击者
from_location = self._convert_to_line_type(from_line, from_pos, is_source=True)
attacker = None
player_name = self.engine.player_names[self.engine.active_player] # 使用玩家名称
for unit in self.engine.battlefield.get_all_units(player_name):
if unit.position == from_location:
attacker = unit
break
if not attacker:
return False, f"{from_line}:{from_pos} 没有找到己方单位"
# 获取目标 - 需要检查敌方单位
enemy_player_name = self.engine.player_names[1 - self.engine.active_player] # 使用敌方玩家名称
to_location = self._convert_to_line_type(to_line, to_pos, is_source=False)
target = None
# 先检查敌方单位
for unit in self.engine.battlefield.get_all_units(enemy_player_name):
if unit.position == to_location:
target = unit
break
# 如果没有找到敌方单位检查是否是HQ目标
target_id = None
if not target:
if to_line == 'SUPPORT':
enemy_player_index = 1 - self.engine.active_player
if enemy_player_index == 0:
hq = self.engine.battlefield.player1_hq
target_id = "hq1"
else:
hq = self.engine.battlefield.player2_hq
target_id = "hq2"
# 检查HQ位置
if hasattr(hq, 'position_index') and hq.position_index == to_pos:
target = hq
if not target:
return False, f"{to_line}:{to_pos} 没有找到敌方目标"
# 执行攻击 - HQ使用字符串ID单位使用UUID
if target_id:
result = self.engine.attack_target(attacker.id, target_id, self.engine.active_player)
else:
result = self.engine.attack_target(attacker.id, target.id, self.engine.active_player)
if result['success']:
damage = result.get('damage_dealt', result.get('damage', 0))
target_name = getattr(target, 'name', 'HQ')
message = f"{attacker.name} 攻击 {target_name},造成 {damage} 点伤害"
# 检查游戏是否结束
if result.get('game_over', False):
winner_name = result.get('winner_name', '未知')
message += f"\n\n🎉 游戏结束! {winner_name} 获得胜利!"
return True, message
else:
return False, f"❌ 攻击失败: {result['reason']}"
except (ValueError, IndexError) as e:
return False, f"参数错误: {str(e)}"
def cmd_endturn(self, args: List[str]) -> Tuple[bool, str]:
"""结束回合"""
result = self.engine.end_turn()
msg = f"回合结束!\n"
msg += f"新回合: {result['turn_number']}.{result['turn_phase']}\n"
msg += f"当前玩家: {result['new_active_player']}\n"
msg += f"Kredits: {result['kredits']}/{result['kredits_slot']}"
if result['is_new_round']:
msg += f"\n🆕 新的完整回合开始!"
return True, msg
def cmd_reset(self, args: List[str]) -> Tuple[bool, str]:
"""重置战场"""
# 需要创建新的引擎实例
return True, "RESET_ENGINE"
def cmd_switch(self, args: List[str]) -> Tuple[bool, str]:
"""切换活动玩家(调试用)"""
self.engine.active_player = 1 - self.engine.active_player
new_player = self.engine.player_names[self.engine.active_player]
return True, f"切换到玩家: {new_player}"
def cmd_kredits(self, args: List[str]) -> Tuple[bool, str]:
"""查看资源状态"""
p1_k = self.engine.player1_kredits
p1_s = self.engine.player1_kredits_slot
p2_k = self.engine.player2_kredits
p2_s = self.engine.player2_kredits_slot
msg = f"\n资源状态:\n"
msg += f" {self.engine.player_names[0]}: {p1_k}/{p1_s} Kredits\n"
msg += f" {self.engine.player_names[1]}: {p2_k}/{p2_s} Kredits"
return True, msg
def cmd_set_kredits(self, args: List[str]) -> Tuple[bool, str]:
"""设置Kredits"""
if len(args) < 2:
return False, "用法: setk <player_id> <amount>"
try:
player_id = int(args[0])
amount = int(args[1])
if player_id not in [0, 1]:
return False, "玩家ID必须是0或1"
self.engine.debug_set_kredits(player_id, kredits=amount)
return True, f"设置玩家{player_id} Kredits为 {amount}"
except ValueError:
return False, "参数必须是数字"
def cmd_set_slot(self, args: List[str]) -> Tuple[bool, str]:
"""设置Kredits Slot"""
if len(args) < 2:
return False, "用法: sets <player_id> <amount>"
try:
player_id = int(args[0])
amount = int(args[1])
if player_id not in [0, 1]:
return False, "玩家ID必须是0或1"
self.engine.debug_set_kredits(player_id, kredits_slot=amount)
return True, f"设置玩家{player_id} Kredits Slot为 {amount}"
except ValueError:
return False, "参数必须是数字"
def cmd_help(self, args: List[str]) -> Tuple[bool, str]:
"""显示帮助"""
return True, "SHOW_HELP"
def _get_unit_by_index(self, index: int) -> Optional[Any]:
"""根据全局索引获取单位"""
current_index = 0
# 检查Player1支援线
for unit in self.engine.battlefield.player1_support.get_all_units():
if unit:
if current_index == index:
return unit
current_index += 1
# 检查前线
for unit in self.engine.battlefield.front_line.get_all_units():
if unit:
if current_index == index:
return unit
current_index += 1
# 检查Player2支援线
for unit in self.engine.battlefield.player2_support.get_all_units():
if unit:
if current_index == index:
return unit
current_index += 1
return None
def get_command_suggestions(self, partial: str) -> List[str]:
"""获取命令建议(用于自动补全)"""
if not partial:
return list(self.commands.keys())
suggestions = []
for cmd in self.commands.keys():
if cmd.startswith(partial.lower()):
suggestions.append(cmd)
return suggestions