from typing import Any, Dict, List from .ability_descriptions import ( EFFECT_DESCRIPTIONS, EFFECT_DESCRIPTIONS_JP, TRIGGER_DESCRIPTIONS, TRIGGER_DESCRIPTIONS_JP, ) from .ability_filter import format_filter_attr from .ability_ir import SemanticAbility, SemanticCondition, SemanticCost, SemanticEffect from .generated_enums import AbilityCostType, ConditionType, EffectType, TargetType, TriggerType def build_semantic_form(ability: Any) -> Dict[str, Any]: semantic_effects: List[SemanticEffect] = [] for eff in ability.effects: try: params_copy = eff.params.copy() if hasattr(eff.params, "copy") else dict(eff.params or {}) except (AttributeError, TypeError, ValueError): params_copy = {} semantic_effects.append( SemanticEffect( effect_type=eff.effect_type.name if hasattr(eff.effect_type, "name") else str(eff.effect_type), value=eff.value, target=eff.target.name if hasattr(eff.target, "name") else str(eff.target), params=params_copy, is_optional=eff.is_optional, ) ) semantic_conditions: List[SemanticCondition] = [] for cond in ability.conditions: try: params_upper = {k.upper(): v for k, v in cond.params.items() if isinstance(k, str)} except (AttributeError, TypeError): params_upper = {} comp_str = str(cond.params.get("comparison") or params_upper.get("COMPARISON") or "GE").upper() if cond.params else "GE" filter_summary = "" if hasattr(cond, "attr") and cond.attr: try: filter_summary = format_filter_attr(cond.attr) except (AttributeError, TypeError, ValueError): filter_summary = "" semantic_conditions.append( SemanticCondition( condition_type=cond.type.name if hasattr(cond.type, "name") else str(cond.type), value=cond.value if hasattr(cond, "value") else 0, comparison=comp_str, filter_summary=filter_summary, area=str(cond.params.get("area", "")).upper() if cond.params else "", is_negated=cond.is_negated if hasattr(cond, "is_negated") else False, ) ) semantic_costs: List[SemanticCost] = [] for cost in ability.costs: semantic_costs.append( SemanticCost( cost_type=cost.type.name if hasattr(cost.type, "name") else str(cost.type), value=cost.value, is_optional=cost.is_optional, ) ) instructions_summary = "" if ability.instructions: parts = [] from .ability import Condition, Cost, Effect for instr in ability.instructions: if isinstance(instr, Effect): parts.append(f"Effect({instr.effect_type.name})") elif isinstance(instr, Condition): parts.append(f"Condition({instr.type.name})") elif isinstance(instr, Cost): parts.append(f"Cost({instr.type.name})") instructions_summary = " -> ".join(parts) semantic_form = SemanticAbility( trigger=ability.trigger.name if hasattr(ability.trigger, "name") else str(ability.trigger), effects=semantic_effects, conditions=semantic_conditions, costs=semantic_costs, is_once_per_turn=ability.is_once_per_turn, description=ability.pseudocode or ability.raw_text, instructions_summary=instructions_summary, ) ability.semantic_form = semantic_form.to_dict() return ability.semantic_form def reconstruct_text(ability: Any, lang: str = "en") -> str: parts: List[str] = [] is_jp = lang == "jp" effect_descriptions = EFFECT_DESCRIPTIONS_JP if is_jp else EFFECT_DESCRIPTIONS trigger_descriptions = TRIGGER_DESCRIPTIONS_JP if is_jp else TRIGGER_DESCRIPTIONS trigger_name = getattr(ability.trigger, "name", str(ability.trigger)) parts.append(trigger_descriptions.get(ability.trigger, f"[{trigger_name}]")) for cost in ability.costs: if is_jp: if cost.type == AbilityCostType.ENERGY: parts.append(f"(コスト: エネルギー{cost.value})") elif cost.type == AbilityCostType.TAP_SELF: parts.append("(コスト: 自分をレスト)") elif cost.type == AbilityCostType.DISCARD_HAND: parts.append(f"(コスト: 手札を{cost.value}枚捨てる)") elif cost.type == AbilityCostType.SACRIFICE_SELF: parts.append("(コスト: 自分を退場)") else: parts.append(f"(コスト: {cost.type.name} {cost.value})") else: if cost.type == AbilityCostType.ENERGY: parts.append(f"(Cost: Pay {cost.value} Energy)") elif cost.type == AbilityCostType.TAP_SELF: parts.append("(Cost: Rest Self)") elif cost.type == AbilityCostType.DISCARD_HAND: parts.append(f"(Cost: Discard {cost.value} from hand)") elif cost.type == AbilityCostType.SACRIFICE_SELF: parts.append("(Cost: Sacrifice Self)") else: parts.append(f"(Cost: {cost.type.name} {cost.value})") for cond in ability.conditions: neg = "NOT " if getattr(cond, "is_negated", False) and not is_jp else "" cond_desc = f"{neg}{cond.type.name}" if cond.params.get("group") is not None: cond_desc += f"({cond.params['group']})" if cond.params.get("unit") is not None: cond_desc += f" ({cond.params['unit']})" if cond.params.get("zone"): cond_desc += f" (in {cond.params['zone']})" if not is_jp else f" ({cond.params['zone']})" if cond.type == ConditionType.HAS_KEYWORD: cond_desc += f" ({cond.params.get('keyword', '?')})" parts.append(cond_desc) for eff in ability.effects: template = effect_descriptions.get(eff.effect_type, getattr(eff.effect_type, "name", str(eff.effect_type))) context = eff.params.copy() context["value"] = eff.value try: desc = template.format(**context) except KeyError: desc = template if eff.params.get("per_energy"): desc += " per Energy" if not is_jp else " / エネルギーごと" if eff.params.get("per_member"): desc += " per Member" if not is_jp else " / メンバーごと" if eff.params.get("per_live"): desc += " per Live" if not is_jp else " / ライブごと" if eff.target == TargetType.MEMBER_SELECT: desc += " (Choose member)" if not is_jp else " (対象を選ぶ)" if eff.target in (TargetType.OPPONENT, getattr(TargetType, "OPPONENT_HAND", TargetType.OPPONENT)): if ("opponent" not in desc.lower() and not is_jp) or (is_jp and "相手" not in desc): desc += " (Opponent)" if not is_jp else " (相手)" parts.append(desc) if eff.modal_options: for index, option in enumerate(eff.modal_options, start=1): option_descs = [] for sub_eff in option: option_template = effect_descriptions.get(sub_eff.effect_type, sub_eff.effect_type.name) option_context = sub_eff.params.copy() option_context["value"] = sub_eff.value try: option_descs.append(option_template.format(**option_context)) except KeyError: option_descs.append(option_template) label = f"Option {index}" if not is_jp else f"選択肢{index}" parts.append(f"[{label}: {' + '.join(option_descs)}]") return " ".join(parts) __all__ = ["build_semantic_form", "reconstruct_text"]