Add project files: decks, scripts, and collection data
This commit is contained in:
174
scripts/analyze_decks.py
Normal file
174
scripts/analyze_decks.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze decks against a hydrated collection to find upgrade options.
|
||||
Usage:
|
||||
python analyze_decks.py --collection collection_hydrated/deck.json --decks deck1.json deck2.json ...
|
||||
python analyze_decks.py --collection collection_hydrated/deck.json --deck-dir decks/
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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 load_deck(path: str) -> dict:
|
||||
"""Load deck JSON (from parse_deck.py output or deck.json format)."""
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
if 'cards' in data:
|
||||
return data['cards']
|
||||
return data
|
||||
|
||||
|
||||
def get_color_identity(card: dict) -> set:
|
||||
"""Get color identity as a set."""
|
||||
return set(card.get('color_identity', []))
|
||||
|
||||
|
||||
def matches_colors(card_identity: set, deck_colors: set) -> bool:
|
||||
"""Check if card identity fits within deck colors."""
|
||||
return card_identity <= deck_colors or not card_identity
|
||||
|
||||
|
||||
def find_upgrades(collection: dict[str, dict], deck_cards: dict, all_deck_cards: set,
|
||||
deck_colors: set, archetype: str | None = None) -> dict:
|
||||
"""Find potential upgrade cards for a deck."""
|
||||
available = set(collection.keys()) - all_deck_cards
|
||||
upgrades = {
|
||||
'creatures': [],
|
||||
'instants': [],
|
||||
'sorceries': [],
|
||||
'artifacts': [],
|
||||
'enchantments': [],
|
||||
'lands': [],
|
||||
'other': []
|
||||
}
|
||||
|
||||
for name in available:
|
||||
card = collection.get(name, {})
|
||||
if not card:
|
||||
continue
|
||||
|
||||
ci = get_color_identity(card)
|
||||
if not matches_colors(ci, deck_colors):
|
||||
continue
|
||||
|
||||
type_line = card.get('type_line', '').lower() if card.get('type_line') else ''
|
||||
entry = {
|
||||
'name': name,
|
||||
'mana_cost': card.get('mana_cost', ''),
|
||||
'cmc': card.get('cmc', 0),
|
||||
'type_line': card.get('type_line', ''),
|
||||
'oracle_text': card.get('oracle_text', ''),
|
||||
'count': card.get('count', 1)
|
||||
}
|
||||
|
||||
if 'creature' in type_line:
|
||||
upgrades['creatures'].append(entry)
|
||||
elif 'instant' in type_line:
|
||||
upgrades['instants'].append(entry)
|
||||
elif 'sorcery' in type_line:
|
||||
upgrades['sorceries'].append(entry)
|
||||
elif 'artifact' in type_line:
|
||||
upgrades['artifacts'].append(entry)
|
||||
elif 'enchantment' in type_line:
|
||||
upgrades['enchantments'].append(entry)
|
||||
elif 'land' in type_line:
|
||||
upgrades['lands'].append(entry)
|
||||
else:
|
||||
upgrades['other'].append(entry)
|
||||
|
||||
return upgrades
|
||||
|
||||
|
||||
def find_synergies(collection: dict, available: set, deck_colors: set,
|
||||
keywords: list) -> list:
|
||||
"""Find cards with specific keyword synergies."""
|
||||
synergies = []
|
||||
for name in available:
|
||||
card = collection.get(name, {})
|
||||
if not card:
|
||||
continue
|
||||
ci = get_color_identity(card)
|
||||
if not matches_colors(ci, deck_colors):
|
||||
continue
|
||||
oracle = card.get('oracle_text', '').lower() if card.get('oracle_text') else ''
|
||||
if any(kw.lower() in oracle for kw in keywords):
|
||||
synergies.append({
|
||||
'name': name,
|
||||
'type_line': card.get('type_line', ''),
|
||||
'mana_cost': card.get('mana_cost', ''),
|
||||
'oracle_snippet': oracle[:100]
|
||||
})
|
||||
return synergies
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Analyze decks for upgrade options')
|
||||
parser.add_argument('--collection', '-c', required=True, help='Hydrated collection JSON')
|
||||
parser.add_argument('--decks', '-d', nargs='+', help='Deck JSON files')
|
||||
parser.add_argument('--deck-dir', help='Directory containing deck JSON files')
|
||||
parser.add_argument('--output', '-o', help='Output JSON file')
|
||||
parser.add_argument('--keywords', '-k', nargs='+', help='Keywords to search for synergies')
|
||||
args = parser.parse_args()
|
||||
|
||||
collection = load_collection(args.collection)
|
||||
|
||||
decks = {}
|
||||
if args.decks:
|
||||
for path in args.decks:
|
||||
name = Path(path).stem
|
||||
decks[name] = load_deck(path)
|
||||
elif args.deck_dir:
|
||||
for path in Path(args.deck_dir).glob('*.json'):
|
||||
decks[path.stem] = load_deck(str(path))
|
||||
else:
|
||||
print("Error: Provide --decks or --deck-dir", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
all_deck_cards = set()
|
||||
for deck_cards in decks.values():
|
||||
all_deck_cards.update(deck_cards.keys())
|
||||
|
||||
results = {}
|
||||
for deck_name, deck_cards in decks.items():
|
||||
deck_colors = set()
|
||||
for name in deck_cards:
|
||||
card = collection.get(name, {})
|
||||
deck_colors |= get_color_identity(card)
|
||||
|
||||
upgrades = find_upgrades(collection, deck_cards, all_deck_cards, deck_colors)
|
||||
|
||||
available = set(collection.keys()) - all_deck_cards
|
||||
synergies = {}
|
||||
if args.keywords:
|
||||
synergies = find_synergies(collection, available, deck_colors, args.keywords)
|
||||
|
||||
results[deck_name] = {
|
||||
'total_cards': sum(deck_cards.values()),
|
||||
'unique_cards': len(deck_cards),
|
||||
'color_identity': list(deck_colors),
|
||||
'available_upgrades': {k: len(v) for k, v in upgrades.items()},
|
||||
'upgrades': upgrades,
|
||||
'synergies': synergies
|
||||
}
|
||||
|
||||
output = json.dumps(results, indent=2)
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
else:
|
||||
print(output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
72
scripts/deck_report.py
Normal file
72
scripts/deck_report.py
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate a deck upgrade report comparing collection to existing decks.
|
||||
Usage:
|
||||
python deck_report.py --collection collection_hydrated/deck.json --decks-dir decks/ --output report.md
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_collection(path: str) -> dict:
|
||||
with open(path, 'r') as f:
|
||||
cards = json.load(f)
|
||||
return {c['name']: c for c in cards}
|
||||
|
||||
|
||||
def load_deck(path: str) -> dict:
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
if 'cards' in data:
|
||||
return data
|
||||
return {'cards': data, 'name': Path(path).stem}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate deck upgrade report')
|
||||
parser.add_argument('--collection', '-c', required=True)
|
||||
parser.add_argument('--decks-dir', '-d', required=True)
|
||||
parser.add_argument('--output', '-o', required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
collection = load_collection(args.collection)
|
||||
decks = []
|
||||
for path in Path(args.decks_dir).glob('*.json'):
|
||||
decks.append(load_deck(str(path)))
|
||||
|
||||
# Find all cards in decks
|
||||
all_deck_cards = set()
|
||||
for deck in decks:
|
||||
all_deck_cards.update(deck['cards'].keys())
|
||||
|
||||
available = set(collection.keys()) - all_deck_cards
|
||||
|
||||
report = []
|
||||
report.append("# Deck Upgrade Report\n")
|
||||
report.append(f"Collection size: {len(collection)} cards")
|
||||
report.append(f"Cards in decks: {len(all_deck_cards)}")
|
||||
report.append(f"Available for upgrades: {len(available)}\n")
|
||||
|
||||
for deck in decks:
|
||||
deck_name = deck.get('name', 'Unknown')
|
||||
deck_cards = deck['cards']
|
||||
deck_colors = set()
|
||||
for name in deck_cards:
|
||||
card = collection.get(name, {})
|
||||
ci = card.get('color_identity', [])
|
||||
deck_colors.update(ci)
|
||||
|
||||
report.append(f"\n---\n## {deck_name}\n")
|
||||
report.append(f"**Colors:** {''.join(sorted(deck_colors))}")
|
||||
report.append(f"**Total cards:** {sum(deck_cards.values())}")
|
||||
report.append(f"**Unique cards:** {len(deck_cards)}\n")
|
||||
|
||||
with open(args.output, 'w') as f:
|
||||
f.write('\n'.join(report))
|
||||
print(f"Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
119
scripts/find_synergies.py
Normal file
119
scripts/find_synergies.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/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()
|
||||
62
scripts/parse_deck.py
Normal file
62
scripts/parse_deck.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Parse deck list text files or pasted deck lists.
|
||||
Usage:
|
||||
python parse_deck.py <deck_file.txt>
|
||||
python parse_deck.py --stdin < decklist.txt
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def parse_deck_text(text: str) -> dict[str, int]:
|
||||
"""Parse deck list text into {card_name: count} dict."""
|
||||
cards = {}
|
||||
for line in text.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
match = re.match(r'^(\d+)x?\s+(.+)$', line, re.IGNORECASE)
|
||||
if match:
|
||||
count = int(match.group(1))
|
||||
name = match.group(2).strip()
|
||||
cards[name] = count
|
||||
return cards
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Parse deck list into JSON')
|
||||
parser.add_argument('input', nargs='?', help='Deck list file')
|
||||
parser.add_argument('--stdin', action='store_true', help='Read from stdin')
|
||||
parser.add_argument('-o', '--output', help='Output JSON file')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.stdin:
|
||||
text = sys.stdin.read()
|
||||
elif args.input:
|
||||
with open(args.input, 'r') as f:
|
||||
text = f.read()
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
cards = parse_deck_text(text)
|
||||
result = {
|
||||
'total_cards': sum(cards.values()),
|
||||
'unique_cards': len(cards),
|
||||
'cards': cards
|
||||
}
|
||||
|
||||
output = json.dumps(result, indent=2)
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
else:
|
||||
print(output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user