""" MTG Card Interaction Rule Engine Extracts card interactions using structured rules instead of NLP. Designed for rolling updates when new MTGJSON data is loaded. """ import re from typing import Dict, List, Tuple, Optional, Any from dataclasses import dataclass from enum import Enum class InteractionType(Enum): """Types of card interactions.""" MECHANIC = "mechanic" ARCHETYPE = "archetype" SYNERGY = "synergy" COUNTER = "counter" EVOLUTION = "evolution" MANA = "mana" SET_THEME = "set_theme" class SynergyType(Enum): """Types of synergies between cards.""" ARCHETYPE_SUPPORT = "archetype_support" MECHANIC_SUPPORT = "mechanic_support" MANA_BASE = "mana_base" COMBO_PARTNER = "combo_partner" COUNTER_PARTNER = "counter_partner" EVOLUTION_CHAIN = "evolution_chain" class CounterType(Enum): """Types of counter relationships.""" DIRECT_COUNTER = "direct_counter" MANA_DISADVANTAGE = "mana_disadvantage" OUTCLASS = "outclass" COUNTER_ROLE = "counter_role" class EvolutionType(Enum): """Types of evolution relationships.""" TRANSFORM = "transform" EVOLVE = "evolve" DOUBLE_SIDED = "double_sided" MODAL_DFC = "modal_dfc" REPRINTED = "reprinted" @dataclass class CardProfile: """Structured profile of a card for interaction extraction.""" name: str mana_cost: Optional[str] type_line: Optional[str] oracle_text: Optional[str] subtypes: Optional[str] supertypes: Optional[str] colors: Optional[str] color_identity: Optional[str] power: Optional[str] toughness: Optional[str] loyalty: Optional[str] set_code: Optional[str] set_id: int card_id: int # Extracted fields mechanics: List[str] = None archetypes: List[str] = None targets: List[str] = None triggers: List[str] = None effects: List[str] = None themes: List[str] = None def __post_init__(self): if self.mechanics is None: self.mechanics = [] if self.archetypes is None: self.archetypes = [] if self.targets is None: self.targets = [] if self.triggers is None: self.triggers = [] if self.effects is None: self.effects = [] if self.themes is None: self.themes = [] class MTGRuleEngine: """ Extracts card interactions using structured rules. This is NOT NLP. It uses: - Regex patterns for known game language - Curated dictionaries for mechanics/archetypes - Game rule logic for determining interactions """ def __init__(self): # Define mechanics and their extraction patterns self.mechanics_patterns = { 'flying': r'Flying', 'first_strike': r'First strike', 'double_strike': r'Double strike', 'deathtouch': r'Death touch', 'lifelink': r'Lifelink', 'haste': r'Haste', 'trample': r'Trample', 'menace': r'Menace', 'vigilance': r'Vegilance', 'reach': r'Reach', 'indestructible': r'Indestructible', 'hexproof': r'Hexproof', 'shroud': r'Shroud', 'defender': r'Defender', 'landfall': r'Landfall', 'delve': r'Delve', 'soulshift': r'Soulshift', 'suspend': r'Suspend', 'convoke': r'Convoke', 'rampage': r'Rampage', 'toxic': r'Toxic', 'crew': r'Crew', 'equip': r'Equip', 'annihilator': r'Annihilator', 'spectacle': r'Spectacle', 'prowess': r'Prowess', 'aftermath': r'Aftermath', 'adapt': r'Adapt', 'amplify': r'Amplify', 'awaken': r'Awaken', 'banding': r'Band with', 'bestow': r'Bestow', 'burst': r'Burst', 'channel': r'Channel', 'clash': r'Clash', 'curse': r'Curse', 'day_night': r'Day|Night', 'decay': r'Decay', 'defiant': r'Defiant', 'demolish': r'Demolish', 'detain': r'Detain', 'detect': r'Detect', 'devour': r'Devour', 'disguise': r'Disguise', 'disturb': r'Disturb', 'dome': r'Dome', 'dredge': r'Dredge', 'emerge': r'Emerge', 'encore': r'Encore', 'endure': r'Endure', 'evoke': r'Evoke', 'evolve': r'Evolve', 'exalted': r'Exalted', 'exile': r'Exile', 'exploit': r'Exploit', 'extort': r'Extort', 'fairy': r'Fairy', 'fanatic': r'Fanatic', 'fathom': r'Fathom', 'fear': r'Fear', 'feline': r'Feline', 'flash': r'Flash', 'flight': r'Flight', 'foretell': r'Foretell', 'frenzy': r'Frenzy', 'fumble': r'Fumble', 'galvanize': r'Galvanize', 'gateway': r'Gateway', 'genesis': r'Genesis', 'graft': r'Graft', 'grave': r'Grave', 'grit': r'Grit', 'guardian': r'Guardian', 'harvest': r'Harvest', 'healer': r'Healer', 'heroic': r'Heroic', 'hideaway': r'Hideaway', 'hinterland': r'Hinterland', 'hoard': r'Hoard', 'hour': r'Hour', 'illusion': r'Illusion', 'immortal': r'Immortal', 'impulse': r'Impulse', 'inspiration': r'Inspiration', 'instill': r'Instill', 'iron': r'Iron', 'junk': r'Junk', 'kicker': r'Kicker', 'knight': r'Knight', 'land': r'Land', 'leech': r'Leech', 'lich': r'Lich', 'lifespan': r'Lifespan', 'lightning': r'Lightning', 'living': r'Living', 'lurk': r'Lurk', 'madness': r'Madness', 'manifest': r'Manifest', 'map': r'Map', 'meld': r'Meld', 'miracle': r'Miracle', 'mitosis': r'Mitosis', 'modular': r'Modular', 'moon': r'Moon', 'mother': r'Mother', 'morph': r'Morph', 'mutate': r'Mutate', 'ninja': r'Ninja', 'night': r'Night', 'nightmare': r'Nightmare', 'pact': r'Pact', 'paradox': r'Paradox', 'persist': r'Persist', 'pillage': r'Pillage', 'pivot': r'Pivot', 'planar': r'Planar', 'polar': r'Polar', 'pour': r'Pour', 'prey': r'Prey', 'priest': r'Priest', 'primer': r'Primer', 'probe': r'Probe', 'prosperity': r'Prosperity', 'psychic': r'Psychic', 'puppet': r'Puppet', 'quest': r'Quest', 'quote': r'Quote', 'rage': r'Rage', 'raid': r'Raid', 'raise': r'Raise', 'rally': r'Rally', 'rapid': r'Rapid', 'rat': r'Rat', 'rebound': r'Rebound', 'reckless': r'Reckless', 'recoup': r'Recoup', 'reflect': r'Reflect', 'refresh': r'Refresh', 'replicate': r'Replicate', 'reverberate': r'Reverberate', 'reviviant': r'Reviviant', 'rift': r'Rift', 'rip': r'Rip', 'ritual': r'Ritual', 'rite': r'Rite', 'rogue': r'Rogue', 'savant': r'Savant', 'scavenge': r'Scavenge', 'seek': r'Seek', 'shadow': r'Shadow', 'shards': r'Shards', 'skulk': r'Skulk', 'smelt': r'Smelt', 'snap': r'Snap', 'snow': r'Snow', 'spectacle': r'Spectacle', 'splice': r'Splice', 'spore': r'Spore', 'sprawl': r'Sprawl', 'stabilize': r'Stabilize', 'stasis': r'Stasis', 'storm': r'Storm', 'story': r'Story', 'substitute': r'Substitute', 'sunder': r'Sunder', 'surge': r'Surge', 'survive': r'Survive', 'swarm': r'Swarm', 'symbiosis': r'Symbiosis', 'synchronized': r'Synchronized', 'synth': r'Synth', 'table': r'Table', 'taint': r'Taint', 'tank': r'Tank', 'thorn': r'Thorn', 'thwart': r'Thwart', 'time': r'Time', 'tinker': r'Tinker', 'toxin': r'Toxin', 'trail': r'Trail', 'transfigure': r'Transfigure', 'transform': r'Transform', 'transport': r'Transport', 'trouble': r'Trouble', 'tunnel': r'Tunnel', 'unearth': r'Unearth', 'unleash': r'Unleash', 'unmask': r'Unmask', 'unstoppable': r'Unstoppable', 'urborg': r'Urborg', 'urgent': r'Urgent', 'utility': r'Utility', 'vengeful': r'Vengeful', 'vanish': r'Vanish', 'venom': r'Venom', 'victory': r'Victory', 'villainous': r'Villainous', 'vitalize': r'Vitalize', 'void': r'Void', 'voyage': r'Veoyage', 'ward': r'Ward', 'watch': r'Watch', 'weave': r'Weave', 'wed': r'Wed', 'whammy': r'Whammy', 'wild': r'Wild', 'will': r'Will', 'wisp': r'Wisp', 'witch': r'Witch', 'woe': r'Woe', 'wounded': r'Wounded', 'wrap': r'Wrap', 'wrought': r'Wrought', 'wurm': r'Wurm', 'wythe': r'Wythe', } # Define archetype patterns self.archetype_patterns = { 'goblin': r'Goblin', 'elf': r'Elf', 'vampire': r'Veampire', 'angel': r'Angel', 'dragon': r'Dragon', 'human': r'Human', 'zombie': r'Zombie', 'soldier': r'Soldier', 'knight': r'Knight', 'wizard': r'Wizard', 'spirit': r'Spirit', 'demon': r'Demon', 'snake': r'Snake', 'cat': r'Cat', 'wolf': r'Wolf', 'bear': r'Bear', 'bird': r'Bird', 'insect': r'Insect', 'horror': r'Horror', 'goat': r'Goat', 'ox': r'Ox', 'elephant': r'Elephant', 'whale': r'Whale', 'shark': r'Shark', 'fish': r'Fish', 'serpent': r'Serpent', 'lizard': r'Lizard', 'scorpion': r'Scorpion', 'spider': r'Spider', 'rat': r'Rat', 'drake': r'Drake', 'wyvern': r'Wyvern', 'phoenix': r'Phoenix', 'lynx': r'Lynx', 'jaguar': r'Jaguar', 'hydra': r'Hydra', 'leviathan': r'Leviathan', 'kraken': r'Kraken', 'cyclops': r'Cyclops', 'golem': r'Golem', 'homunculus': r'Homunculus', 'clay': r'Clay', 'construct': r'Construct', 'myr': r'Myr', 'aether': r'Aether', 'pumpkin': r'Pumpkin', 'pirate': r'Pirate', 'pegasus': r'Pegasus', 'unicorn': r'Unicorn', 'centaur': r'Centaur', 'merfolk': r'Merfolk', 'mermaid': r'Mermaid', 'naga': r'Naga', 'satyr': r'Satyr', 'dryad': r'Dryad', 'treant': r'Treant', 'elemental': r'Elemental', 'fiend': r'Fiend', 'imp': r'Imp', 'faerie': r'Faerie', 'minion': r'Minion', 'abomination': r'Abomination', 'beast': r'Beast', 'demigod': r'Demigod', 'god': r'God', 'avatar': r'Avatar', 'guardian': r'Guardian', 'warrior': r'Warrior', 'rogue': r'Rogue', 'artificer': r'Artificer', 'bard': r'Bard', 'monk': r'Monk', 'ninja': r'Ninja', 'samurai': r'Samurai', 'assassin': r'Assassin', 'thief': r'Thief', 'acrobat': r'Acrobat', 'explorer': r'Explorer', 'farmer': r'Farmer', 'myth': r'Myth', 'illusion': r'Illusion', 'mirror': r'Mirror', 'phantom': r'Phantom', 'shapeshifter': r'Shapeshifter', 'shaman': r'Shaman', 'skeleton': r'Skeleton', 'slime': r'Slime', 'squirrel': r'Squirrel', 'troll': r'Troll', 'tyrannosaur': r'Tyrannosaur', 'wraith': r'Wraith', 'wurm': r'Wurm', } # Target types for counter interactions self.target_types = { 'creature': r'creature', 'artifact': r'artifact', 'enchantment': r'enchantment', 'instant': r'instant', 'sorcery': r'sorcery', 'planeswalker': r'planeswalker', 'land': r'land', 'player': r'player', } # Trigger patterns self.trigger_patterns = { 'enters_battlefield': r'when [~|this] enters the battlefield', 'leaves_battlefield': r'when [~|this] leaves the battlefield', 'attacks': r'whenever [~|this] attacks', 'blocks': r'whenever [~|this] blocks', 'dies': r'when [~|this] dies', 'damage': r'deals [0-9]+ damage', 'draws_card': r'draw a card|draw two cards', 'gains_life': r'gain [0-9]+ life', 'creates_token': r'create a token', 'taps': r'tap: add', 'untaps': r'untap: add', 'destroys': r'destroy target', 'exiles': r'exile target', 'counters_spell': r'counter target spell', } # Effect patterns self.effect_patterns = { 'gain_flying': r'gain flying', 'gain_first_strike': r'gain first strike', 'gain_double_strike': r'gain double strike', 'gain_deathtouch': r'gain deathtouch', 'gain_lifelink': r'gain lifelink', 'gain_haste': r'gain haste', 'gain_trample': r'gain trample', 'gain_vigilance': r'gain vigilance', 'gain_indestructible': r'gain indestructible', 'gain_hexproof': r'gain hexproof', 'until_end_of_turn': r'until end of turn', 'until_next_turn': r'until your next turn', } def extract_mechanics(self, card: CardProfile) -> List[str]: """Extract mechanics from card type line and oracle text.""" mechanics = [] # Check type line for mechanics if card.type_line: for mechanic, pattern in self.mechanics_patterns.items(): if re.search(pattern, card.type_line, re.IGNORECASE): mechanics.append(mechanic) # Check oracle text for mechanics if card.oracle_text: for mechanic, pattern in self.mechanics_patterns.items(): if re.search(pattern, card.oracle_text, re.IGNORECASE): if mechanic not in mechanics: mechanics.append(mechanic) return mechanics def extract_archetypes(self, card: CardProfile) -> List[str]: """Extract archetypes from card subtypes.""" archetypes = [] if card.subtypes: for archetype, pattern in self.archetype_patterns.items(): if re.search(pattern, card.subtypes, re.IGNORECASE): archetypes.append(archetype) return archetypes def extract_targets(self, card: CardProfile) -> List[str]: """Extract target types from oracle text.""" targets = [] if card.oracle_text: for target, pattern in self.target_types.items(): if re.search(pattern, card.oracle_text, re.IGNORECASE): targets.append(target) return targets def extract_triggers(self, card: CardProfile) -> List[str]: """Extract trigger conditions from oracle text.""" triggers = [] if card.oracle_text: for trigger, pattern in self.trigger_patterns.items(): if re.search(pattern, card.oracle_text, re.IGNORECASE): triggers.append(trigger) return triggers def extract_effects(self, card: CardProfile) -> List[str]: """Extract game effects from oracle text.""" effects = [] if card.oracle_text: for effect, pattern in self.effect_patterns.items(): if re.search(pattern, card.oracle_text, re.IGNORECASE): effects.append(effect) return effects def extract_themes(self, card: CardProfile) -> List[str]: """Extract set themes based on card characteristics.""" themes = [] # Storm theme if 'storm' in card.mechanics or 'storm' in card.oracle_text.lower(): themes.append('storm') # Token theme if any(e in card.effects for e in ['creates_token']): themes.append('tokens') # Mill theme if any(t in card.triggers for t in ['draws_card']): themes.append('draw') # Life gain theme if any(e in card.effects for e in ['gains_life']): themes.append('life_gain') # Board wipe theme if any(t in card.triggers for t in ['dies']): themes.append('board_wipe') # Reanimate theme if any(t in card.triggers for t in ['leaves_battlefield']): themes.append('reanimate') # Countermagic theme if any(e in card.effects for e in ['counters_spell']): themes.append('countermagic') # Card advantage theme if any(t in card.triggers for t in ['draws_card']): themes.append('card_advantage') # Mana acceleration theme if any(t in card.triggers for t in ['taps', 'untaps']): themes.append('mana_acceleration') # Combat tricks theme if any(e in card.effects for e in ['gain_flying', 'gain_first_strike', 'gain_double_strike', 'gain_deathtouch', 'gain_lifelink', 'gain_vigilance']): themes.append('combat_tricks') # ETB effects theme if any(t in card.triggers for t in ['enters_battlefield']): themes.append('etb_effects') # LTB effects theme if any(t in card.triggers for t in ['leaves_battlefield']): themes.append('ltb_effects') return themes def profile_card(self, card_data: Dict[str, Any]) -> CardProfile: """Convert raw MTGJSON card data to CardProfile.""" # Parse subtypes subtypes = None if card_data.get('subtypes'): subtypes = ', '.join(card_data['subtypes']) # Parse supertypes supertypes = None if card_data.get('supertypes'): supertypes = ', '.join(card_data['supertypes']) # Parse colors colors = None if card_data.get('colors'): colors = ', '.join(card_data['colors']) # Parse color identity color_identity = None if card_data.get('colorIdentity'): color_identity = ', '.join(card_data['colorIdentity']) # Extract interactions profile = CardProfile( name=card_data.get('name', ''), mana_cost=card_data.get('manaCost'), type_line=card_data.get('typeLine'), oracle_text=card_data.get('oracleText'), subtypes=subtypes, supertypes=supertypes, colors=colors, color_identity=color_identity, power=card_data.get('power'), toughness=card_data.get('toughness'), loyalty=card_data.get('loyalty'), set_code=card_data.get('set', {}).get('code') if card_data.get('set') else None, set_id=card_data.get('setId', 0), card_id=card_data.get('id', 0), ) # Extract mechanics, archetypes, etc. profile.mechanics = self.extract_mechanics(profile) profile.archetypes = self.extract_archetypes(profile) profile.targets = self.extract_targets(profile) profile.triggers = self.extract_triggers(profile) profile.effects = self.extract_effects(profile) profile.themes = self.extract_themes(profile) return profile def find_synergies(self, card_a: CardProfile, card_b: CardProfile) -> List[Tuple[str, int, str]]: """ Find synergies between two cards. Returns list of (synergy_type, strength, notes) tuples. """ synergies = [] # Same archetype synergy if card_a.archetypes and card_b.archetypes: common_archetypes = set(card_a.archetypes) & set(card_b.archetypes) if common_archetypes: synergies.append(( 'archetype_support', 3, f"Both are {', '.join(common_archetypes)}" )) # Mechanic support if card_a.mechanics and card_b.mechanics: # If card_b has a mechanic that supports card_a's archetype for mech in card_a.mechanics: if mech in card_b.mechanics: synergies.append(( 'mechanic_support', 2, f"Both have {mech}" )) # Mana base synergy if card_a.colors and card_b.colors: # Check for color compatibility colors_a = set(card_a.colors.split(',')) colors_b = set(card_b.colors.split(',')) if colors_a == colors_b: synergies.append(( 'mana_base', 4, "Same color identity" )) # Combo partner if card_a.targets and card_b.triggers: # If card_a targets creatures and card_b triggers on creatures if 'creature' in card_a.targets and any(t in card_b.triggers for t in ['enters_battlefield', 'dies']): synergies.append(( 'combo_partner', 3, "Card A targets creatures, Card B interacts with creature entry/death" )) # Counter partner if card_a.targets and card_b.targets: # If they target different types, they complement each other targets_a = set(card_a.targets) targets_b = set(card_b.targets) if targets_a != targets_b and targets_a & targets_b: synergies.append(( 'counter_partner', 2, "Different target types provide coverage" )) # Evolution chain if card_a.name == card_b.name: synergies.append(( 'evolution_chain', 2, "Same card name (reprint or different version)" )) return synergies def find_counters(self, card_a: CardProfile, card_b: CardProfile) -> List[Tuple[str, int, str]]: """ Find counter relationships between two cards. Returns list of (counter_type, strength, notes) tuples. """ counters = [] # Different color identities if card_a.color_identity and card_b.color_identity: colors_a = set(card_a.color_identity.split(',')) colors_b = set(card_b.color_identity.split(',')) if colors_a != colors_b: counters.append(( 'mana_disadvantage', 2, "Different color identities create strategic tension" )) # Outclass if card_a.power and card_b.power: try: power_a = int(card_a.power) power_b = int(card_b.power) if power_a > power_b + 1: counters.append(( 'outclass', 3, f"Card A has higher power ({power_a} vs {power_b})" )) elif power_b > power_a + 1: counters.append(( 'outclass', 3, f"Card B has higher power ({power_b} vs {power_a})" )) except (ValueError, TypeError): pass # Counter role if card_a.targets and 'creature' in card_a.targets: if card_b.mechanics and any(m in card_b.mechanics for m in ['deathtouch', 'trample']): counters.append(( 'counter_role', 2, "Card A targets creatures, Card B has combat keywords" )) return counters def find_evolution(self, card: CardProfile, all_cards: Dict[int, CardProfile]) -> List[Tuple[str, int, str]]: """ Find evolution relationships for a card. Returns list of (evolution_type, strength, notes) tuples. """ evolutions = [] # Find reprints for other_id, other_card in all_cards.items(): if other_id != card.card_id and card.name == other_card.name: evolutions.append(( 'reprinted', 2, f"Reprint in {other_card.set_code} (set_id: {other_card.set_id})" )) # Find transform pairs (same name, different face) # This would require checking card_faces in the database return evolutions def build_interaction_graph(self, cards: List[Dict[str, Any]]) -> Dict[str, Any]: """ Build interaction graph for a batch of cards. Returns dictionary with: - mechanics: card_id -> mechanics list - archetypes: card_id -> archetypes list - synergies: (card_a, card_b) -> list of synergies - counters: (card_a, card_b) -> list of counters - evolutions: card_id -> list of evolutions """ # Profile all cards profiles = {} for card_data in cards: if card_data.get('id'): profile = self.profile_card(card_data) profiles[profile.card_id] = profile # Extract interactions graph = { 'mechanics': {}, 'archetypes': {}, 'synergies': [], 'counters': [], 'evolutions': [], } # Extract mechanics and archetypes for card_id, profile in profiles.items(): graph['mechanics'][card_id] = profile.mechanics graph['archetypes'][card_id] = profile.archetypes # Find synergies between all card pairs card_ids = list(profiles.keys()) for i in range(len(card_ids)): for j in range(i + 1, len(card_ids)): card_a = profiles[card_ids[i]] card_b = profiles[card_ids[j]] synergies = self.find_synergies(card_a, card_b) if synergies: graph['synergies'].append({ 'card_a': card_a.card_id, 'card_b': card_b.card_id, 'synergies': synergies, }) # Find counters between all card pairs for i in range(len(card_ids)): for j in range(i + 1, len(card_ids)): card_a = profiles[card_ids[i]] card_b = profiles[card_ids[j]] counters = self.find_counters(card_a, card_b) if counters: graph['counters'].append({ 'card_a': card_a.card_id, 'card_b': card_b.card_id, 'counters': counters, }) # Find evolutions for each card for card_id, profile in profiles.items(): evolutions = self.find_evolution(profile, profiles) if evolutions: graph['evolutions'].append({ 'card_id': card_id, 'evolutions': evolutions, }) return graph def main(): """Test the rule engine with sample data.""" engine = MTGRuleEngine() # Sample card data sample_cards = [ { 'id': 1, 'name': 'Lightning Bolt', 'manaCost': '{R}', 'typeLine': 'Instant', 'oracleText': 'Lightning Bolt deals 3 damage to any target.', 'subtypes': [], 'supertypes': [], 'colors': ['R'], 'colorIdentity': ['R'], 'set': {'code': '2X2'}, 'setId': 100, }, { 'id': 2, 'name': 'Lightning Greaves', 'manaCost': '{1}{R}', 'typeLine': 'Artifact — Equipment', 'oracleText': 'Enchanted creature has hexproof and haste.\nEquip {1}', 'subtypes': ['Equipment'], 'supertypes': [], 'colors': ['R'], 'colorIdentity': ['R'], 'power': None, 'toughness': None, 'set': {'code': '10E'}, 'setId': 200, }, { 'id': 3, 'name': 'Elvish Archers', 'manaCost': '{G}', 'typeLine': 'Creature — Elf Ranger', 'oracleText': 'Elvish Archers can\'t be blocked by creatures with power 2 or less.\n{T}: Target creature gets -1/-1 until end of turn.', 'subtypes': ['Elf', 'Ranger'], 'supertypes': [], 'colors': ['G'], 'colorIdentity': ['G'], 'power': '1', 'toughness': '1', 'set': {'code': '5DN'}, 'setId': 300, }, { 'id': 4, 'name': 'Swords to Plowshares', 'manaCost': '{W}', 'typeLine': 'Enchantment', 'oracleText': 'Exile target creature. Its controller gains 1 life.', 'subtypes': [], 'supertypes': [], 'colors': ['W'], 'colorIdentity': ['W'], 'set': {'code': '2X2'}, 'setId': 100, }, ] # Build interaction graph graph = engine.build_interaction_graph(sample_cards) # Print results print("=" * 60) print("MTG Card Interaction Graph") print("=" * 60) print("\nšŸ“Š Mechanics:") for card_id, mechanics in graph['mechanics'].items(): print(f" Card {card_id}: {mechanics}") print("\nšŸ“Š Archetypes:") for card_id, archetypes in graph['archetypes'].items(): print(f" Card {card_id}: {archetypes}") print("\nšŸ”— Synergies:") for synergy in graph['synergies']: print(f" Cards {synergy['card_a']} ↔ {synergy['card_b']}:") for syn_type, strength, notes in synergy['synergies']: print(f" - {syn_type} (strength: {strength}): {notes}") print("\nāš”ļø Counters:") for counter in graph['counters']: print(f" Cards {counter['card_a']} ↔ {counter['card_b']}:") for counter_type, strength, notes in counter['counters']: print(f" - {counter_type} (strength: {strength}): {notes}") print("\nšŸ”„ Evolutions:") for evolution in graph['evolutions']: print(f" Card {evolution['card_id']}:") for evol_type, strength, notes in evolution['evolutions']: print(f" - {evol_type} (strength: {strength}): {notes}") print("\n" + "=" * 60) print("āœ… Interaction graph built successfully!") print("=" * 60) if __name__ == "__main__": main()