Spaces:
Sleeping
Sleeping
| 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"] |