mtgencode/lib/cardlib.py

1080 lines
42 KiB
Python

# card representation
import re
import random
import json
import utils
import transforms
from manalib import Manacost, Manatext
# Some text prettification stuff that people may not have installed
try:
from titlecase import titlecase
except ImportError:
def titlecase(s):
return s.title()
try:
import textwrap
import nltk.data
sent_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
# This could me made smarter - MSE will capitalize for us after :,
# but we still need to capitalize the first english component of an activation
# cost that starts with symbols, such as {2U}, *R*emove a +1/+1 counter from @: etc.
def cap(s):
return s[:1].capitalize() + s[1:]
# This crazy thing is actually invoked as an unpass, so newlines are still
# encoded.
def sentencecase(s):
s = s.replace(utils.x_marker, utils.reserved_marker)
lines = s.split(utils.newline)
clines = []
for line in lines:
if line:
sentences = sent_tokenizer.tokenize(line)
clines += [' '.join([cap(sent) for sent in sentences])]
return utils.newline.join(clines).replace(utils.reserved_marker, utils.x_marker)
except ImportError:
# non-nltk implementation provided by PAK90
def uppercaseNewLineAndFullstop(string):
# ok, let's capitalize every letter after a full stop and newline.
# first let's find all indices of '.' and '\n'
indices = [0] # initialise with 0, since we always want to capitalise the first letter.
newlineIndices = [0] # also need to keep track of pure newlines (for planeswalkers).
for i in range (len(string)):
if string[i] == '\\':
indices.append(i + 1) # we want the index of the letter after the \n, so add one.
newlineIndices.append(i + 1)
if string[i] == '.' or string[i] == "=": # also handle the choice bullets.
indices.append(i + 2) # we want the index of the letter after the ., so we need to count the space as well.
indexSet = set(indices) # convert it to a set for the next part; the capitalisation.
return "".join(c.upper() if i in indexSet else c for i, c in enumerate(string))
def sentencecase(s):
return uppercaseNewLineAndFullstop(s)
# These are used later to determine what the fields of the Card object are called.
# Define them here because they have nothing to do with the actual format.
field_name = 'name'
field_rarity = 'rarity'
field_cost = 'cost'
field_supertypes = 'supertypes'
field_types = 'types'
field_subtypes = 'subtypes'
field_loyalty = 'loyalty'
field_pt = 'pt'
field_text = 'text'
field_other = 'other' # it's kind of a pseudo-field
# Import the labels, because these do appear in the encoded text.
field_label_name = utils.field_label_name
field_label_rarity = utils.field_label_rarity
field_label_cost = utils.field_label_cost
field_label_supertypes = utils.field_label_supertypes
field_label_types = utils.field_label_types
field_label_subtypes = utils.field_label_subtypes
field_label_loyalty = utils.field_label_loyalty
field_label_pt = utils.field_label_pt
field_label_text = utils.field_label_text
fieldnames = [
field_name,
field_rarity,
field_cost,
field_supertypes,
field_types,
field_subtypes,
field_loyalty,
field_pt,
field_text,
]
# legacy
fmt_ordered_old = [
field_name,
field_supertypes,
field_types,
field_loyalty,
field_subtypes,
field_rarity,
field_pt,
field_cost,
field_text,
]
fmt_ordered_norarity = [
field_name,
field_supertypes,
field_types,
field_loyalty,
field_subtypes,
field_pt,
field_cost,
field_text,
]
# standard
fmt_ordered_default = [
field_types,
field_supertypes,
field_subtypes,
field_loyalty,
field_pt,
field_text,
field_cost,
field_rarity,
field_name,
]
# minor variations
fmt_ordered_noname = [
field_types,
field_supertypes,
field_subtypes,
field_loyalty,
field_pt,
field_text,
field_cost,
field_rarity,
]
fmt_ordered_named = [
field_name,
field_types,
field_supertypes,
field_subtypes,
field_loyalty,
field_pt,
field_text,
field_cost,
field_rarity,
]
fmt_labeled_default = {
field_name : field_label_name,
field_rarity : field_label_rarity,
field_cost : field_label_cost,
field_supertypes : field_label_supertypes,
field_types : field_label_types,
field_subtypes : field_label_subtypes,
field_loyalty : field_label_loyalty,
field_pt : field_label_pt,
field_text : field_label_text,
}
# sanity test if a card's fields look plausible
def fields_check_valid(fields):
# all cards must have a name and a type
if not field_name in fields:
return False
if not field_types in fields:
return False
# creatures and vehicles have p/t, other things don't
iscreature = False
for idx, value in fields[field_types]:
if 'creature' in value:
iscreature = True
elif field_subtypes in fields:
for idx, value in fields[field_subtypes]:
if 'vehicle' in value:
iscreature = True
if iscreature:
return field_pt in fields
else:
return not field_pt in fields
# These functions take a bunch of source data in some format and turn
# it into nicely labeled fields that we know how to initialize a card from.
# Both return a dict that maps field names to lists of possible values,
# paired with the index that we read that particular field value from.
# So, {fieldname : [(idx, value), (idx, value)...].
# Usually we want these lists to be length 1, but you never know.
# Of course to make things nice and simple, that dict is the third element
# of a triple that reports parsing success and valid success as its
# first two elements.
# This whole things assumes the json format of mtgjson.com.
# Here's a brief list of relevant fields:
# name - string
# names - list (used for split, flip, and double-faced)
# manaCost - string
# cmc - number
# colors - list
# type - string (the whole big long damn thing)
# supertypes - list
# types - list
# subtypes - list
# text - string
# power - string
# toughness - string
# loyalty - number
# And some less useful ones, in case they're wanted for something:
# layout - string
# rarity - string
# flavor - string
# artist - string
# number - string
# multiverseid - number
# variations - list
# imageName - string
# watermark - string
# border - string
# timeshifted - boolean
# hand - number
# life - number
# reserved - boolean
# releaseDate - string
# starter - boolean
def fields_from_json(src_json, linetrans = True):
parsed = True
valid = True
fields = {}
# we hardcode in what the things are called in the mtgjson format
if 'name' in src_json:
name_val = src_json['name'].lower()
name_orig = name_val
name_val = transforms.name_pass_1_sanitize(name_val)
name_val = utils.to_ascii(name_val)
fields[field_name] = [(-1, name_val)]
else:
name_orig = ''
parsed = False
# return the actual Manacost object
if 'manaCost' in src_json:
cost = Manacost(src_json['manaCost'], fmt = 'json')
valid = valid and cost.valid
parsed = parsed and cost.parsed
fields[field_cost] = [(-1, cost)]
if 'supertypes' in src_json:
fields[field_supertypes] = [(-1, map(lambda s: utils.to_ascii(s.lower()),
src_json['supertypes']))]
if 'types' in src_json:
fields[field_types] = [(-1, map(lambda s: utils.to_ascii(s.lower()),
src_json['types']))]
else:
parsed = False
if 'subtypes' in src_json:
fields[field_subtypes] = [(-1, map(lambda s: utils.to_ascii(s.lower())
# urza's lands...
.replace('"', "'").replace('-', utils.dash_marker),
src_json['subtypes']))]
if 'rarity' in src_json:
if src_json['rarity'] in utils.json_rarity_map:
fields[field_rarity] = [(-1, utils.json_rarity_map[src_json['rarity']])]
else:
fields[field_rarity] = [(-1, src_json['rarity'])]
parsed = False
else:
parsed = False
if 'loyalty' in src_json:
fields[field_loyalty] = [(-1, utils.to_unary(str(src_json['loyalty'])))]
p_t = ''
parsed_pt = True
if 'power' in src_json:
p_t = utils.to_ascii(utils.to_unary(src_json['power'])) + '/' # hardcoded
parsed_pt = False
if 'toughness' in src_json:
p_t = p_t + utils.to_ascii(utils.to_unary(src_json['toughness']))
parsed_pt = True
elif 'toughness' in src_json:
p_t = '/' + utils.to_ascii(utils.to_unary(src_json['toughness'])) # hardcoded
parsed_pt = False
if p_t:
fields[field_pt] = [(-1, p_t)]
parsed = parsed and parsed_pt
# similarly, return the actual Manatext object
if 'text' in src_json:
text_val = src_json['text'].lower()
text_val = transforms.text_pass_1_strip_rt(text_val)
text_val = transforms.text_pass_2_cardname(text_val, name_orig)
text_val = transforms.text_pass_3_unary(text_val)
text_val = transforms.text_pass_4a_dashes(text_val)
text_val = transforms.text_pass_4b_x(text_val)
text_val = transforms.text_pass_5_counters(text_val)
text_val = transforms.text_pass_6_uncast(text_val)
text_val = transforms.text_pass_7_choice(text_val)
text_val = transforms.text_pass_8_equip(text_val)
text_val = transforms.text_pass_9_newlines(text_val)
text_val = transforms.text_pass_10_symbols(text_val)
if linetrans:
text_val = transforms.text_pass_11_linetrans(text_val)
text_val = utils.to_ascii(text_val)
text_val = text_val.strip()
mtext = Manatext(text_val, fmt = 'json')
valid = valid and mtext.valid
fields[field_text] = [(-1, mtext)]
# we don't need to worry about bsides because we handle that in the constructor
return parsed, valid and fields_check_valid(fields), fields
def fields_from_format(src_text, fmt_ordered, fmt_labeled, fieldsep):
parsed = True
valid = True
fields = {}
if fmt_labeled:
labels = {fmt_labeled[k] : k for k in fmt_labeled}
field_label_regex = '[' + ''.join(labels.keys()) + ']'
def addf(fields, fkey, fval):
# make sure you pass a pair
if fval and fval[1]:
if fkey in fields:
fields[fkey] += [fval]
else:
fields[fkey] = [fval]
textfields = src_text.split(fieldsep)
idx = 0
true_idx = 0
for textfield in textfields:
# ignore leading or trailing empty fields due to seps
if textfield == '':
if true_idx == 0 or true_idx == len(textfields) - 1:
true_idx += 1
continue
# count the field index for other empty fields but don't add them
else:
idx += 1
true_idx += 1
continue
lab = None
if fmt_labeled:
labs = re.findall(field_label_regex, textfield)
# use the first label if we saw any at all
if len(labs) > 0:
lab = labs[0]
textfield = textfield.replace(lab, '', 1)
# try to use the field label if we got one
if lab and lab in labels:
fname = labels[lab]
# fall back to the field order specified
elif idx < len(fmt_ordered):
fname = fmt_ordered[idx]
# we don't know what to do with this field: call it other
else:
fname = field_other
parsed = False
valid = False
# specialized handling
if fname in [field_cost]:
fval = Manacost(textfield)
parsed = parsed and fval.parsed
valid = valid and fval.valid
addf(fields, fname, (idx, fval))
elif fname in [field_text]:
fval = Manatext(textfield)
valid = valid and fval.valid
addf(fields, fname, (idx, fval))
elif fname in [field_supertypes, field_types, field_subtypes]:
addf(fields, fname, (idx, textfield.split()))
else:
addf(fields, fname, (idx, textfield))
idx += 1
true_idx += 1
# again, bsides are handled by the constructor
return parsed, valid and fields_check_valid(fields), fields
class CardEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Manatext):
return obj.format()
if isinstance(obj, Manacost):
return obj.format()
# Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj)
# Here's the actual Card class that other files should use.
class Card:
'''card representation with data'''
def __init__(self, src, fmt_ordered = fmt_ordered_default,
fmt_labeled = fmt_labeled_default,
fieldsep = utils.fieldsep, linetrans = True):
# source fields, exactly one will be set
self.json = None
self.raw = None
# 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] = ''
self.__dict__[field_cost] = Manacost('')
self.__dict__[field_supertypes] = []
self.__dict__[field_types] = []
self.__dict__[field_subtypes] = []
self.__dict__[field_loyalty] = ''
self.__dict__[field_loyalty + '_value'] = None
self.__dict__[field_pt] = ''
self.__dict__[field_pt + '_p'] = None
self.__dict__[field_pt + '_p_value'] = None
self.__dict__[field_pt + '_t'] = None
self.__dict__[field_pt + '_t_value'] = None
self.__dict__[field_text] = Manatext('')
self.__dict__[field_text + '_lines'] = []
self.__dict__[field_text + '_words'] = []
self.__dict__[field_text + '_lines_words'] = []
self.__dict__[field_other] = []
self.bside = None
# format-independent view of processed input
self.fields = None # will be reset later
# looks like a json object
if isinstance(src, dict):
self.json = src
if utils.json_field_bside in src:
self.bside = Card(src[utils.json_field_bside],
fmt_ordered = fmt_ordered,
fmt_labeled = fmt_labeled,
fieldsep = fieldsep,
linetrans = linetrans)
p_success, v_success, parsed_fields = fields_from_json(src, linetrans = linetrans)
self.parsed = p_success
self.valid = v_success
self.fields = parsed_fields
# otherwise assume text encoding
else:
self.raw = src
sides = src.split(utils.bsidesep)
if len(sides) > 1:
self.bside = Card(utils.bsidesep.join(sides[1:]),
fmt_ordered = fmt_ordered,
fmt_labeled = fmt_labeled,
fieldsep = fieldsep,
linetrans = linetrans)
p_success, v_success, parsed_fields = fields_from_format(sides[0], fmt_ordered,
fmt_labeled, fieldsep)
self.parsed = p_success
self.valid = v_success
self.fields = parsed_fields
# amusingly enough, both encodings allow infinitely deep nesting of bsides...
# python name hackery
if self.fields:
for field in self.fields:
# look for a specialized set function
if hasattr(self, '_set_' + field):
getattr(self, '_set_' + field)(self.fields[field])
# otherwise use the default one
elif field in self.__dict__:
self.set_field_default(field, self.fields[field])
# If we don't recognize the field, fail. This is a totally artificial
# limitation; if we just used the default handler for the else case,
# we could set arbitrarily named fields.
else:
raise ValueError('python name mangling failure: unknown field for Card(): '
+ field)
else:
# valid but not parsed indicates that the card was apparently empty
self.parsed = False
# These setters are invoked via name mangling, so they have to match
# the field names specified above to be used. Otherwise we just
# always fall back to the (uninteresting) default handler.
# Also note that all fields come wrapped in pairs, with the first member
# specifying the index the field was found at when parsing the card. These will
# all be -1 if the card was parsed from (unordered) json.
def set_field_default(self, field, values):
first = True
for idx, value in values:
if first:
first = False
self.__dict__[field] = value
else:
# stick it in other so we'll be know about it when we format the card
self.valid = False
self.__dict__[field_other] += [(idx, '<' + field + '> ' + str(value))]
def _set_loyalty(self, values):
first = True
for idx, value in values:
if first:
first = False
self.__dict__[field_loyalty] = value
try:
self.__dict__[field_loyalty + '_value'] = int(value)
except ValueError:
self.__dict__[field_loyalty + '_value'] = None
# Technically '*' could still be valid, but it's unlikely...
else:
self.valid = False
self.__dict__[field_other] += [(idx, '<loyalty> ' + str(value))]
def _set_pt(self, values):
first = True
for idx, value in values:
if first:
first = False
self.__dict__[field_pt] = value
p_t = value.split('/') # hardcoded
if len(p_t) == 2:
self.__dict__[field_pt + '_p'] = p_t[0]
try:
self.__dict__[field_pt + '_p_value'] = int(p_t[0])
except ValueError:
self.__dict__[field_pt + '_p_value'] = None
self.__dict__[field_pt + '_t'] = p_t[1]
try:
self.__dict__[field_pt + '_t_value'] = int(p_t[1])
except ValueError:
self.__dict__[field_pt + '_t_value'] = None
else:
self.valid = False
else:
self.valid = False
self.__dict__[field_other] += [(idx, '<pt> ' + str(value))]
def _set_text(self, values):
first = True
for idx, value in values:
if first:
first = False
mtext = value
self.__dict__[field_text] = mtext
fulltext = mtext.encode()
if fulltext:
self.__dict__[field_text + '_lines'] = map(Manatext,
fulltext.split(utils.newline))
self.__dict__[field_text + '_words'] = re.sub(utils.unletters_regex,
' ',
fulltext).split()
self.__dict__[field_text + '_lines_words'] = map(
lambda line: re.sub(utils.unletters_regex, ' ', line).split(),
fulltext.split(utils.newline))
else:
self.valid = False
self.__dict__[field_other] += [(idx, '<text> ' + str(value))]
def _set_other(self, values):
# just record these, we could do somthing unset valid if we really wanted
for idx, value in values:
self.__dict__[field_other] += [(idx, value)]
def to_json(self):
fields = {}
for key in self.fields:
fields[key] = self.fields[key][0][1]
fields["name"] = titlecase(transforms.name_unpass_1_dashes(self.__dict__[field_name]))
mtext = self.__dict__[field_text].text
mtext = transforms.text_unpass_1_choice(mtext, delimit = False)
mtext = transforms.text_unpass_2_counters(mtext)
mtext = transforms.text_unpass_3_uncast(mtext)
mtext = transforms.text_unpass_4_unary(mtext)
mtext = transforms.text_unpass_5_symbols(mtext, False, False)
mtext = sentencecase(mtext)
#mtext = transforms.text_unpass_6_cardname(mtext, fields["name"])
mtext = transforms.text_unpass_7_newlines(mtext)
mtext = transforms.text_unpass_8_unicode(mtext)
newtext = Manatext('')
newtext.text = mtext
newtext.costs = self.__dict__[field_text].costs
fields["text"] = newtext.format(for_forum = False, for_html = False)
if self.__dict__[field_pt]:
fields["pt"] = utils.from_unary(self.__dict__[field_pt])
if self.__dict__[field_loyalty]:
fields["loyalty"] = utils.from_unary(self.__dict__[field_loyalty])
return json.dumps(fields, cls=CardEncoder)
# Output functions that produce various formats. encode() is specific to
# the NN representation, use str() or format() for output intended for human
# readers.
def encode(self, fmt_ordered = fmt_ordered_default, fmt_labeled = fmt_labeled_default,
fieldsep = utils.fieldsep, initial_sep = True, final_sep = True,
randomize_fields = False, randomize_mana = False, randomize_lines = False):
outfields = []
for field in fmt_ordered:
if field in self.__dict__:
outfield = self.__dict__[field]
if outfield:
# specialized field handling for the ones that aren't strings (sigh)
if isinstance(outfield, list):
outfield_str = ' '.join(outfield)
elif isinstance(outfield, Manacost):
outfield_str = outfield.encode(randomize = randomize_mana)
elif isinstance(outfield, Manatext):
outfield_str = outfield.encode(randomize = randomize_mana)
if randomize_lines:
outfield_str = transforms.randomize_lines(outfield_str)
else:
outfield_str = outfield
else:
outfield_str = ''
if fmt_labeled and field in fmt_labeled:
outfield_str = fmt_labeled[field] + outfield_str
outfields += [outfield_str]
else:
raise ValueError('unknown field for Card.encode(): ' + str(field))
if randomize_fields:
random.shuffle(outfields)
if initial_sep:
outfields = [''] + outfields
if final_sep:
outfields = outfields + ['']
outstr = fieldsep.join(outfields)
if self.bside:
outstr = (outstr + utils.bsidesep
+ self.bside.encode(fmt_ordered = fmt_ordered,
fmt_labeled = fmt_labeled,
fieldsep = fieldsep,
randomize_fields = randomize_fields,
randomize_mana = randomize_mana,
initial_sep = initial_sep, final_sep = final_sep))
return outstr
def format(self, gatherer = False, for_forum = False, vdump = False, for_html = False):
linebreak = '\n'
if for_html:
linebreak = '<hr>' + linebreak
outstr = ''
if for_html:
outstr += '<div class="card-text">\n'
if gatherer:
cardname = titlecase(transforms.name_unpass_1_dashes(self.__dict__[field_name]))
if vdump and not cardname:
cardname = '_NONAME_'
# in general, for_html overrides for_forum
if for_html:
outstr += '<b>'
elif for_forum:
outstr += '[b]'
outstr += cardname
if for_html:
outstr += '</b>'
elif for_forum:
outstr += '[/b]'
coststr = self.__dict__[field_cost].format(for_forum=for_forum, for_html=for_html)
if vdump or not coststr == '_NOCOST_':
outstr += ' ' + coststr
if for_html and for_forum:
#force for_html to false to create tootip with forum spoiler
outstr += ('<div class="hover_img"><a href="#">[F]</a> <span><p>'
+ self.format(gatherer=gatherer, for_forum=for_forum, for_html=False, vdump=vdump).replace('\n', '<br>')
+ '</p></span></div><a href="#top" style="float: right;">back to top</a>')
if self.__dict__[field_rarity]:
if self.__dict__[field_rarity] in utils.json_rarity_unmap:
rarity = utils.json_rarity_unmap[self.__dict__[field_rarity]]
else:
rarity = self.__dict__[field_rarity]
outstr += ' (' + rarity + ')'
if vdump:
if not self.parsed:
outstr += ' _UNPARSED_'
if not self.valid:
outstr += ' _INVALID_'
outstr += linebreak
basetypes = map(str.capitalize, self.__dict__[field_types])
if vdump and len(basetypes) < 1:
basetypes = ['_NOTYPE_']
outstr += ' '.join(map(str.capitalize, self.__dict__[field_supertypes]) + basetypes)
if self.__dict__[field_subtypes]:
outstr += (' ' + utils.dash_marker + ' ' +
' '.join(self.__dict__[field_subtypes]).title())
if self.__dict__[field_pt]:
outstr += ' (' + utils.from_unary(self.__dict__[field_pt]) + ')'
if self.__dict__[field_loyalty]:
outstr += ' ((' + utils.from_unary(self.__dict__[field_loyalty]) + '))'
if self.__dict__[field_text].text:
outstr += linebreak
mtext = self.__dict__[field_text].text
mtext = transforms.text_unpass_1_choice(mtext, delimit = False)
mtext = transforms.text_unpass_2_counters(mtext)
#mtext = transforms.text_unpass_3_uncast(mtext)
mtext = transforms.text_unpass_4_unary(mtext)
mtext = transforms.text_unpass_5_symbols(mtext, for_forum, for_html)
mtext = sentencecase(mtext)
mtext = transforms.text_unpass_6_cardname(mtext, cardname)
mtext = transforms.text_unpass_7_newlines(mtext)
#mtext = transforms.text_unpass_8_unicode(mtext)
newtext = Manatext('')
newtext.text = mtext
newtext.costs = self.__dict__[field_text].costs
outstr += newtext.format(for_forum = for_forum, for_html = for_html)
if vdump and self.__dict__[field_other]:
outstr += linebreak
if for_html:
outstr += '<i>'
elif for_forum:
outstr += '[i]'
else:
outstr += utils.dash_marker * 2
first = True
for idx, value in self.__dict__[field_other]:
if for_html:
if not first:
outstr += '<br>\n'
else:
first = False
else:
outstr += linebreak
outstr += '(' + str(idx) + ') ' + str(value)
if for_html:
outstr += '</i>'
if for_forum:
outstr += '[/i]'
else:
cardname = self.__dict__[field_name]
#cardname = transforms.name_unpass_1_dashes(self.__dict__[field_name])
if vdump and not cardname:
cardname = '_NONAME_'
outstr += cardname
coststr = self.__dict__[field_cost].format(for_forum=for_forum, for_html=for_html)
if vdump or not coststr == '_NOCOST_':
outstr += ' ' + coststr
if vdump:
if not self.parsed:
outstr += ' _UNPARSED_'
if not self.valid:
outstr += ' _INVALID_'
if for_html and for_forum:
#force for_html to false to create tootip with forum spoiler
outstr += ('<div class="hover_img"><a href="#">[F]</a> <span><p>'
+ self.format(gatherer=gatherer, for_forum=for_forum, for_html=False, vdump=vdump).replace('\n', '<br>')
+ '</p></span></div><a href="#top" style="float: right;">back to top</a>')
outstr += linebreak
outstr += ' '.join(self.__dict__[field_supertypes] + self.__dict__[field_types])
if self.__dict__[field_subtypes]:
outstr += ' ' + utils.dash_marker + ' ' + ' '.join(self.__dict__[field_subtypes])
if self.__dict__[field_rarity]:
if self.__dict__[field_rarity] in utils.json_rarity_unmap:
rarity = utils.json_rarity_unmap[self.__dict__[field_rarity]]
else:
rarity = self.__dict__[field_rarity]
outstr += ' (' + rarity.lower() + ')'
if self.__dict__[field_text].text:
outstr += linebreak
mtext = self.__dict__[field_text].text
mtext = transforms.text_unpass_1_choice(mtext, delimit = True)
#mtext = transforms.text_unpass_2_counters(mtext)
#mtext = transforms.text_unpass_3_uncast(mtext)
mtext = transforms.text_unpass_4_unary(mtext)
mtext = transforms.text_unpass_5_symbols(mtext, for_forum, for_html)
#mtext = transforms.text_unpass_6_cardname(mtext, cardname)
mtext = transforms.text_unpass_7_newlines(mtext)
#mtext = transforms.text_unpass_8_unicode(mtext)
newtext = Manatext('')
newtext.text = mtext
newtext.costs = self.__dict__[field_text].costs
outstr += newtext.format(for_forum=for_forum, for_html=for_html)
if self.__dict__[field_pt]:
outstr += linebreak
outstr += '(' + utils.from_unary(self.__dict__[field_pt]) + ')'
if self.__dict__[field_loyalty]:
outstr += linebreak
outstr += '((' + utils.from_unary(self.__dict__[field_loyalty]) + '))'
if vdump and self.__dict__[field_other]:
outstr += linebreak
if for_html:
outstr += '<i>'
else:
outstr += utils.dash_marker * 2
first = True
for idx, value in self.__dict__[field_other]:
if for_html:
if not first:
outstr += '<br>\n'
else:
first = False
else:
outstr += linebreak
outstr += '(' + str(idx) + ') ' + str(value)
if for_html:
outstr += '</i>'
if self.bside:
if for_html:
outstr += '\n'
# force for_forum to false so that the inner div doesn't duplicate the forum
# spoiler of the bside
outstr += self.bside.format(gatherer=gatherer, for_forum=False, for_html=for_html, vdump=vdump)
else:
outstr += linebreak
outstr += utils.dash_marker * 8
outstr += linebreak
outstr += self.bside.format(gatherer=gatherer, for_forum=for_forum, for_html=for_html, vdump=vdump)
# if for_html:
# if for_forum:
# outstr += linebreak
# # force for_html to false to create a copyable forum spoiler div
# outstr += ('<div>'
# + self.format(gatherer=gatherer, for_forum=for_forum, for_html=False, vdump=vdump).replace('\n', '<br>')
# + '</div>')
if for_html:
outstr += "</div>"
return outstr
def to_mse(self, print_raw = False, vdump = False):
outstr = ''
# need a 'card' string first
outstr += 'card:\n'
cardname = titlecase(transforms.name_unpass_1_dashes(self.__dict__[field_name]))
outstr += '\tname: ' + cardname + '\n'
if self.__dict__[field_rarity]:
if self.__dict__[field_rarity] in utils.json_rarity_unmap:
rarity = utils.json_rarity_unmap[self.__dict__[field_rarity]]
else:
rarity = self.__dict__[field_rarity]
outstr += '\trarity: ' + rarity.lower() + '\n'
if not self.__dict__[field_cost].none:
outstr += ('\tcasting cost: '
+ self.__dict__[field_cost].format().replace('{','').replace('}','')
+ '\n')
outstr += '\tsuper type: ' + ' '.join(self.__dict__[field_supertypes]
+ self.__dict__[field_types]).title() + '\n'
if self.__dict__[field_subtypes]:
outstr += '\tsub type: ' + ' '.join(self.__dict__[field_subtypes]).title() + '\n'
if self.__dict__[field_pt]:
ptstring = utils.from_unary(self.__dict__[field_pt]).split('/')
if (len(ptstring) > 1): # really don't want to be accessing anything nonexistent.
outstr += '\tpower: ' + ptstring[0] + '\n'
outstr += '\ttoughness: ' + ptstring[1] + '\n'
if self.__dict__[field_text].text:
mtext = self.__dict__[field_text].text
mtext = transforms.text_unpass_1_choice(mtext, delimit = False)
mtext = transforms.text_unpass_2_counters(mtext)
mtext = transforms.text_unpass_3_uncast(mtext)
mtext = transforms.text_unpass_4_unary(mtext)
mtext = transforms.text_unpass_5_symbols(mtext, False, False)
mtext = sentencecase(mtext)
# I don't really want these MSE specific passes in transforms,
# but they could be pulled out separately somewhere else in here.
mtext = mtext.replace(utils.this_marker, '<atom-cardname><nospellcheck>'
+ utils.this_marker + '</nospellcheck></atom-cardname>')
mtext = transforms.text_unpass_6_cardname(mtext, cardname)
mtext = transforms.text_unpass_7_newlines(mtext)
mtext = transforms.text_unpass_8_unicode(mtext)
newtext = Manatext('')
newtext.text = mtext
newtext.costs = self.__dict__[field_text].costs
newtext = newtext.format()
# See, the thing is, I think it's simplest and easiest to just leave it like this.
# What could possibly go wrong?
newtext = newtext.replace('{','<sym-auto>').replace('}','</sym-auto>')
else:
newtext = ''
# Annoying special case for bsides;
# This could be improved by having an intermediate function that returned
# all of the formatted fields in a data structure and a separate wrapper
# that actually packed them into the MSE format.
if self.bside:
newtext = newtext.replace('\n','\n\t\t')
outstr += '\trule text:\n\t\t' + newtext + '\n'
outstr += '\tstylesheet: new-split\n'
cardname2 = titlecase(transforms.name_unpass_1_dashes(
self.bside.__dict__[field_name]))
outstr += '\tname 2: ' + cardname2 + '\n'
if self.bside.__dict__[field_rarity]:
if self.bside.__dict__[field_rarity] in utils.json_rarity_unmap:
rarity2 = utils.json_rarity_unmap[self.bside.__dict__[field_rarity]]
else:
rarity2 = self.bside.__dict__[field_rarity]
outstr += '\trarity 2: ' + rarity2.lower() + '\n'
if not self.bside.__dict__[field_cost].none:
outstr += ('\tcasting cost 2: '
+ self.bside.__dict__[field_cost].format()
.replace('{','').replace('}','')
+ '\n')
outstr += ('\tsuper type 2: '
+ ' '.join(self.bside.__dict__[field_supertypes]
+ self.bside.__dict__[field_types]).title() + '\n')
if self.bside.__dict__[field_subtypes]:
outstr += ('\tsub type 2: '
+ ' '.join(self.bside.__dict__[field_subtypes]).title() + '\n')
if self.bside.__dict__[field_pt]:
ptstring2 = utils.from_unary(self.bside.__dict__[field_pt]).split('/')
if (len(ptstring2) > 1): # really don't want to be accessing anything nonexistent.
outstr += '\tpower 2: ' + ptstring2[0] + '\n'
outstr += '\ttoughness 2: ' + ptstring2[1] + '\n'
if self.bside.__dict__[field_text].text:
mtext2 = self.bside.__dict__[field_text].text
mtext2 = transforms.text_unpass_1_choice(mtext2, delimit = False)
mtext2 = transforms.text_unpass_2_counters(mtext2)
mtext2 = transforms.text_unpass_3_uncast(mtext2)
mtext2 = transforms.text_unpass_4_unary(mtext2)
mtext2 = transforms.text_unpass_5_symbols(mtext2, False, False)
mtext2 = sentencecase(mtext2)
mtext2 = mtext2.replace(utils.this_marker, '<atom-cardname><nospellcheck>'
+ utils.this_marker + '</nospellcheck></atom-cardname>')
mtext2 = transforms.text_unpass_6_cardname(mtext2, cardname2)
mtext2 = transforms.text_unpass_7_newlines(mtext2)
mtext2 = transforms.text_unpass_8_unicode(mtext2)
newtext2 = Manatext('')
newtext2.text = mtext2
newtext2.costs = self.bside.__dict__[field_text].costs
newtext2 = newtext2.format()
newtext2 = newtext2.replace('{','<sym-auto>').replace('}','</sym-auto>')
newtext2 = newtext2.replace('\n','\n\t\t')
outstr += '\trule text 2:\n\t\t' + newtext2 + '\n'
# Need to do Special Things if it's a planeswalker.
# This code mostly works, but it won't get quite the right thing if the planeswalker
# abilities don't come before any other ones. Should be fixed.
elif "planeswalker" in str(self.__dict__[field_types]):
outstr += '\tstylesheet: m15-planeswalker\n'
# set up the loyalty cost fields using regex to find how many there are.
i = 0
lcost_regex = r'[-+]?\d+: ' # 1+ figures, might be 0.
for lcost in re.findall(lcost_regex, newtext):
i += 1
outstr += '\tloyalty cost ' + str(i) + ': ' + lcost + '\n'
# sub out the loyalty costs.
newtext = re.sub(lcost_regex, '', newtext)
# We need to uppercase again, because MSE won't magically capitalize for us
# like it does after semicolons.
# Abusing passes like this is terrible, should really fix sentencecase.
newtext = transforms.text_pass_9_newlines(newtext)
newtext = sentencecase(newtext)
newtext = transforms.text_unpass_7_newlines(newtext)
if self.__dict__[field_loyalty]:
outstr += '\tloyalty: ' + utils.from_unary(self.__dict__[field_loyalty]) + '\n'
newtext = newtext.replace('\n','\n\t\t')
outstr += '\trule text:\n\t\t' + newtext + '\n'
else:
newtext = newtext.replace('\n','\n\t\t')
outstr += '\trule text:\n\t\t' + newtext + '\n'
# now append all the other useless fields that the setfile expects.
outstr += '\thas styling: false\n\ttime created:2015-07-20 22:53:07\n\ttime modified:2015-07-20 22:53:08\n\textra data:\n\timage:\n\tcard code text:\n\tcopyright:\n\timage 2:\n\tcopyright 2:\n\tnotes:'
return outstr
def vectorize(self):
ld = '('
rd = ')'
outstr = ''
if self.__dict__[field_rarity]:
outstr += ld + self.__dict__[field_rarity] + rd + ' '
coststr = self.__dict__[field_cost].vectorize(delimit = True)
if coststr:
outstr += coststr + ' '
typestr = ' '.join(map(lambda s: '(' + s + ')',
self.__dict__[field_supertypes] + self.__dict__[field_types]))
if typestr:
outstr += typestr + ' '
if self.__dict__[field_subtypes]:
outstr += ' '.join(self.__dict__[field_subtypes]) + ' '
if self.__dict__[field_pt]:
outstr += ' '.join(map(lambda s: '(' + s + ')',
self.__dict__[field_pt].replace('/', '/ /').split()))
outstr += ' '
if self.__dict__[field_loyalty]:
outstr += '((' + self.__dict__[field_loyalty] + ')) '
outstr += self.__dict__[field_text].vectorize()
if self.bside:
outstr = '_ASIDE_ ' + outstr + '\n\n_BSIDE_ ' + self.bside.vectorize()
return outstr
def get_colors(self):
return self.__dict__[field_cost].get_colors()
def get_types(self):
return self.__dict__[field_types]
def get_cmc(self):
return self.__dict__[field_cost].cmc