kards-env/interactive/command_parser.py

445 lines
17 KiB
Python
Raw 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 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