rabukasim / engine /game /data_loader.py
trioskosmos's picture
Upload folder using huggingface_hub
463f868 verified
import json
import os
from typing import Any, Dict, Tuple
from pydantic import TypeAdapter
from engine.models.ability import AbilityCostType, Cost
from engine.models.card import EnergyCard, LiveCard, MemberCard
class CardDataLoader:
def __init__(self, json_path: str):
self.json_path = json_path
def _repair_stale_optional_energy_deck_costs(self, cards: Dict[int, Any]) -> None:
"""Normalize legacy compiled abilities that lost PLACE_ENERGY_WAIT costs.
Some older compiled card files encoded
`COST: PLACE_ENERGY_WAIT(1) (Optional)` as an empty-cost ability with only
the follow-up effect preserved. Rehydrate those into a real deck-to-energy
cost so the runtime prompt and resolution path stay consistent.
"""
for card in cards.values():
for ability in getattr(card, "abilities", []):
text = f"{getattr(ability, 'pseudocode', '')}\n{getattr(ability, 'raw_text', '')}".upper()
if "COST: PLACE_ENERGY_WAIT" not in text:
continue
if getattr(ability, "costs", None):
continue
is_optional = "(OPTIONAL)" in text
ability.costs = [
Cost(
AbilityCostType.PLACE_ENERGY_FROM_DECK,
1,
params={"wait": True},
is_optional=is_optional,
)
]
semantic = getattr(ability, "semantic_form", None)
if isinstance(semantic, dict):
semantic["costs"] = [
{
"type": "PLACE_ENERGY_FROM_DECK",
"value": 1,
"target": "PLAYER",
"params": {"wait": True},
"optional": is_optional,
}
]
def load(self) -> Tuple[Dict[int, MemberCard], Dict[int, LiveCard], Dict[int, Any]]:
# Auto-detect compiled file
target_path = self.json_path
if target_path.endswith("cards.json"):
# Check for compiled file in the same directory, or in data/
compiled_path = target_path.replace("cards.json", "cards_compiled.json")
if os.path.exists(compiled_path):
target_path = compiled_path
else:
root_path = os.path.join(os.getcwd(), "data", "cards_compiled.json")
if os.path.exists(root_path):
target_path = root_path
# Fallback to relative path search if absolute fails (common in tests)
if not os.path.exists(target_path):
# Try assuming path is relative to project root
# But we don't know project root easily.
pass
# print(f"Loading card data from {target_path}...")
with open(target_path, "r", encoding="utf-8") as f:
data = json.load(f)
members = {}
lives = {}
energy = {}
if "member_db" in data:
# Compiled format (v1.0)
m_adapter = TypeAdapter(MemberCard)
l_adapter = TypeAdapter(LiveCard)
e_adapter = TypeAdapter(EnergyCard)
for k, v in data["member_db"].items():
members[int(k)] = m_adapter.validate_python(v)
for k, v in data["live_db"].items():
# print(f"Loading live {k}")
lives[int(k)] = l_adapter.validate_python(v)
# print(f"DEBUG: Internal live_db keys: {len(data['live_db'])}, loaded: {len(lives)}")
for k, v in data["energy_db"].items():
energy[int(k)] = e_adapter.validate_python(v)
self._repair_stale_optional_energy_deck_costs(lives)
self._repair_stale_optional_energy_deck_costs(members)
else:
# Legacy raw format
# Since we removed runtime parsing from the engine to separate concerns,
# we cannot load raw cards anymore.
raise RuntimeError(
"Legacy cards.json format detected. Runtime parsing is disabled. "
"Please run 'uv run compiler/main.py' to generate 'data/cards_compiled.json'."
)
return members, lives, energy