def open_deck(self, f, dname=None, durl=None, aname=None, aurl=None, diurl=None): """ Opens a deck from file f (overwriting current deck if present) :param f: the file path (local) :param dname: name of the deck :param durl: deck url :param aname: deck author :param aurl: author url :param diurl: discord url """ # TODO: add better exception handling and raising i.e. check for existence # of file first if not os.path.exists(f): raise lts.LituusException(lts.EIOIN, "File {} does not exist".format(f)) try: self._read_deck_(f) self._dname = dname if dname else self._path.split('/')[-1] self._url = durl if durl else "Unknown" self._author = aname if aname else "Unknown" self._aurl = aurl if aurl else "" self._diurl = diurl if diurl else "" except IOError as e: raise lts.LituusExeption(lts.EIOIN, e) except Exception as e: raise lts.LituusException(lts.EUNDEF, e)
def merge_attrs(attrs, strict=1): """ merges the attributes lists in attrs based on specified strictness level. if strict is True confirms all proplists have the same key->value pairs, otherwise adds unique key->value pairs and 'ands' differing values :param attrs: list of attribute lists :param strict: oneof: 0 = LOW no checking on sameness across parameters/parameter values 1 = MEDIUM parameter values across common parameters must be the same but parameters are not required to be shared across all attribute dicts 2 = HIGH parameters and parameter values must be the same in each prop list :return: merged attribute dicts """ if not attrs: return {} # don't bother if the list is empty mattrs = {} keys = list(set.union(*map(set, [attr.keys() for attr in attrs]))) for key in keys: vals = set() # check each paraemter for existence if high strictness for attr in attrs: if not key in attr: if strict == 2: raise lts.LituusException( lts.ETAG, "Incompatible attribute {}".format(key)) else: vals.add(attr[key]) # check for same attribute values if strictness is not low if strict > 0 and len(vals) > 1: raise lts.LituusException( lts.ETAG, "Incompatible attribute values {}".format(key)) mattrs[key] = mtgl.AND.join([val for val in vals]) return mattrs
def attr(self, nid, attr): try: return self._t.node[nid][attr] except KeyError: if not nid in self._t: raise lts.LituusException(lts.ENODE, "No such node {}".format(nid)) else: raise lts.LituusException( lts.ENODE, "{} has attribute {}".format(nid, attr))
def _read_deck_(self, f): """ reads a deck from file with path f """ # check for commander if not self._cmdr: raise ds = None try: # get the multiverse (assumes saved) mv = multiverse.multiverse(0) # check file extension and read in if possible _, fext = os.path.splitext(f) if fext == '.cod': ds = EDHDeck._read_cod_(f) elif fext == '.dec': ds = EDHDeck._read_dec_(f) else: raise lts.LituusException( lts.EPARAM, "Unable to process '{}' files".format(fext)) except lts.LituusException: raise except Exception as e: raise lts.LituusException(lts.EUNDEF, "Error reading deck {}\n{}".format(f, e)) # start with the mainboard (make sure split cards are handled properly) for qty, cname in ds['mainboard']: if '/' in cname and not '//' in cname: cname = " // ".join( [cname.strip() for cname in cname.split('/')]) self.add_card(mv[cname], qty) # then the sideboard for qty, cname in ds['sideboard']: if '/' in cname and not '//' in cname: cname = " // ".join( [cname.strip() for cname in cname.split('/')]) try: self.add_sb_card(mv[cname], qty) except mtgl.MTGLException: # we'll ignore sideboard errors pass # since some decks have commander(s) in sideboard, some in mainboard & some # in both, make uniform and place in mainboard for cmdr in self._cmdr: if cmdr in self._sb: if not cmdr in self._mb: self.add_card(mv[cmdr], 1) self.del_sb_card(cmdr) # set the deck name and path self._dname = ds['name'] # set the name self._path = f
def siblings(self, nid): try: ss = self.children(self.parent(nid)) ss.remove(nid) return ss except KeyError: raise lts.LituusException(lts.ENODE, "No such node {}".format(nid))
def parent(self, nid): try: return next(self._t.predecessors(nid)) except StopIteration: return None except KeyError: raise lts.LituusException(lts.ENODE, "No such node {}".format(nid))
def _read_dec_(f): """ reads a .dec deck file """ ds = {} fin = None try: fin = open(f) ls = fin.readlines() fin.close() # get cards from mainboard and sideboard # 3 cases mainboard, sideboard and token (we ignore tokens for now) ds = {'name': "", 'mainboard': [], 'sideboard': []} sb = False for l in ls: l = l.strip() # remove trailing newlines if not l: continue # skip empty lines if l.startswith('SB:'): sb = True # tokens start after SB and are not prepended with "SB: " if sb and not l.startswith('SB:'): break else: if sb: ds['sideboard'].append( EDHDeck._dec_card_(l[3:].strip())) else: ds['mainboard'].append(EDHDeck._dec_card_(l)) except IOError as e: raise lts.LituusException(lts.EOIOIN, "Failed reading file {}".format(e)) finally: if fin: fin.close() return ds
def harvest(name, jcard): """ extract details from the json card and return the card dict :param name: the name of the card :param jcard: json card dict :return: card dict """ try: dcard = { 'rid': md5(name.encode()).hexdigest(), 'name': name, 'layout': jcard['layout'], 'super-type': jcard['supertypes'], 'type': jcard['types'], 'sub-type': jcard['subtypes'], 'cmc': int(jcard['convertedManaCost']) if 'convertedManaCost' in jcard else 0, 'mana-cost': jcard['manaCost'] if 'manaCost' in jcard else '{0}', 'P/T': None, 'loyalty': None, 'color-ident': jcard['colorIdentity'], 'colors': jcard['colors'], 'oracle': jcard['text'] if 'text' in jcard else "", 'tag': "", 'mtgt': None, 'sets': jcard['printings'], } except KeyError as e: raise lts.LituusException(lts.EDATA, "{}->{}".format(name, e)) # check for existence before setting the following if 'Planeswalker' in jcard['types'] and 'loyalty' in jcard: dcard['loyalty'] = jcard['loyalty'] elif 'Creature' in jcard['types']: dcard['P/T'] = "{}/{}".format(jcard['power'], jcard['toughness']) if 'faceConvertedManacost' in jcard: dcard['face-cmc'] = jcard['faceConvertedManacost'] else: dcard['face-cmc'] = dcard['cmc'] return dcard
def right_sibling(self, nid): try: ss = self.children(self.parent(nid)) i = ss.index(nid) return ss[i + 1] except IndexError: return None except KeyError: raise lts.LituusException(lts.ENODE, "No such node {}".format(nid))
def _read_cod_(f): """ reads a cockatrice deck file """ ds = {'name': "", 'mainboard': [], 'sideboard': []} fin = None try: # open the xml file, soup it and close fin = open(f) deck = soup(fin, 'xml') fin.close() # is there a name in the cockatrice file? try: dname = deck.find('deckname').contents[0].__str__() if dname: ds['name'] = dname except IndexError: pass # get mainboard, sideboard cards try: zones = deck.find_all('zone') for zone in zones: if not 'name' in zone.attrs: continue if zone.attrs['name'] == 'main': for card in zone.find_all('card'): ds['mainboard'].append(( int(card.attrs['number']), # qty card.attrs['name'].__str__()) # card name ) elif zone.attrs['name'] == 'side': for card in zone.find_all('card'): ds['sideboard'].append(( int(card.attrs['number']), # qty card.attrs['name'].__str__()) # card name ) except IndexError as e: raise lts.LituusException(lts.EDATA, "Unexpected {}".format(e)) except IOError as e: raise lts.LituusException(lts.EOIOIN, "Failed reading file {}".format(e)) finally: if fin: fin.close() # set the path if we get here return ds
def add_edge(self, pid, cid): """ adds an edge from parent pid to child cid :param pid: parent-id :param cid: child-id """ # don't allow edges to be added to a node with a parent if self.parent(cid): raise lts.LituusException(lts.ENODE, "{} is not rootless".format(cid)) self._t.add_edge(pid, cid)
def del_card(self, cname): """ deletes the card identified by cname :param cname: card name """ try: del self._qty[cname] del self._mb[cname] except KeyError: raise lts.LituusException(lts.EPARAM, "No such card {}".format(cname))
def del_sb_card(self, cname): """ deletes the sideboard card identified by cname :param cname: card name """ try: del self._sqty[cname] del self._sb[cname] except KeyError: raise lts.LituusException(lts.EDATA, "{} does not exist".format(cname))
def add_attr(self, nid, k, v): """ adds the k=v pair to the nodes attributes. Note if the attribute k already exists in the node's attributes, it will be overwritten with v :param nid: the node-id :param k: the key :param v: the value """ try: self._t.node[nid][k] = v except KeyError: raise lts.LituusException(lts.ENODE, "No such node {}".format(nid))
def untag(tkn): """ returns the tag-id, tag value and attribute dict of tkn if it is a tagged item :param tkn: the token to untag :return: the tag, tag-value and property dict """ attrs = {} try: # get the tag, the value and any properties tag, val, attr = re_tag.match(tkn).groups() if attr: attrs = { p[0]: p[1] for p in [p.split('=') for p in re_tag_attrs.findall(tkn)] } return tag, val, attrs except AttributeError: # signifies a None returned from re_tag raise lts.LituusException(lts.ETAG, "Invalid tag {}".format(tkn))
def cedh_hash(self): """ calculates the cockatrice hash for an online cEDH Competition deck (Commander(s) and only commander(s) in sidedeck) """ # we have to temporalily remove any cards in the sideboard t_sb = self._sb.copy() t_sqty = self._sqty.copy() t_mb = self._mb.copy() t_qty = self._qty.copy() dhash = None try: # empty the sideboard self.del_sideboard() # move commanders from mainboard to sideboard before hashing for cmdr in self.commander: card = self._mb[cmdr] self.del_card(cmdr) self.add_sb_card(card, 1) # calculate the hash dhash = self.hash(True) # & restore the deck for cname in self._sb: self.add_card(self._sb[cname], 1) self.del_sideboard() for cname in t_sb: self.add_sb_card(t_sb[cname], t_sqty[cname]) except Exception as e: # recover self._mb = t_mb self._qty = t_qty self._sb = t_sb self._sqty = t_sqty raise lts.LituusException(lts.EUNDEF, "Error hashing {}".format(e)) return dhash
def gold_hist(self): """ returns a histogram of the count of multicolored cards. This will be of the form i->n where i is the number of colors and n is the number of cards in the deck having i colors """ # set histogram to 0 for 2..n where n is the maximum number of colors # i.e. 2 = 2 different colors, 3 = 3 different colors etc mch = {x: 0 for x in range(2, len(mtg.mana_colors) + 1)} for cname in self._mb: # get card object, skip if land card = self._mb[cname] if card.is_land(): continue # get the mana symbols in the cards cost try: mcs = mtg.re_mana_sym.findall(card.mana_cost) except TypeError: # cards with no casting cost i.e. suspend will end up here if mv[card].cmc == 0: continue else: raise lts.LituusException(lts.UNDEF, "Unexpected {}".format(e)) mcs2 = [] # for ease, recreate manacost list removing could be snow, phyrexian # or hybrid and numeral symbols for mc in mcs: if '/' in mc: for s in mc.split('/'): if s in mtg.mana_colors: mcs2.append(s) elif mc in mtg.mana_colors: mcs2.append(mc) try: mch[len(set(mcs2))] += 1 except KeyError: pass return mch
def findall(self, ntype, source='root', attr=None, val=None): """ finds all nodes in the tree of the type ntype starting at source with attribute attr (if set) having value val (if set) :param ntype: node type to find :param source: the source to start the search from :param attr: the attribute key the node will have :param val: the val that the given attribute key will have :return: a list of node ids """ if val and not attr: raise lts.LituusException(lts.ETREE, "attr required with val") found = [] for node in nx.dfs_preorder_nodes(self._t, source): if node_type(node) == ntype and not node == source: if attr: if attr in self._t.node[node]: if val: if self._t.node[node][attr] == val: found.append(node) else: if attr in self._t.node[nid]: found.append(node) else: found.append(node) return found
def double_mana(self): """ returns list of cards in pack with double (or more) of the same mana symbol in the casting cost """ dbl = [] for cname in self._mb: # get the card object and skip if a land card = self._mb[cname] if card.is_land(): continue # find mana symbols in the casting cost try: mcs = mtg.re_mana_sym.findall(card.mana_cost) except TypeError as e: # cards with no casting cost i.e. suspend will end up here if mv[card].cmc == 0: continue else: raise lts.LituusException(lts.UNDEF, "Unexpected {}".format(e)) # for ease, recreate manacost list removing extra symbols could be snow, # phyrexian or hybrid and numeral symbols mcs2 = [] for mc in mcs: if '/' in mc: for s in mc.split('/'): if s in mtg.mana_colors: mcs2.append(s) elif mc in mtg.mana_colors: mcs2.append(mc) # iterate the modified list of mana symbols for mc in mcs2: if mcs2.count(mc) > 1: dbl.append(cname) break return dbl
def adds_mana_all(self): raise lts.LituusException(lts.EIMPL,"Pending") def adds_mana(self): raise lts.LituusException(lts.EIMPL,"Pending")
def land_category(self): raise lts.LituusException(lts.EIMPL,"Pending") #### PRIVATE HELPER FUNCTIONS #### def _etb_self_(self): raise lts.LituusException(lts.EIMPL,"Pending")
def mana_plurality(self): raise lts.LituusException(lts.EIMPL,"Pending") #### LAND RELATED #### def utility_land(self): raise lts.LituusException(lts.EIMPL,"Pending")
def adds_mana_pref(self): raise lts.LituusException(lts.EIMPL,"Pending") def mana_plurality(self): raise lts.LituusException(lts.EIMPL,"Pending")
def utility_land(self): raise lts.LituusException(lts.EIMPL,"Pending") def nonmana_land(self): raise lts.LituusException(lts.EIMPL,"Pending")
def save_deck(self, f): raise lts.LituusException(lts.EIMPL, "Pending")
def nonmana_land(self): raise lts.LituusException(lts.EIMPL,"Pending") def land_category(self): raise lts.LituusException(lts.EIMPL,"Pending")
def _etb_self_(self): raise lts.LituusException(lts.EIMPL,"Pending") def _etb_other_(self): raise lts.LituusException(lts.EIMPL,"Pending")
def _etb_other_(self): raise lts.LituusException(lts.EIMPL,"Pending") def _reduce_by_(self,ms): raise lts.LituusException(lts.EIMPL,"Pending")
def multiverse(update=0): """ :param update: one of 0 = load saved multiverse 1 = reparse json file and create new multiverse 2 = download json file and create new multiverse https://mtgjson.com/json/AllSets.json :returns multiverse dict """ # files to create mv = pack.Pack() # multiverse tc = {} # transformed cards n2r = {} # name to reference dict if update == 0: fin = None try: fin = open(mvpath, 'rb') mv = pickle.load(fin) fin.close() return mv except FileNotFoundError: raise lts.LituusException(lts.EIOIN, "Multiverse file does not exist") except pickle.PickleError: raise lts.LituusException(lts.EIOIN, "Error loading multiverse") finally: if fin: fin.close() # there is no version checking. on update, downloads AllCards.json & reparses # TODO: Downloading allcards disabled until debugging is complete if update == 2 and False: fout = None try: print("Requesting AllCards.json") jurl = requests.get(url_cards) if jurl.status_code != 200: raise RuntimeError fout = open(jpath, 'w') fout.write(jurl.json()) fout.close() print("AllCards.json updated") except RuntimeError: raise lts.LituusException(lts.ENET, "Failed to download AllCards.json") except OSError: raise lts.LituusException(lts.EIOOUT, "Failed to save AllCards.json") finally: if fout: fout.close() # read in AllCards.json fin = None try: print("Loading the Multiverse") fin = open(jpath, 'r') mverse = _hack_cards_(json.load(fin)) # fix errors in cards fin.close() except IOError: raise lts.LituusException(lts.ERRIOIN, "Error reading AllCards.json") finally: if fin: fin.close() # parse the mverse print('Tagging the Multiverse') start = time.time() import_cards(mv, tc, n2r, mverse) end = time.time() print("Imported {} cards and {} transformed cards in {:.2f}s.".format( mv.qty(), len(tc), end - start)) #TODO: add graphing here and do the progress bar print('Graphing the Multiverse') # pickle the multiverse fout = None try: print("Writing multiverse file") fout = open(mvpath, 'wb') pickle.dump(mv, fout) fout.close() except pickle.PickleError: raise lts.LituusException(lts.EIOOUT, "Failed pickling multiverse") except IOError as e: raise lts.LituusException(lts.EIOOUT, "Failed saving multiverse") finally: if fout: fout.close() # & then transformed fout = None try: print("Writing transformed file") fout = open(tcpath, 'wb') pickle.dump(tc, fout) fout.close() except pickle.PickleError as e: raise lts.LituusException(lts.EIOOUT, "Failed pickling transformed") except IOError as e: raise lts.LituusException(lts.EIOOUT, "Failed saving transformed") finally: if fout: fout.close() # & then the n2r dict fout = None try: print("Writing Name Reference file") fout = open(n2rpath, 'wb') pickle.dump(n2r, fout) fout.close() except pickle.PickleError as e: raise lts.LituusException(lts.EIOOUT, "Failed pickling name reference") except IOError as e: raise lts.LituusException(lts.EIOOUT, "Failed saving name reference") finally: if fout: fout.close() return mv
def print(self,attr=False): raise lts.LituusException(lts.EIMPL,"Pending") @property