added html visual spoilers for nearest cards / names (creativity metrics)
This commit is contained in:
parent
2ebb15cb28
commit
2cd8b03249
8 changed files with 68 additions and 22 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
60
decode.py
60
decode.py
|
@ -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'))
|
||||||
|
|
|
@ -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] = ''
|
||||||
|
|
|
@ -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
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in a new issue