import json import re from collections import defaultdict import os import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) from compiler.aliases import ( CONDITION_SEMANTIC_SPECIAL_CASES, CONDITION_TRUE_ALIASES, EFFECT_GRAMMAR_CONVENIENCES, EFFECT_SEMANTIC_SPECIAL_CASES, EFFECT_TRUE_ALIASES, IGNORED_CONDITIONS, KEYWORD_CONDITIONS, TRIGGER_ALIASES, ) def analyze(): # Load metadata with open("data/metadata.json", "r", encoding="utf-8") as f: metadata = json.load(f) parser_trigger_aliases = set(TRIGGER_ALIASES) parser_effect_aliases = set(EFFECT_TRUE_ALIASES) | set(EFFECT_GRAMMAR_CONVENIENCES) | set(EFFECT_SEMANTIC_SPECIAL_CASES) parser_condition_aliases = ( set(CONDITION_TRUE_ALIASES) | set(CONDITION_SEMANTIC_SPECIAL_CASES) | set(KEYWORD_CONDITIONS) | set(IGNORED_CONDITIONS) ) parser_ignored_conds = set(IGNORED_CONDITIONS) # Load consolidated abilities with open("data/consolidated_abilities.json", "r", encoding="utf-8") as f: consolidated = json.load(f) # Load cards compiled to get counts with open("data/cards_compiled.json", "r", encoding="utf-8") as f: compiled = json.load(f) all_members = compiled.get("member_db", {}) # Map pseudocode to card count pseudo_to_count = defaultdict(int) pseudo_to_cards = defaultdict(list) for mid, mdata in all_members.items(): for ability in mdata.get("abilities", []): p = ability.get("pseudocode", "") if p: pseudo_to_count[p] += 1 pseudo_to_cards[p].append(mdata.get("card_no", mid)) # Extract keywords from pseudocode effects = defaultdict(int) conditions = defaultdict(int) triggers = defaultdict(int) costs = defaultdict(int) targets = defaultdict(int) keyword_usage_cards = { "EFFECT": defaultdict(set), "CONDITION": defaultdict(set), "TRIGGER": defaultdict(set), "COST": defaultdict(set), "TARGET": defaultdict(set), } for pseudo, count in pseudo_to_count.items(): cards = pseudo_to_cards[pseudo] lines = pseudo.split("\n") for line in lines: line = line.strip() # Extract Effects if "EFFECT:" in line: match = re.search(r"EFFECT:\s*([\w|]+)", line) if match: for e in match.group(1).split("|"): effects[e] += count for c in cards: keyword_usage_cards["EFFECT"][e].add(c) # Extract Conditions if "CONDITION:" in line: match = re.search(r"CONDITION:\s*([\w|]+)", line) if match: for c_kw in match.group(1).split("|"): conditions[c_kw] += count for c in cards: keyword_usage_cards["CONDITION"][c_kw].add(c) # Extract Triggers if "TRIGGER:" in line: match = re.search(r"TRIGGER:\s*([\w|]+)", line) if match: for t in match.group(1).split("|"): triggers[t] += count for c in cards: keyword_usage_cards["TRIGGER"][t].add(c) # Extract Costs if "COST:" in line: match = re.search(r"COST:\s*([\w|]+)", line) if match: for cost_kw in match.group(1).split("|"): costs[cost_kw] += count for c in cards: keyword_usage_cards["COST"][cost_kw].add(c) # Extract Targets (-> KEYWORD) targets_match = re.findall(r"->\s*([\w|]+)", line) for tm in targets_match: for target_kw in tm.split("|"): targets[target_kw] += count for c in cards: keyword_usage_cards["TARGET"][target_kw].add(c) # Compare with metadata meta_opcodes = set(metadata.get("opcodes", {}).keys()) meta_conditions = set(metadata.get("conditions", {}).keys()) meta_triggers = set(metadata.get("triggers", {}).keys()) meta_costs = set(metadata.get("costs", {}).keys()) meta_targets = set(metadata.get("targets", {}).keys()) # Extras that are often handled without opcodes or are meta ignored_keywords = { "COUNT_VAL", "PLAYER", "SELF", "OPPONENT", "VARIABLE", "PER_CARD", "AND", "OR", "NOT", "TRUE", "FALSE", "IF", "THEN", "ELSE", "VALUE_GT", "VALUE_GE", "VALUE_LT", "VALUE_LE", "VALUE_EQ", "VALUE_NE", } def print_gaps(category, found, meta, parser_aliases, ignore=set()): print(f"\n=== {category} GAPS (Not in Metadata AND Not Aliased) ===") missing = [] for kw, count in sorted(found.items(), key=lambda x: -x[1]): if kw not in meta and kw not in parser_aliases and kw not in ignore and not kw.isdigit(): missing.append((kw, count)) if not missing: print("No gaps found.") else: for kw, count in missing: cards = sorted(list(keyword_usage_cards[category][kw]))[:5] print(f"{kw:<25} Usage: {count:>3} Cards: {', '.join(cards)}") print_gaps("TRIGGER", triggers, meta_triggers, parser_trigger_aliases) print_gaps("EFFECT", effects, meta_opcodes, parser_effect_aliases) print_gaps( "CONDITION", conditions, meta_conditions, parser_condition_aliases, ignored_keywords.union(parser_ignored_conds) ) print_gaps("COST", costs, meta_costs, set()) # Costs don't have aliases usually print_gaps("TARGET", targets, meta_targets, set(), ignored_keywords) if __name__ == "__main__": analyze()