Skip to content

Instantly share code, notes, and snippets.

@flxai
Last active June 27, 2022 17:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flxai/716ed1164db4553823b76a6dfab378ae to your computer and use it in GitHub Desktop.
Save flxai/716ed1164db4553823b76a6dfab378ae to your computer and use it in GitHub Desktop.
Neural MtG

Let's play neural MtG!

  1. Get the mtg-neural-deck.py script to download some cards.
  2. Build a deck with your freshly hallucinated cards.
  3. ???
  4. Profit!

EDA

Check out the Jupyter notebook eda.ipynb to throw some statistics on your already downloaded cards.

Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
#!/usr/bin/env bash
find cards -type f -iname '*.png' | wc -l
echo
while read f; do
echo -n "${f%%.json}: "
jq ".name" -r < "cards/info/$f"
done <<< $(ls -htr1 cards/info | tail -5)
#!/usr/bin/env python3
# Automatically download newly generated neural MtG cards for freestyle deck building
# All credits to urzas.ai and respective authors
# Please use with care! Do not create unnecessary load!
import arrow
import io
import json
import pathlib
import PIL
import time
import sys
import urllib.request
import regex as re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
card_states = {
"Penning Words...": 0,
"Casting Shapes...": 1,
"Forge New Spell": 2,
}
def wait_for_card(driver, sleep_time=1, max_retries=60):
old_url = driver.current_url
# Wait for button to show "Forge Another"
btn_new_card = driver.find_elements(By.ID, "action-button")[0]
card_info = None
last_state = None
retries = 0
while True:
if retries > max_retries:
print(" TIMEOUT :(")
raise ConnectionAbortedError("Could not generate card in time.")
txt = btn_new_card.text
if txt in card_states.keys():
state = card_states[txt]
elif txt == "Forge a Spell":
btn_new_card.click()
state = None
else:
raise ValueError(f"Didn't understand button's label: {txt}")
# State transition
if state != last_state:
# Finished
if state == 2:
print(f" {retries * sleep_time}s")
break
else:
if last_state != None:
print(f" {retries * sleep_time}s")
if state == 1:
card_info = get_card_info(driver)
print(card_info)
print(txt, end="")
sys.stdout.flush()
elif state != None:
print(".", end="")
sys.stdout.flush()
retries += 1
last_state = state
time.sleep(sleep_time)
# Wait for URL to update via automatic redirect
print("Generating Image...", end="")
sys.stdout.flush()
retries = 0
while True:
if retries > max_retries:
# TODO FIXME NEW WEBSITE DOES NOT REDIRECT ANYMORE?
print(" TIMEOUT :(")
raise ConnectionAbortedError("Could not generate card in time.")
break
print(".", end="")
sys.stdout.flush()
time.sleep(sleep_time)
try:
new_url = driver.current_url
except Exception as e:
print(e)
continue
if new_url != old_url:
break
retries += 1
print(f" {retries * sleep_time}s")
return card_info
def convert_mana_cost_abbrev(element):
html = element.get_attribute('innerHTML')
html_replaced = re.sub(r'<abbr class="[^"]+ card-symbol-([A-Z0-9])" [^>]+>{T}</abbr>', r'[\1]', html)
return html_replaced.replace('&nbsp;', '')
def get_card_info(driver):
get_css = lambda css: driver.find_element(By.CSS_SELECTOR, css)
card_name = get_css('.card-frame > .frame-header > .name').text.strip()
element_mana_cost = get_css('.card-frame > .frame-header > .mana-cost')
card_mana_cost = convert_mana_cost_abbrev(element_mana_cost)
card_type = get_css('.card-frame > .frame-type-line > .type').text
element_description = get_css('.card-frame > .frame-text-box > .description')
card_description = convert_mana_cost_abbrev(element_description).split('<br>')
card_flavour_text = get_css('.card-frame > .frame-text-box > .flavour-text').text
card_info = {
'name': card_name.strip(),
'mana_cost': card_mana_cost.strip(),
'type': card_type.strip(),
'description': [s.strip() for s in card_description],
}
return card_info
def download_card(driver, img_dir):
img_card = driver.find_elements(By.CSS_SELECTOR, ".card-side > img")[1]
img_url = img_card.get_attribute('src')
img_file = img_url.split('/')[-1].split('?')[0]
img_path = f"{img_dir}/{img_file}"
pathlib.Path(img_dir).mkdir(parents=True, exist_ok=True)
try:
urllib.request.urlretrieve(img_url, img_path)
print(f"Saved card's image to {img_path}")
except TimeoutError:
print("Timeout while downloading card. :(")
return img_file
def initialize_driver():
# Initialize selenium driver
options = webdriver.chrome.options.Options()
options.headless = True
options.add_argument("--window-size=1024,840")
options.add_argument('--disable-browser-side-navigation') # Remedies hanging when getting current URL?
driver = webdriver.Chrome(options=options)
# Visit base URL
url_start = "https://www.urzas.ai/"
driver.get(url_start)
return driver
def save_card_info(card_info, info_file):
pathlib.Path('/'.join(info_file.split('/')[:-1])).mkdir(parents=True, exist_ok=True)
json_string = json.dumps(card_info)
with open(info_file, 'w') as json_file:
json_file.write(json_string)
print(f"Saved card's info to {info_file}")
def generate_and_save_card(driver):
# Click button to generate card
btn_new_card = driver.find_elements(By.ID, "action-button")[0]
btn_new_card.click()
try:
card_info = wait_for_card(driver)
except ConnectionAbortedError:
print("Skipped because of timeout...")
return False
img_file = download_card(driver, 'cards/img')
info_file = img_file.replace('.png', '.json')
save_card_info(card_info, f'cards/info/{info_file}')
return True
if __name__ == '__main__':
print("Initializing selenium driver...")
n = 5
driver = initialize_driver()
last_timestamp = arrow.utcnow()
for i in range(n):
print(f"Downloading card {i + 1}/{n}...")
success = generate_and_save_card(driver)
success_str = "Successfully" if success else "Failed to"
timestamp = arrow.utcnow()
print(f"{success_str} generate and download card within {(timestamp - last_timestamp).seconds}s")
print()
last_timestamp = timestamp
driver.quit()
#!/usr/bin/env python3
# Removes files that are either only in cards/info or in cards/img
import os
from glob import glob
from pathlib import Path
cards_img = [Path(f).stem for f in glob('cards/img/*.png')]
cards_info = [Path(f).stem for f in glob('cards/info/*.json')]
cards_img_missing = [
cards_img[i]
for i, c in enumerate(cards_img)
if c not in cards_info
]
cards_info_missing = [
cards_info[i]
for i, c in enumerate(cards_info)
if c not in cards_img
]
if len(cards_img_missing) > 0:
print("Deleting the following image files, because there is no corresponding info:")
for c in cards_img_missing:
print(c)
os.remove(f"cards/img/{c}.png")
if len(cards_info_missing) > 0:
print("Deleting the following info files, because there is no corresponding image:")
for c in cards_info_missing:
print(c)
os.remove(f"cards/info/{c}.json")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment