""" MTG Card Interaction Determinator Determines specific card interactions (synergies, counters, evolutions) using game rules and card profiles. """ from typing import List, Tuple, Optional from dataclasses import dataclass from card_profile_extractor import CardProfile @dataclass class InteractionResult: """Result of an interaction determination.""" card_a_id: int card_b_id: int interaction_type: str # 'synergy', 'counter', 'evolution' strength: int # 1-5 confidence: float # 0.0-1.0 notes: str metadata: dict = None def __post_init__(self): if self.metadata is None: self.metadata = {} class InteractionDeterminator: """ Determines card interactions using game rules. Uses deterministic rules based on: - Shared archetypes (e.g., both are goblins) - Supporting mechanics (e.g., one has haste, the other has trample) - Mana compatibility (same colors work well together) - Target/trigger relationships (one targets, the other interacts) - Evolution chains (same card, different versions) """ def __init__(self): """Initialize the determinator.""" pass def determine_synergies( self, profile_a: CardProfile, profile_b: CardProfile ) -> List[InteractionResult]: """ Determine synergies between two cards. Synergies are positive interactions where cards work well together. Examples: - Both are goblins (archetype synergy) - One has haste, the other has trample (mechanic synergy) - Same color identity (mana synergy) - One targets creatures, the other buffs creatures (combo synergy) Args: profile_a: First card profile profile_b: Second card profile Returns: List of synergy results """ synergies = [] # 1. Archetype synergy: both share an archetype if profile_a.archetypes and profile_b.archetypes: common_archetypes = set(profile_a.archetypes) & set(profile_b.archetypes) if common_archetypes: synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=3, confidence=0.95, notes=f"Both are {', '.join(common_archetypes)}", metadata={'common_archetypes': list(common_archetypes)} )) # 2. Mana synergy: same color identity if profile_a.colors and profile_b.colors: if set(profile_a.colors) == set(profile_b.colors): synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=4, confidence=0.9, notes="Same color identity", metadata={'colors': profile_a.colors} )) # 3. Mechanic synergy: complementary mechanics if profile_a.mechanics and profile_b.mechanics: # Haste + trample = aggressive combo if ('haste' in profile_a.mechanics and 'trample' in profile_b.mechanics) or \ ('haste' in profile_b.mechanics and 'trample' in profile_a.mechanics): synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=4, confidence=0.85, notes="Haste + Trample combo", metadata={'mechanics': ['haste', 'trample']} )) # Lifelink + combat keywords = combat combo combat_keywords = ['first_strike', 'double_strike', 'deathtouch', 'trample'] if ('lifelink' in profile_a.mechanics and any(k in profile_b.mechanics for k in combat_keywords)) or \ ('lifelink' in profile_b.mechanics and any(k in profile_a.mechanics for k in combat_keywords)): synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=3, confidence=0.8, notes="Lifelink + combat keywords combo", metadata={'mechanics': ['lifelink', 'combat']} )) # 4. Combo synergy: one targets, the other interacts with targets if profile_a.targets and profile_b.triggers: # Card A targets creatures, Card B interacts with creature actions if 'creature' in profile_a.targets: creature_actions = ['enters_battlefield', 'dies', 'attacks', 'blocks'] if any(t in creature_actions for t in profile_b.triggers): synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=3, confidence=0.85, notes="Card A targets creatures, Card B interacts with creature actions", metadata={'card_a_targets': 'creature', 'card_b_interacts': 'creature_actions'} )) # 5. Support synergy: one has a mechanic, the other supports it if profile_a.mechanics and profile_b.effects: # If card B has an effect that supports card A's mechanic if 'haste' in profile_a.mechanics and 'gain_haste' in profile_b.effects: synergies.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='synergy', strength=3, confidence=0.8, notes="Card B grants haste to Card A", metadata={'mechanic': 'haste', 'effect': 'gain_haste'} )) return synergies def determine_counters( self, profile_a: CardProfile, profile_b: CardProfile ) -> List[InteractionResult]: """ Determine counter relationships between two cards. Counters are negative interactions where one card is disadvantaged by another. Examples: - Different color identities (strategic tension) - One has higher power (stat disadvantage) - One counters the other's strategy (counter role) Args: profile_a: First card profile profile_b: Second card profile Returns: List of counter results """ counters = [] # 1. Color counter: different color identities if profile_a.colors and profile_b.colors: if set(profile_a.colors) != set(profile_b.colors): counters.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='counter', strength=2, confidence=0.8, notes="Different color identities", metadata={'colors_a': profile_a.colors, 'colors_b': profile_b.colors} )) # 2. Stat counter: one has significantly higher power if profile_a.power and profile_b.power: try: power_a = int(profile_a.power) power_b = int(profile_b.power) if power_a > power_b + 1: counters.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='counter', strength=3, confidence=0.75, notes=f"Card A has higher power ({power_a} vs {power_b})", metadata={'power_a': power_a, 'power_b': power_b} )) elif power_b > power_a + 1: counters.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='counter', strength=3, confidence=0.75, notes=f"Card B has higher power ({power_b} vs {power_a})", metadata={'power_a': power_a, 'power_b': power_b} )) except (ValueError, TypeError): pass # 3. Counter role: one targets creatures, the other has combat keywords if profile_a.targets and profile_b.mechanics: if 'creature' in profile_a.targets: combat_keywords = ['deathtouch', 'trample', 'first_strike', 'double_strike'] if any(m in combat_keywords for m in profile_b.mechanics): counters.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='counter', strength=2, confidence=0.6, notes="Card A targets creatures, Card B has combat keywords", metadata={'target': 'creature', 'mechanics': profile_b.mechanics} )) return counters def determine_evolutions( self, profile_a: CardProfile, profile_b: CardProfile ) -> List[InteractionResult]: """ Determine evolution relationships between two cards. Evolutions track when a card has been reprinted, transformed, or evolved. Examples: - Same name in different sets (reprint) - Transform pairs (different faces of same card) - Double-sided cards Args: profile_a: First card profile profile_b: Second card profile Returns: List of evolution results """ evolutions = [] # 1. Same name = reprint if profile_a.name == profile_b.name: evolutions.append(InteractionResult( card_a_id=profile_a.id, card_b_id=profile_b.id, interaction_type='evolution', strength=2, confidence=0.9, notes=f"Reprint of {profile_a.name}", metadata={'card_name': profile_a.name} )) # 2. Transform pairs would require checking card_faces in the database # This is handled separately in the pipeline return evolutions def determine_all_interactions( self, profiles: List[CardProfile] ) -> dict: """ Determine all interactions for a batch of cards. Args: profiles: List of card profiles Returns: Dictionary with: - synergies: list of synergy results - counters: list of counter results - evolutions: list of evolution results """ synergies = [] counters = [] evolutions = [] # Compare all pairs for i in range(len(profiles)): for j in range(i + 1, len(profiles)): profile_a = profiles[i] profile_b = profiles[j] # Determine synergies synergies.extend(self.determine_synergies(profile_a, profile_b)) # Determine counters counters.extend(self.determine_counters(profile_a, profile_b)) # Determine evolutions evolutions.extend(self.determine_evolutions(profile_a, profile_b)) return { 'synergies': synergies, 'counters': counters, 'evolutions': evolutions, }