From 2cd8b03249d0e2a7186e85bccd4b10a37009d477 Mon Sep 17 00:00:00 2001 From: Bill Zorn Date: Mon, 9 Nov 2015 23:41:45 -0800 Subject: [PATCH] added html visual spoilers for nearest cards / names (creativity metrics) --- README.md | 7 ++--- decode.py | 60 +++++++++++++++++++++++++++++------------- lib/cardlib.py | 3 +++ lib/config.py | 1 + lib/html_extra_data.py | 2 +- lib/jdecode.py | 5 ++++ lib/namediff.py | 11 ++++++++ lib/utils.py | 1 + 8 files changed, 68 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 5cb4661..012d73a 100644 --- a/README.md +++ b/README.md @@ -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 -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 @@ -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}] - [-g] [-f] [-c] [-d] [-v] [-mse] + [-g] [-f] [-c] [-d] [-v] [-mse] [-html] infile [outfile] positional arguments: @@ -75,13 +75,14 @@ optional arguments: -v, --verbose verbose output -mse, --mse use Magic Set Editor 2 encoding; will output as .mse- 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. 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 diff --git a/decode.py b/decode.py index da95040..4f0bd19 100755 --- a/decode.py +++ b/decode.py @@ -12,9 +12,6 @@ import cardlib from cbow import CBOW 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', gatherer = False, for_forum = False, for_mse = 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) if creativity: - cbow = CBOW() 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 = ('
' + truename + + 'image' + ': ' + str(dist) + '
') + else: + namestr = '
' + truename + ': ' + str(dist) + '
' + elif for_forum: + namestr = '[card]' + truename + '[/card]' + ': ' + str(dist) + '\n' + else: + namestr = truename + ': ' + str(dist) + '\n' + return namestr def writecards(writer): if for_mse: @@ -78,27 +103,26 @@ def main(fname, oname = None, verbose = True, encoding = 'std', fstring = fstring.replace('<', '(').replace('>', ')') writer.write(('\n' + fstring[:-1]).replace('\n', '\n\t\t')) else: - writer.write((card.format(gatherer = gatherer, for_forum = for_forum, - vdump = vdump, for_html = for_html) + '\n').encode('utf-8')) + fstring = card.format(gatherer = gatherer, for_forum = for_forum, + vdump = vdump, for_html = for_html) + if creativity and for_html: + fstring = fstring[:-6] # chop off the closing to stick stuff in + writer.write((fstring + '\n').encode('utf-8')) if creativity: cstring = '~~ closest cards ~~\n' - nearest = cbow.nearest(card) + nearest = card.nearest_cards for dist, cardname in nearest: - cardname = namediff.names[cardname] - if for_forum: - cardname = '[card]' + cardname + '[/card]' - cstring += cardname + ': ' + str(dist) + '\n' + cstring += hoverimg(cardname, dist, namediff) cstring += '~~ closest names ~~\n' - nearest = namediff.nearest(card.name) + nearest = card.nearest_names for dist, cardname in nearest: - cardname = namediff.names[cardname] - if for_forum: - cardname = '[card]' + cardname + '[/card]' - cstring += cardname + ': ' + str(dist) + '\n' - if for_mse: - cstring = cstring.replace('<', '(').replace('>', ')') + cstring += hoverimg(cardname, dist, namediff) + if for_html: + cstring = '
' + cstring.replace('\n', '
\n') + '
\n' + elif for_mse: cstring = ('\n\n' + cstring[:-1]).replace('\n', '\n\t\t') + writer.write(cstring.encode('utf-8')) writer.write('\n'.encode('utf-8')) diff --git a/lib/cardlib.py b/lib/cardlib.py index 1a14ac3..2f2cf77 100644 --- a/lib/cardlib.py +++ b/lib/cardlib.py @@ -399,6 +399,9 @@ class Card: # flags self.parsed = True 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 self.__dict__[field_name] = '' self.__dict__[field_rarity] = '' diff --git a/lib/config.py b/lib/config.py index b6d45f3..aad1f39 100644 --- a/lib/config.py +++ b/lib/config.py @@ -61,3 +61,4 @@ field_label_text = '9' # additional fields we add to the json cards json_field_bside = 'bside' json_field_set_name = 'setName' +json_field_info_code = 'magicCardsInfoCode' diff --git a/lib/html_extra_data.py b/lib/html_extra_data.py index 0d7c3ac..687d1b2 100644 --- a/lib/html_extra_data.py +++ b/lib/html_extra_data.py @@ -1,2 +1,2 @@ box_width = 350 -html_prepend = "\n\n\n" +html_prepend = "\n\n\n" diff --git a/lib/jdecode.py b/lib/jdecode.py index 0c3351a..9c89f3a 100644 --- a/lib/jdecode.py +++ b/lib/jdecode.py @@ -15,9 +15,14 @@ def mtg_open_json(fname, verbose = False): for k_set in jobj: set = jobj[k_set] setname = set['name'] + if 'magicCardsInfoCode' in set: + codename = set['magicCardsInfoCode'] + else: + codename = '' for card in set['cards']: card[utils.json_field_set_name] = setname + card[utils.json_field_info_code] = codename cardnumber = None if 'number' in card: diff --git a/lib/namediff.py b/lib/namediff.py index 2707ea1..0e23783 100644 --- a/lib/namediff.py +++ b/lib/namediff.py @@ -6,6 +6,7 @@ import difflib import os import multiprocessing +import utils import jdecode import cardlib @@ -54,6 +55,7 @@ class Namediff: json_fname = os.path.join(datadir, 'AllSets.json')): self.verbose = verbose self.names = {} + self.codes = {} if self.verbose: print 'Setting up namediff...' @@ -71,11 +73,20 @@ class Namediff: card = cardlib.Card(jcards[idx]) name = card.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: print ' Duplicate name ' + name + ', ignoring.' else: self.names[name] = jname + if jcode and jnum: + self.codes[name] = jcode + '/' + jnum + '.jpg' + else: + self.codes[name] = '' namecount += 1 print ' Read ' + str(namecount) + ' unique cardnames' diff --git a/lib/utils.py b/lib/utils.py index 6b514a5..81a1b23 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -78,6 +78,7 @@ field_label_text = config.field_label_text # additional fields we add to the json cards json_field_bside = config.json_field_bside json_field_set_name = config.json_field_set_name +json_field_info_code = config.json_field_info_code # unicode / ascii conversion unicode_trans = {