#!/usr/bin/env python3 """ Find cards with specific synergies from a hydrated collection. Usage: python find_synergies.py --collection collection_hydrated/deck.json --colors U R --keywords "landfall" "enter the battlefield" python find_synergies.py --collection collection_hydrated/deck.json --colors U R --creature-type Bird python find_synergies.py --collection collection_hydrated/deck.json --colors U R --cmc-min 4 --type instant """ import argparse import json import re import sys from typing import Optional def load_collection(path: str) -> dict: """Load hydrated collection JSON.""" with open(path, 'r') as f: cards = json.load(f) return {c['name']: c for c in cards} def matches_colors(card_identity: set, allowed_colors: set) -> bool: """Check if card identity fits within allowed colors.""" if not card_identity: return True return card_identity <= allowed_colors def extract_creature_types(type_line: str) -> list: """Extract creature types from type line.""" if not type_line: return [] parts = type_line.split('—') if len(parts) < 2: return [] types_part = parts[1].strip() return [t.strip().lower() for t in types_part.split()] def main(): parser = argparse.ArgumentParser(description='Find synergistic cards in collection') parser.add_argument('--collection', '-c', required=True, help='Hydrated collection JSON') parser.add_argument('--colors', nargs='+', default=[], help='Allowed colors (e.g., U R for Izzet)') parser.add_argument('--exclude', nargs='+', default=[], help='Cards to exclude (already in deck)') parser.add_argument('--keywords', '-k', nargs='+', help='Keywords to search in oracle text') parser.add_argument('--creature-type', '-t', help='Creature type to search for') parser.add_argument('--type', help='Card type to filter (creature, instant, sorcery, etc.)') parser.add_argument('--cmc-min', type=int, help='Minimum CMC') parser.add_argument('--cmc-max', type=int, help='Maximum CMC') parser.add_argument('--output', '-o', help='Output JSON file') parser.add_argument('--format', choices=['json', 'text'], default='text', help='Output format') args = parser.parse_args() collection = load_collection(args.collection) allowed_colors = set(c.upper() for c in args.colors) exclude = set(args.exclude) matches = [] for name, card in collection.items(): if name in exclude: continue ci = set(card.get('color_identity', [])) if not matches_colors(ci, allowed_colors): continue type_line = card.get('type_line', '').lower() if card.get('type_line') else '' if args.type and args.type.lower() not in type_line: continue cmc = card.get('cmc', 0) or 0 if args.cmc_min is not None and cmc < args.cmc_min: continue if args.cmc_max is not None and cmc > args.cmc_max: continue if args.creature_type: creature_types = extract_creature_types(type_line) if args.creature_type.lower() not in creature_types: continue oracle = card.get('oracle_text', '').lower() if card.get('oracle_text') else '' if args.keywords: if not any(kw.lower() in oracle for kw in args.keywords): continue matches.append({ 'name': name, 'mana_cost': card.get('mana_cost', ''), 'cmc': cmc, 'type_line': card.get('type_line', ''), 'colors': card.get('colors', []), 'color_identity': list(ci), 'oracle_text': card.get('oracle_text', ''), 'count': card.get('count', 1) }) matches.sort(key=lambda x: (x['cmc'], x['name'])) if args.format == 'json': output = json.dumps(matches, indent=2) if args.output: with open(args.output, 'w') as f: f.write(output) else: print(output) else: for card in matches: print(f"{card['mana_cost']} {card['name']} ({int(card['cmc'])}cmc) x{card['count']}") if card['oracle_text']: print(f" {card['oracle_text'][:100]}...") if __name__ == '__main__': main()