Spaces:
Sleeping
Sleeping
File size: 6,773 Bytes
14d9438 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | 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
|