Spaces:
Sleeping
Sleeping
| import json | |
| from collections import defaultdict | |
| import torch | |
| import numpy as np | |
| import os | |
| import pickle | |
| import yaml | |
| basics = ['Mountain', 'Forest', 'Swamp', 'Island', 'Plains'] | |
| def normalize_card_name(name): | |
| """Canonical card name: front face only, no A- prefix, underscores→spaces, basic variants collapsed.""" | |
| name = name.split(' // ')[0] # DFC: keep front face only | |
| name = basic_card_name(name) # Mountain_1 → Mountain (must precede underscore replace) | |
| name = name.replace('_', ' ') # CSV column artifact | |
| if name.startswith('A-'): | |
| name = name[2:] # Alchemy rebalanced cards | |
| name = name.replace("Sol'kanar", "Sol'Kanar") # capitalization mismatch in source data | |
| return name | |
| def load_config(path): | |
| with open(path, 'r') as f: | |
| return yaml.safe_load(f) | |
| def unwrap(net): | |
| return net.module if hasattr(net, "module") else net | |
| def get_embedding_dict(path, add_nontransformed=False): | |
| with open(path, 'rb') as f: | |
| embedding_dict = pickle.load(f) | |
| if add_nontransformed: | |
| # Normalize all keys: deduplicate DFCs, strip A- prefixes, collapse basic variants. | |
| # Earlier entries win so the raw full-name embedding isn't silently dropped. | |
| normalized = {} | |
| for k, v in embedding_dict.items(): | |
| nk = normalize_card_name(k) | |
| if nk not in normalized: | |
| normalized[nk] = v | |
| return normalized | |
| return embedding_dict | |
| def dump_embedding_dict(embedding_dict, name, embedding_folder = ''): | |
| with open(name, 'wb') as f: | |
| pickle.dump(embedding_dict,f) | |
| def _download_scryfall_cards(path): | |
| import requests | |
| from datetime import date | |
| os.makedirs(path, exist_ok=True) | |
| headers = {"User-Agent": "MTG-IL/1.0", "Accept": "application/json"} | |
| print("Fetching Scryfall bulk data index...") | |
| resp = requests.get("https://api.scryfall.com/bulk-data", headers=headers) | |
| resp.raise_for_status() | |
| bulk_index = resp.json() | |
| if "data" not in bulk_index: | |
| raise RuntimeError(f"Unexpected Scryfall response: {bulk_index}") | |
| entry = next((e for e in bulk_index["data"] if e["type"] == "default_cards"), None) | |
| if entry is None: | |
| raise RuntimeError(f"No default_cards entry. Types: {[e['type'] for e in bulk_index['data']]}") | |
| download_uri = entry["download_uri"] | |
| out_path = os.path.join(path, f"cards_{date.today()}.json") | |
| print(f"Downloading Scryfall card data to {out_path}...") | |
| with requests.get(download_uri, stream=True, headers=headers) as r: | |
| r.raise_for_status() | |
| with open(out_path, "wb") as f: | |
| for chunk in r.iter_content(chunk_size=1 << 17): | |
| f.write(chunk) | |
| print("Scryfall download complete.") | |
| return out_path | |
| def get_card_json(path = './data/', transform = False): | |
| card_files = [f for f in os.listdir(path) if 'cards_' in f] | |
| card_files.sort(key=lambda x: os.path.getmtime(os.path.join(path, x))) | |
| if len(card_files) == 0: | |
| card_path = _download_scryfall_cards(path) | |
| else: | |
| card_path = os.path.join(path, card_files[-1]) | |
| print(f'Using cards file: {card_path}') | |
| cards = json.load(open(card_path, encoding='utf8')) | |
| if transform: | |
| card_dict_split = {} | |
| for card in cards: | |
| card_dict_split[normalize_card_name(card['name'])] = card | |
| return card_dict_split | |
| return cards | |
| def load_winrates(path): | |
| import csv | |
| with open(path, 'r') as f: | |
| reader = csv.reader(f) | |
| next(reader) | |
| winrates = {row[0]: float(row[15].strip('%')) if row[15] else None for row in reader} | |
| min_winrate = min([winrate for winrate in winrates.values() if winrate is not None]) | |
| for card in winrates: | |
| if winrates[card] is None: | |
| winrates[card] = min_winrate | |
| return winrates | |
| def clean_card(card): | |
| card = basic_card_name(card) | |
| if 'A-' in card: | |
| card = card.replace('A-','') | |
| elif "Sol'kanar" in card: | |
| card = card.replace("Sol'kanar","Sol'Kanar") | |
| elif '_' in card: | |
| card = card.replace('_',' ') | |
| return card | |
| def basic_card_name(card_name): | |
| ints = ['1','2','3','4','5'] | |
| for b in basics: | |
| if b in card_name: | |
| for i in ints: | |
| if card_name == f'{b}_{i}': | |
| return b | |
| return card_name | |
| def get_embedding_of_card(card_name, embedding_dict): | |
| try: | |
| key = normalize_card_name(card_name) | |
| if key in embedding_dict: | |
| return embedding_dict[key], False | |
| raise Exception(f'Could not find {card_name!r} (normalized: {key!r})') | |
| except Exception as e: | |
| print(e) | |
| raise e | |
| def get_card_embeddings(card_names, embedding_dict, embedding_size=1330): | |
| embeddings = [] | |
| for card in card_names: | |
| if card == '': | |
| embeddings.append([]) | |
| elif card == []: | |
| if type(embedding_size) == tuple: | |
| channels, height, width = embedding_size | |
| new_embedding = torch.zeros(1,channels, height, width) | |
| else: | |
| new_embedding = torch.zeros(1,embedding_size) | |
| embeddings.append(new_embedding) | |
| elif isinstance(card, list): | |
| if len(card) == 0: | |
| embeddings.append(None) | |
| continue | |
| deck_embedding = [] | |
| for c in card: | |
| embedding, got_new = get_embedding_of_card(c, embedding_dict) | |
| deck_embedding.append(embedding) | |
| try: | |
| num_cards = len(deck_embedding) | |
| deck_embedding = torch.stack(deck_embedding) | |
| if type(embedding_size) == tuple: | |
| channels, height, width = embedding_size | |
| deck_embedding = deck_embedding.view(num_cards,channels, height, width) | |
| else: | |
| deck_embedding = deck_embedding.view(num_cards,-1) | |
| except Exception as e: | |
| raise e | |
| embeddings.append(deck_embedding) | |
| else: | |
| embedding, got_new = get_embedding_of_card(card, embedding_dict) | |
| embeddings.append(embedding) | |
| return embeddings | |
| def get_original_name(transformed_name): | |
| cards_original = transformed_name.replace('_', ' ') | |
| for suffix in [' 1', ' 2', ' 3', ' 4', ' 5']: | |
| cards_original = cards_original.replace(suffix, '') | |
| if 'Nicol Bolas' in cards_original: | |
| cards_original = 'Nicol Bolas, the Ravager // Nicol Bolas, the Arisen' | |
| return cards_original | |