added html visual spoilers for nearest cards / names (creativity metrics)

This commit is contained in:
Bill Zorn 2015-11-09 23:41:45 -08:00
parent 2ebb15cb28
commit 2cd8b03249
8 changed files with 68 additions and 22 deletions

View file

@ -4,7 +4,7 @@ Utilities to assist in the process of generating Magic the Gathering cards with
http://www.mtgsalvation.com/forums/creativity/custom-card-creation/612057-generating-magic-cards-using-deep-recurrent-neural http://www.mtgsalvation.com/forums/creativity/custom-card-creation/612057-generating-magic-cards-using-deep-recurrent-neural
The purpose of this code is mostly to wrangle text between various human and machine readable formats. The original input comes from [mtgjson](http://mtgjson.com); this is filtered and reduced to one of several input formats intended for neural network training, such as the standard encoded format used in [data/output.txt](https://github.com/billzorn/mtgencode/blob/master/data/output.txt). Any json or encoded data, including output from appropriately trained neural nets, can then be interpreted as cards and decoded to a human readable format, such as a text spoiler or [Magic Set Editor 2](http://magicseteditor.sourceforge.net) set file. The purpose of this code is mostly to wrangle text between various human and machine readable formats. The original input comes from [mtgjson](http://mtgjson.com); this is filtered and reduced to one of several input formats intended for neural network training, such as the standard encoded format used in [data/output.txt](https://github.com/billzorn/mtgencode/blob/master/data/output.txt). Any json or encoded data, including output from appropriately trained neural nets, can then be interpreted as cards and decoded to a human readable format, such as a text spoiler, [Magic Set Editor 2](http://magicseteditor.sourceforge.net) set file, or a pretty, portable html file that can be viewed in any browser.
## Requirements ## Requirements
@ -57,7 +57,7 @@ custom | Blank format slot, inteded to help users add their own formats to t
``` ```
usage: decode.py [-h] [-e {std,named,noname,rfields,old,norarity,vec,custom}] usage: decode.py [-h] [-e {std,named,noname,rfields,old,norarity,vec,custom}]
[-g] [-f] [-c] [-d] [-v] [-mse] [-g] [-f] [-c] [-d] [-v] [-mse] [-html]
infile [outfile] infile [outfile]
positional arguments: positional arguments:
@ -75,13 +75,14 @@ optional arguments:
-v, --verbose verbose output -v, --verbose verbose output
-mse, --mse use Magic Set Editor 2 encoding; will output as .mse- -mse, --mse use Magic Set Editor 2 encoding; will output as .mse-
set file set file
-html, --html create a .html file with pretty forum formatting
``` ```
The default output is a text spoiler which modifies the output of the neural net as little as possible while making it human readable. Specifying the -g option will produce a prettier, Gatherer-inspired text spoiler with heavier-weight transformations applied to the text, such as capitalization. The -f option encodes mana symbols in the format used by the mtgsalvation forum; this is useful if you want to cut and paste your spoiler into a post to share it. The default output is a text spoiler which modifies the output of the neural net as little as possible while making it human readable. Specifying the -g option will produce a prettier, Gatherer-inspired text spoiler with heavier-weight transformations applied to the text, such as capitalization. The -f option encodes mana symbols in the format used by the mtgsalvation forum; this is useful if you want to cut and paste your spoiler into a post to share it.
Passing the -mse option will cause decode.py to produce both the hilarious internal MSE text format as well as an actual mse set file, which is really just a renamed zip archive. The -f and -g flags will be respected in the text that is dumped to each card's notes field. Passing the -mse option will cause decode.py to produce both the hilarious internal MSE text format as well as an actual mse set file, which is really just a renamed zip archive. The -f and -g flags will be respected in the text that is dumped to each card's notes field.
Finally, the -c and -d options will print out additional data about the quality of the cards. Running with -c is extremely slow due to the massive amount of computation involved; -d is probably a good idea to use in general unless you're trying to produce pretty output to show off. Finally, the -c and -d options will print out additional data about the quality of the cards. Running with -c is extremely slow due to the massive amount of computation involved, though at least we can do it in parallel over all of your processor cores; -d is probably a good idea to use in general unless you're trying to produce pretty output to show off. Using html mode is especially useful with -c as we can link to visual spoilers from magiccards.info.
### Examples ### Examples

View file

@ -12,9 +12,6 @@ import cardlib
from cbow import CBOW from cbow import CBOW
from namediff import Namediff from namediff import Namediff
def exclude_sets(cardset):
return cardset == 'Unglued' or cardset == 'Unhinged' or cardset == 'Celebration'
def main(fname, oname = None, verbose = True, encoding = 'std', def main(fname, oname = None, verbose = True, encoding = 'std',
gatherer = False, for_forum = False, for_mse = False, gatherer = False, for_forum = False, for_mse = False,
creativity = False, vdump = False, for_html = False): creativity = False, vdump = False, for_html = False):
@ -52,8 +49,36 @@ def main(fname, oname = None, verbose = True, encoding = 'std',
cards = jdecode.mtg_open_file(fname, verbose=verbose, fmt_ordered=fmt_ordered) cards = jdecode.mtg_open_file(fname, verbose=verbose, fmt_ordered=fmt_ordered)
if creativity: if creativity:
cbow = CBOW()
namediff = Namediff() namediff = Namediff()
cbow = CBOW()
if verbose:
print 'Computing nearest names...'
nearest_names = namediff.nearest_par(map(lambda c: c.name, cards), n=3)
if verbose:
print 'Computing nearest cards...'
nearest_cards = cbow.nearest_par(cards)
for i in range(0, len(cards)):
cards[i].nearest_names = nearest_names[i]
cards[i].nearest_cards = nearest_cards[i]
if verbose:
print '...Done.'
def hoverimg(cardname, dist, nd):
truename = nd.names[cardname]
code = nd.codes[cardname]
namestr = ''
if for_html:
if code:
namestr = ('<div class="hover_img"><a href="#">' + truename
+ '<span><img src="http://magiccards.info/scans/en/' + code
+ '" alt="image"/></span></a>' + ': ' + str(dist) + '</div>')
else:
namestr = '<div>' + truename + ': ' + str(dist) + '</div>'
elif for_forum:
namestr = '[card]' + truename + '[/card]' + ': ' + str(dist) + '\n'
else:
namestr = truename + ': ' + str(dist) + '\n'
return namestr
def writecards(writer): def writecards(writer):
if for_mse: if for_mse:
@ -78,27 +103,26 @@ def main(fname, oname = None, verbose = True, encoding = 'std',
fstring = fstring.replace('<', '(').replace('>', ')') fstring = fstring.replace('<', '(').replace('>', ')')
writer.write(('\n' + fstring[:-1]).replace('\n', '\n\t\t')) writer.write(('\n' + fstring[:-1]).replace('\n', '\n\t\t'))
else: else:
writer.write((card.format(gatherer = gatherer, for_forum = for_forum, fstring = card.format(gatherer = gatherer, for_forum = for_forum,
vdump = vdump, for_html = for_html) + '\n').encode('utf-8')) vdump = vdump, for_html = for_html)
if creativity and for_html:
fstring = fstring[:-6] # chop off the closing </div> to stick stuff in
writer.write((fstring + '\n').encode('utf-8'))
if creativity: if creativity:
cstring = '~~ closest cards ~~\n' cstring = '~~ closest cards ~~\n'
nearest = cbow.nearest(card) nearest = card.nearest_cards
for dist, cardname in nearest: for dist, cardname in nearest:
cardname = namediff.names[cardname] cstring += hoverimg(cardname, dist, namediff)
if for_forum:
cardname = '[card]' + cardname + '[/card]'
cstring += cardname + ': ' + str(dist) + '\n'
cstring += '~~ closest names ~~\n' cstring += '~~ closest names ~~\n'
nearest = namediff.nearest(card.name) nearest = card.nearest_names
for dist, cardname in nearest: for dist, cardname in nearest:
cardname = namediff.names[cardname] cstring += hoverimg(cardname, dist, namediff)
if for_forum: if for_html:
cardname = '[card]' + cardname + '[/card]' cstring = '<hr><div>' + cstring.replace('\n', '<br>\n') + '</div>\n</div>'
cstring += cardname + ': ' + str(dist) + '\n' elif for_mse:
if for_mse:
cstring = cstring.replace('<', '(').replace('>', ')')
cstring = ('\n\n' + cstring[:-1]).replace('\n', '\n\t\t') cstring = ('\n\n' + cstring[:-1]).replace('\n', '\n\t\t')
writer.write(cstring.encode('utf-8')) writer.write(cstring.encode('utf-8'))
writer.write('\n'.encode('utf-8')) writer.write('\n'.encode('utf-8'))

View file

@ -399,6 +399,9 @@ class Card:
# flags # flags
self.parsed = True self.parsed = True
self.valid = True # doesn't record that much self.valid = True # doesn't record that much
# placeholders to fill in with expensive distance metrics
self.nearest_names = []
self.nearest_cards = []
# default values for all fields # default values for all fields
self.__dict__[field_name] = '' self.__dict__[field_name] = ''
self.__dict__[field_rarity] = '' self.__dict__[field_rarity] = ''

View file

@ -61,3 +61,4 @@ field_label_text = '9'
# additional fields we add to the json cards # additional fields we add to the json cards
json_field_bside = 'bside' json_field_bside = 'bside'
json_field_set_name = 'setName' json_field_set_name = 'setName'
json_field_info_code = 'magicCardsInfoCode'

File diff suppressed because one or more lines are too long

View file

@ -15,9 +15,14 @@ def mtg_open_json(fname, verbose = False):
for k_set in jobj: for k_set in jobj:
set = jobj[k_set] set = jobj[k_set]
setname = set['name'] setname = set['name']
if 'magicCardsInfoCode' in set:
codename = set['magicCardsInfoCode']
else:
codename = ''
for card in set['cards']: for card in set['cards']:
card[utils.json_field_set_name] = setname card[utils.json_field_set_name] = setname
card[utils.json_field_info_code] = codename
cardnumber = None cardnumber = None
if 'number' in card: if 'number' in card:

View file

@ -6,6 +6,7 @@ import difflib
import os import os
import multiprocessing import multiprocessing
import utils
import jdecode import jdecode
import cardlib import cardlib
@ -54,6 +55,7 @@ class Namediff:
json_fname = os.path.join(datadir, 'AllSets.json')): json_fname = os.path.join(datadir, 'AllSets.json')):
self.verbose = verbose self.verbose = verbose
self.names = {} self.names = {}
self.codes = {}
if self.verbose: if self.verbose:
print 'Setting up namediff...' print 'Setting up namediff...'
@ -71,11 +73,20 @@ class Namediff:
card = cardlib.Card(jcards[idx]) card = cardlib.Card(jcards[idx])
name = card.name name = card.name
jname = jcards[idx]['name'] jname = jcards[idx]['name']
jcode = jcards[idx][utils.json_field_info_code]
if 'number' in jcards[idx]:
jnum = jcards[idx]['number']
else:
jnum = ''
if name in self.names: if name in self.names:
print ' Duplicate name ' + name + ', ignoring.' print ' Duplicate name ' + name + ', ignoring.'
else: else:
self.names[name] = jname self.names[name] = jname
if jcode and jnum:
self.codes[name] = jcode + '/' + jnum + '.jpg'
else:
self.codes[name] = ''
namecount += 1 namecount += 1
print ' Read ' + str(namecount) + ' unique cardnames' print ' Read ' + str(namecount) + ' unique cardnames'

View file

@ -78,6 +78,7 @@ field_label_text = config.field_label_text
# additional fields we add to the json cards # additional fields we add to the json cards
json_field_bside = config.json_field_bside json_field_bside = config.json_field_bside
json_field_set_name = config.json_field_set_name json_field_set_name = config.json_field_set_name
json_field_info_code = config.json_field_info_code
# unicode / ascii conversion # unicode / ascii conversion
unicode_trans = { unicode_trans = {