445 lines
17 KiB
Python
445 lines
17 KiB
Python
"""
|
||
命令解析器模块
|
||
处理用户输入的命令并执行相应操作
|
||
"""
|
||
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 |