""" 卡牌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()