kards-env/kards_battle/cards/card_loader.py

350 lines
12 KiB
Python
Raw Permalink 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.

"""
卡牌YAML加载器
从YAML文件加载卡牌定义并创建卡牌对象
"""
import yaml
from typing import List, Dict, Optional, Set, Any
from pathlib import Path
from ..core.enums import Nation, Rarity
from .card import UnitCard, CardStats
from .card_params import ParameterDefinition
class CardLoadError(Exception):
"""卡牌加载错误"""
pass
class CardLoader:
"""卡牌YAML加载器"""
def __init__(self, cards_dir: Optional[str] = None):
"""
初始化卡牌加载器
Args:
cards_dir: 卡牌文件目录,默认为 assets/cards/
"""
if cards_dir is None:
# 从当前文件位置推断assets目录
current_dir = Path(__file__).parent
project_root = current_dir.parent.parent # kards_battle -> kards
self.cards_dir = project_root / "assets" / "cards"
else:
self.cards_dir = Path(cards_dir)
# 缓存模板定义
self._templates = {}
self._templates_loaded = False
def _parse_nation(self, nation_str: str) -> Nation:
"""将字符串转换为Nation枚举"""
nation_map = {
'germany': Nation.GERMANY,
'italy': Nation.ITALY,
'finland': Nation.FINLAND,
'japan': Nation.JAPAN,
'usa': Nation.USA,
'uk': Nation.UK,
'soviet': Nation.SOVIET,
'france': Nation.FRANCE,
'poland': Nation.POLAND,
}
nation_lower = nation_str.lower()
if nation_lower not in nation_map:
raise CardLoadError(f"未知的国家: {nation_str}")
return nation_map[nation_lower]
def _parse_rarity(self, rarity: int) -> Rarity:
"""将整数转换为Rarity枚举"""
rarity_map = {
1: Rarity.ELITE,
2: Rarity.RARE,
3: Rarity.UNCOMMON,
4: Rarity.COMMON
}
if rarity not in rarity_map:
raise CardLoadError(f"无效的稀有度: {rarity}必须是1-4")
return rarity_map[rarity]
def _parse_exile_nations(self, exile_nations: Optional[List[str]]) -> Optional[Set[Nation]]:
"""解析流亡国家列表"""
if not exile_nations:
return None
result = set()
for nation_str in exile_nations:
result.add(self._parse_nation(nation_str))
return result
def _load_yaml_file(self, file_path: Path) -> Dict[str, Any]:
"""加载YAML文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
except FileNotFoundError:
raise CardLoadError(f"卡牌文件不存在: {file_path}")
except yaml.YAMLError as e:
raise CardLoadError(f"YAML解析错误 ({file_path}): {e}")
def _parse_card_parameters(self, params_data: Optional[List[Dict[str, Any]]]) -> List[ParameterDefinition]:
"""解析卡牌参数定义"""
if not params_data:
return []
parameters = []
for param_data in params_data:
try:
param_def = ParameterDefinition.from_dict(param_data)
parameters.append(param_def)
except Exception as e:
raise CardLoadError(f"解析参数定义失败: {e}")
return parameters
def _load_templates(self):
"""加载卡牌模板定义"""
if self._templates_loaded:
return
spec_file = self.cards_dir / "cards_spec.yaml"
if not spec_file.exists():
self._templates_loaded = True
return
try:
spec_data = self._load_yaml_file(spec_file)
templates = spec_data.get('card_templates', [])
for template in templates:
if 'id' not in template:
continue
self._templates[template['id']] = template
except Exception as e:
raise CardLoadError(f"Failed to load templates: {e}")
self._templates_loaded = True
def _apply_template(self, card_data: Dict[str, Any]) -> Dict[str, Any]:
"""应用模板到卡牌数据"""
self._load_templates()
template_id = card_data.get('template')
if not template_id:
return card_data
if template_id not in self._templates:
raise CardLoadError(f"Template not found: {template_id}")
template = self._templates[template_id]
# 创建合并后的卡牌数据
merged_data = dict(card_data) # 复制原始数据
# 合并模板字段(卡牌数据优先)
for key, value in template.items():
if key == 'id': # 跳过模板ID
continue
if key not in merged_data:
merged_data[key] = value
elif key == 'params':
# 参数需要特殊合并逻辑
template_params = template.get('params', [])
card_params = card_data.get('params', [])
merged_data['params'] = template_params + card_params
return merged_data
def _create_unit_card(self, card_data: Dict[str, Any]) -> UnitCard:
"""从数据字典创建单位卡"""
# 应用模板
card_data = self._apply_template(card_data)
required_fields = ['id', 'name', 'type', 'cost', 'nation', 'rarity', 'unit_definition_id']
for field in required_fields:
if field not in card_data:
raise CardLoadError(f"单位卡缺少必需字段: {field}")
# 验证卡牌类型
if card_data['type'] != 'unit':
raise CardLoadError(f"期望单位卡,但得到: {card_data['type']}")
# 解析基础属性
nation = self._parse_nation(card_data['nation'])
rarity = self._parse_rarity(card_data['rarity'])
exile_nations = self._parse_exile_nations(card_data.get('exile_nations'))
# 解析参数定义
parameters = self._parse_card_parameters(card_data.get('params'))
# 创建单位卡
card = UnitCard(
name=card_data['name'],
cost=card_data['cost'],
nation=nation,
rarity=rarity,
unit_definition_id=card_data['unit_definition_id'],
exile_nations=exile_nations,
description=card_data.get('description', ''),
card_id=card_data['id'],
parameters=parameters
)
return card
def load_unit_cards_from_file(self, file_path: str) -> List[UnitCard]:
"""
从YAML文件加载单位卡
Args:
file_path: YAML文件路径相对于cards_dir
Returns:
单位卡列表
"""
full_path = self.cards_dir / file_path
data = self._load_yaml_file(full_path)
if 'unit_cards' not in data:
raise CardLoadError(f"YAML文件中找不到 'unit_cards' 节点: {file_path}")
cards = []
for card_data in data['unit_cards']:
try:
card = self._create_unit_card(card_data)
cards.append(card)
except Exception as e:
raise CardLoadError(f"创建卡牌失败 (文件: {file_path}, ID: {card_data.get('id', 'unknown')}): {e}")
return cards
def load_nation_cards(self, nation: Nation) -> List[UnitCard]:
"""
加载指定国家的所有卡牌
Args:
nation: 国家
Returns:
该国家的所有卡牌
"""
nation_file_map = {
Nation.GERMANY: 'germany.yaml',
Nation.ITALY: 'italy.yaml',
Nation.USA: 'usa.yaml',
Nation.UK: 'uk.yaml',
Nation.SOVIET: 'soviet.yaml',
Nation.JAPAN: 'japan.yaml',
Nation.FRANCE: 'france.yaml',
Nation.POLAND: 'poland.yaml',
Nation.FINLAND: 'finland.yaml',
}
if nation not in nation_file_map:
raise CardLoadError(f"不支持的国家: {nation}")
filename = nation_file_map[nation]
# 检查文件是否存在
file_path = self.cards_dir / filename
if not file_path.exists():
# 如果文件不存在,返回空列表(某些国家可能还没有卡牌)
return []
return self.load_unit_cards_from_file(filename)
def load_all_available_cards(self) -> Dict[Nation, List[UnitCard]]:
"""
加载所有可用的卡牌
Returns:
按国家分组的卡牌字典
"""
all_cards = {}
# 遍历所有支持的国家
for nation in Nation:
try:
cards = self.load_nation_cards(nation)
if cards: # 只添加有卡牌的国家
all_cards[nation] = cards
except CardLoadError:
# 如果某个国家的卡牌加载失败,跳过但记录
continue
return all_cards
def validate_card_data(self, card_data: Dict[str, Any]) -> List[str]:
"""
验证卡牌数据的完整性
Args:
card_data: 卡牌数据字典
Returns:
错误信息列表,空列表表示验证通过
"""
errors = []
# 检查必需字段
required_fields = ['id', 'name', 'type', 'cost', 'nation', 'rarity']
for field in required_fields:
if field not in card_data:
errors.append(f"缺少必需字段: {field}")
# 检查卡牌类型
if 'type' in card_data:
valid_types = ['unit', 'order', 'countermeasure']
if card_data['type'] not in valid_types:
errors.append(f"无效的卡牌类型: {card_data['type']}")
# 检查费用
if 'cost' in card_data:
if not isinstance(card_data['cost'], int) or card_data['cost'] < 0:
errors.append(f"费用必须是非负整数: {card_data['cost']}")
# 检查稀有度
if 'rarity' in card_data:
if not isinstance(card_data['rarity'], int) or card_data['rarity'] < 1 or card_data['rarity'] > 4:
errors.append(f"稀有度必须是1-4的整数: {card_data['rarity']}")
# 检查国家
if 'nation' in card_data:
try:
self._parse_nation(card_data['nation'])
except CardLoadError as e:
errors.append(str(e))
# 单位卡特殊验证
if card_data.get('type') == 'unit':
if 'unit_definition_id' not in card_data:
errors.append("单位卡必须包含 unit_definition_id 字段")
return errors
# 全局加载器实例
_default_loader = None
def get_card_loader() -> CardLoader:
"""获取默认的卡牌加载器实例"""
global _default_loader
if _default_loader is None:
_default_loader = CardLoader()
return _default_loader
def load_nation_cards(nation: Nation) -> List[UnitCard]:
"""便捷函数:加载指定国家的卡牌"""
return get_card_loader().load_nation_cards(nation)
def load_all_cards() -> Dict[Nation, List[UnitCard]]:
"""便捷函数:加载所有可用卡牌"""
return get_card_loader().load_all_available_cards()