def test_card_type_checkers(self): """Check the various utilities for checking card type and properties.""" self.maxDiff = None oDob = IAbstractCard(u"L\xe1z\xe1r Dobrescu") self.assertTrue(is_vampire(oDob)) self.assertTrue(is_crypt_card(oDob)) self.assertFalse(is_trifle(oDob)) oAbo = IAbstractCard('Abombwe') self.assertFalse(is_vampire(oAbo)) self.assertFalse(is_crypt_card(oAbo)) self.assertTrue(is_trifle(oAbo)) oAshur = IAbstractCard('Ashur Tablets') self.assertFalse(is_vampire(oAshur)) self.assertFalse(is_crypt_card(oAshur)) self.assertFalse(is_trifle(oAshur)) oEarl = IAbstractCard(u'Earl "Shaka74" Deams') self.assertFalse(is_vampire(oEarl)) self.assertTrue(is_crypt_card(oEarl)) self.assertFalse(is_trifle(oEarl)) oOssian = IAbstractCard('Ossian') self.assertFalse(is_vampire(oOssian)) self.assertFalse(is_crypt_card(oOssian)) self.assertFalse(is_trifle(oOssian))
def _extract_crypt(self, dCards): """Extract the crypt cards from the list.""" dCryptStats = { 'size': 0, 'min': 75, 'max': 0, 'avg': 0.0, } dVamps = {} for tKey, iCount in dCards.iteritems(): oCard = tKey[0] if is_crypt_card(oCard): dVamps[tKey] = iCount dCryptStats['size'] += iCount if oCard.cardtype[0].name == "Vampire": iCap = oCard.capacity elif oCard.cardtype[0].name == "Imbued": iCap = oCard.life dCryptStats['avg'] += iCap * iCount if iCap > dCryptStats['max']: dCryptStats['max'] = iCap if iCap < dCryptStats['min']: dCryptStats['min'] = iCap if dCryptStats['size'] > 0: dCryptStats['avg'] = round(dCryptStats['avg'] / dCryptStats['size'], 2) if dCryptStats['min'] == 75: dCryptStats['min'] = 0 return dVamps, dCryptStats
def _setup_cardlists(self, aSelectedCards, bCrypt): """Extract the needed card info from the model""" aAllAbsCards = [IAbstractCard(oCard) for oCard in self.get_all_cards()] iCryptSize = 0 iLibrarySize = 0 self.dSelectedCounts = {} self.iSelectedCount = 0 # Initialise dict 1st, as cards with a count of 0 in the selection # are possible. for oCard in aSelectedCards: self.dSelectedCounts.setdefault(oCard, 0) for oCard in aAllAbsCards: if is_crypt_card(oCard): iCryptSize += 1 else: iLibrarySize += 1 if oCard in self.dSelectedCounts: self.dSelectedCounts[oCard] += 1 self.iSelectedCount += 1 # The assumption is that the user is interested in all copies of # the selected cards (as it seems the most useful), so we treat # the selection of any instance of the card in the list as # selecting all of them if bCrypt: self.iTotal = iCryptSize else: self.iTotal = iLibrarySize
def _setup_cardlists(self, aSelectedCards): """Extract the needed card info from the model""" aAllAbsCards = [IAbstractCard(oCard) for oCard in self._get_all_cards()] iCryptSize = 0 iLibrarySize = 0 self.dSelectedCounts = {} self.iSelectedCount = 0 # Initialise dict 1st, as cards with a count of 0 in the selection # are possible. for oCard in aSelectedCards: self.dSelectedCounts.setdefault(oCard, 0) for oCard in aAllAbsCards: if is_crypt_card(oCard): iCryptSize += 1 else: iLibrarySize += 1 if oCard in self.dSelectedCounts: self.dSelectedCounts[oCard] += 1 self.iSelectedCount += 1 # The assumption is that the user is interested in all copies of # the selected cards (as it seems the most useful), so we treat # the selection of any instance of the card in the list as # selecting all of them if self.bCrypt: self.iTotal = iCryptSize else: self.iTotal = iLibrarySize
def database_updated(self): self._dNameCache = {} self._aStrippable = set() dCount = {} for oCard in AbstractCard.select(): if not is_crypt_card(oCard): continue sStrippedName = strip_group_from_name(oCard.name) dCount.setdefault(sStrippedName, [0, None]) dCount[sStrippedName][0] += 1 dCount[sStrippedName][1] = oCard # We only add the lookup if it's unique for sName, (iCnt, oCard) in dCount.items(): if iCnt == 1: if '(Advanced)' not in sName: self._aStrippable.add(oCard) else: # For advanced vampires, we also need to check if the # non-advanced version is unique sNonAdv = sName.replace(' (Advanced)', '') if sNonAdv not in dCount: # Can happen with reduced testing cardlists / playtest cards / etc. self._aStrippable.add(oCard) elif dCount[sNonAdv][0] == 1: self._aStrippable.add(oCard)
def _extract_crypt(self, dCards): """Extract the crypt cards from the list.""" dCryptStats = { 'size': 0, 'min': 0, 'minsum': 0, 'max': 0, 'maxsum': 0, 'avg': 0.0, } dVamps = {} aCaps = [] for tKey, iCount in dCards.items(): oCard = tKey[0] if is_crypt_card(oCard): dVamps[tKey] = iCount dCryptStats['size'] += iCount if oCard.cardtype[0].name == "Vampire": iCap = oCard.capacity elif oCard.cardtype[0].name == "Imbued": iCap = oCard.life dCryptStats['avg'] += iCap * iCount aCaps.extend([iCap]*iCount) if dCryptStats['size'] > 0: aCaps.sort() dCryptStats['min'] = min(aCaps) dCryptStats['max'] = max(aCaps) dCryptStats['minsum'] = sum(aCaps[:4]) dCryptStats['maxsum'] = sum(aCaps[-4:]) dCryptStats['avg'] = round(dCryptStats['avg'] / dCryptStats['size'], 2) return dVamps, dCryptStats
def print_card_details(oCard, sEncoding): """Print the details of a given card""" # pylint: disable-msg=E1101, R0912 # E1101: SQLObject can confuse pylint # R0912: Several cases to consider, so many branches if len(oCard.cardtype) == 0: print 'CardType: Unknown' else: print 'CardType: %s' % ' / '.join([oT.name for oT in oCard.cardtype]) if len(oCard.clan) > 0: print 'Clan: %s' % ' / '.join([oC.name for oC in oCard.clan]) if len(oCard.creed) > 0: print 'Creed: %s' % ' / '.join([oC.name for oC in oCard.creed]) if oCard.capacity: print 'Capacity: %d' % oCard.capacity if oCard.life: print 'Life: %d' % oCard.life if oCard.group: if oCard.group == -1: print 'Group: Any' else: print 'Group: %d' % oCard.group if not oCard.cost is None: if oCard.cost == -1: print 'Cost: X %s' % oCard.costtype else: print 'Cost: %d %s' % (oCard.cost, oCard.costtype) if len(oCard.discipline) > 0: if is_crypt_card(oCard): aDisciplines = [] aDisciplines.extend(sorted([oP.discipline.name for oP in oCard.discipline if oP.level != 'superior'])) aDisciplines.extend(sorted([oP.discipline.name.upper() for oP in oCard.discipline if oP.level == 'superior'])) sDisciplines = ' '.join(aDisciplines) else: aDisciplines = [oP.discipline.fullname for oP in oCard.discipline] sDisciplines = ' / '.join(aDisciplines) print 'Discipline: %s' % sDisciplines if len(oCard.virtue) > 0: if is_crypt_card(oCard): print 'Virtue: %s' % ' '.join([oC.name for oC in oCard.virtue]) else: print 'Virtue: %s' % ' / '.join([oC.fullname for oC in oCard.virtue]) print format_text(oCard.text.encode(sEncoding, 'xmlcharrefreplace'))
def add_new_card(self, oCard, iCnt): """response to add_new_card events""" self.__iTot += iCnt oAbsCard = IAbstractCard(oCard) if is_crypt_card(oAbsCard): self.__iCrypt += iCnt else: self.__iLibrary += iCnt self.update_numbers()
def alter_card_count(self, oCard, iChg): """respond to alter_card_count events""" self.__iTot += iChg oAbsCard = IAbstractCard(oCard) if is_crypt_card(oAbsCard): self.__iCrypt += iChg else: self.__iLibrary += iChg self.update_numbers()
def _get_selected_crypt_card(self): """Extract selected crypt card from the model.""" # Only interested in distinct cards aAbsCards = set(self._get_selected_abs_cards()) if len(aAbsCards) != 1: return None oCard = aAbsCards.pop() if not is_crypt_card(oCard): # Only want crypt cards return None return oCard
def make_unique_names(): """Create the list of unique crypt card names""" aUnique = set() for oCard in AbstractCard.select().orderBy('canonicalName'): if not is_crypt_card(oCard): continue sBaseName = strip_group_from_name(oCard.name) if sBaseName in aUnique: aUnique.add(oCard.name) else: aUnique.add(sBaseName) return aUnique
def _extract_library(self, dCards): """Extract the library cards from the list.""" iSize = 0 dLib = {} for tKey, iCount in dCards.items(): oCard, sSet = tKey if not is_crypt_card(oCard): aTypes = sorted([x.name for x in oCard.cardtype]) # Looks like it should be the right thing, but may not sTypeString = "/".join(aTypes) # We want to be able to sort over types easily, so # we add them to the keys dLib[(oCard, sTypeString, sSet)] = iCount iSize += iCount return (dLib, iSize)
def _extract_library(self, dCards): """Extract the library cards from the list.""" iSize = 0 dLib = {} for tKey, iCount in dCards.iteritems(): oCard, sSet = tKey if not is_crypt_card(oCard): aTypes = sorted([x.name for x in oCard.cardtype]) # Looks like it should be the right thing, but may not sTypeString = "/".join(aTypes) # We want to be able to sort over types easily, so # we add them to the keys dLib[(oCard, sTypeString, sSet)] = iCount iSize += iCount return (dLib, iSize)
def _get_selected_cards(self): """Extract selected cards from the selection.""" aSelectedCards = [] bCrypt = False bLibrary = False _oModel, aSelection = self.view.get_selection().get_selected_rows() for oPath in aSelection: # pylint: disable-msg=E1101 # pylint doesn't pick up adaptor's methods correctly oCard = IAbstractCard(self.model.get_card_name_from_path(oPath)) if is_crypt_card(oCard): bCrypt = True else: bLibrary = True aSelectedCards.append(oCard) return aSelectedCards, bCrypt, bLibrary
def handle_response(self, sFilename): """Actually do the export""" if sFilename is None: return oCardSet = self._get_card_set() if not oCardSet: return dDeck = json.loads(DECK_TEMPLATE) aCrypt = [] aLibrary = [] for oCard in oCardSet.cards: # Need to turn name into the JSON file version sJSONName = make_json_name(oCard.abstractCard) sAltName = make_alternative_json_name(oCard.abstractCard) if sJSONName not in self._dTTSData: # Check if it's just using the card name instead if sAltName not in self._dTTSData: do_complaint_error("Unable to find an entry for %s (%s)" % (oCard.abstractCard.name, sJSONName)) logging.warning("Unable to find an entry for %s (%s)", oCard.abstractCard.name, sJSONName) return sJSONName = sAltName if is_crypt_card(oCard.abstractCard): aCrypt.append(sJSONName) else: aLibrary.append(sJSONName) dCrypt = dDeck['ObjectStates'][0] dLibrary = dDeck['ObjectStates'][1] for sName in sorted(aCrypt): oObj = self._dTTSData[sName] dTTSCard = oObj.copy() dTTSCard['Transform'] = CRYPT_TRANSFORM dCrypt['DeckIDs'].append(dTTSCard['CardID']) dCrypt['CustomDeck'].update(dTTSCard['CustomDeck']) dCrypt['ContainedObjects'].append(dTTSCard) for sName in sorted(aLibrary): oObj = self._dTTSData[sName] dTTSCard = oObj.copy() dTTSCard['Transform'] = LIB_TRANSFORM dLibrary['DeckIDs'].append(dTTSCard['CardID']) dLibrary['CustomDeck'].update(dTTSCard['CustomDeck']) dLibrary['ContainedObjects'].append(dTTSCard) with open(sFilename, 'w') as oFile: json.dump(dDeck, oFile, indent=2)
def _check_selection(self, aSelectedCards): """Check that selection is useable.""" bCrypt = False bLibrary = False for oCard in aSelectedCards: # pylint: disable=simplifiable-if-statement # pylint misidentifies this because it misses the loop if is_crypt_card(oCard): bCrypt = True else: bLibrary = True # Check that selection doesn't mix crypt and libraries if bLibrary and bCrypt: do_complaint_error("Can't operate on selections including both" " Crypt and Library cards") return False # Store this for later queries self.bCrypt = bCrypt return True
def load(self, aCards): """Listen on load events & update counts""" # We cache type lookups to save time # The cache is short-lived to avoid needing to deal with # flushing the cache on database changes. dCache = {} self.__iCrypt = 0 self.__iLibrary = 0 self.__iTot = len(aCards) for oCard in aCards: bCrypt = dCache.get(oCard.id, None) if bCrypt is None: oAbsCard = IAbstractCard(oCard) bCrypt = is_crypt_card(oAbsCard) dCache[oCard.id] = bCrypt if bCrypt: self.__iCrypt += 1 else: self.__iLibrary += 1 self.update_numbers()
def _get_selected_cards(self): """Extract selected crypt card from the model.""" aCards = [] _oModel, aSelection = self.view.get_selection().get_selected_rows() if len(aSelection) == 0: return None # We allow multiple selections of the same card # (discipline grouping, etc). for oPath in aSelection: # pylint: disable-msg=E1101 # pylint doesn't pick up adaptor's methods correctly oCard = IAbstractCard(self.model.get_card_name_from_path(oPath)) if not is_crypt_card(oCard): # Only want crypt cards return None if oCard not in aCards: aCards.append(oCard) if len(aCards) > 1: return None return oCard
def _gen_inv(self, oHolder): """Process the card set, creating the lines as needed""" aCrypt = [] aLib = [] for oCard in oHolder.cards: oAbsCard = oCard.abstractCard sName = norm_name(oAbsCard) if is_crypt_card(oAbsCard): aCrypt.append(sName) else: aLib.append(sName) aCrypt.sort() aLib.sort() sResult = "%d\n" % len(aCrypt) for sName in aCrypt: sResult += '"%s"\n' % sName sResult += "%d\n" % len(aLib) for sName in aLib: sResult += '"%s"\n' % sName return sResult
def lackey_name(oCard, aUnique): """Escape the card name to Lackey CCG's requirements""" sName = oCard.name if oCard.level is not None: sName = sName.replace("(Advanced)", "Adv.") # Lackey uses (GX) postfix for the new vampires, but old # versions have no suffix if is_crypt_card(oCard): if sName in aUnique: sName = sName.replace('(Group ', '(G') else: sName = strip_group_from_name(sName) sName = move_articles_to_back(sName) # Lackey handles double-quotes a bit oddly, so we must as well if oCard.cardtype[0].name == 'Imbued': # Lackey only uses '' for Imbued sName = sName.replace('"', "''") else: sName = sName.replace('"', "'") # Bounce through ascii to strip accents, etc. return to_ascii(sName)
def _gen_inv(self, oHolder): """Process the card set, creating the lines as needed""" dCards = {} aSeen = set() sResult = "" for oCard in AbstractCard.select(): dCards[oCard] = 0 for oCard in oHolder.cards: oAbsCard = IAbstractCard(oCard) dCards[oAbsCard] += 1 # We sort to ensure we process multi-group cards in the right order for oCard in sorted(dCards, key=lambda x: x.name): iNum = dCards[oCard] sName = norm_name(oCard) # FIXME: It's not clear if ELDB is still being developed enough # to support the multi-group vampires, but we try this anyway if sName in aSeen and is_crypt_card(oCard): sName = f'{sName} (Group {oCard.group})' aSeen.add(sName) sResult += '"%s",%d,0,"","%s"\n' % (sName, iNum, type_of_card(oCard)) return sResult
def load(self, aCards): """Listen on load events & update counts""" # The logic is a bit complicated, but it's intended that # filtering the WW cardlist on a card set will give sensible # results. self._dAbsCounts = {} self._dExpCounts = {} self._dCardTotals = {TOTAL: 0, CRYPT: 0, LIB: 0} self._dExpTotals = {TOTAL: 0, CRYPT: 0, LIB: 0} for oCard in aCards: oAbsCard = IAbstractCard(oCard) if oAbsCard not in self._dAbsCounts: self._dAbsCounts[oAbsCard] = 1 iAbsCount = 1 else: iAbsCount = 0 if oCard.expansionID: # We don't count expansion ifno for cards with no expansion set iExpCount = 1 if oAbsCard not in self._dExpCounts: # First time we've seen this card self._dExpCounts[oAbsCard] = 1 else: # Has an expansion, and by the nature of the list, this is # a new expansion for the card we haven't seen before self._dExpCounts[oAbsCard] += 1 else: iExpCount = 0 if is_crypt_card(oAbsCard): self._dExpTotals[CRYPT] += iExpCount self._dCardTotals[CRYPT] += iAbsCount else: self._dExpTotals[LIB] += iExpCount self._dCardTotals[LIB] += iAbsCount self._dCardTotals[TOTAL] += iAbsCount self._dExpTotals[TOTAL] += iExpCount self.update_numbers()
def cardlist(sGrouping=None): """List the WW cardlist""" if request.method == 'POST': # Form submission if 'grouping' in request.form: sFilter = request.values.get('curfilter', '') return redirect(url_for('change_grouping', source='cardlist', curfilter=sFilter)) elif 'filter' in request.form: sGroup = request.values.get('curgrouping', 'Card Type') return redirect(url_for('filter', source='cardlist', grouping=sGroup)) if sGrouping is None: sGroup = 'Card Type' else: sGroup = sGrouping sFilter = request.args.get('filter', None) if sFilter and sFilter != 'None': try: oFilter = PARSER.apply(sFilter).get_filter() except Exception: oFilter = NullFilter() else: oFilter = NullFilter() dCounts = {'crypt': 0, 'library': 0} cGrouping = ALLOWED_GROUPINGS.get(sGrouping, CardTypeGrouping) for oCard in oFilter.select(AbstractCard): if is_crypt_card(oCard): dCounts['crypt'] += 1 else: dCounts['library'] += 1 aGrpData = cGrouping(oFilter.select(AbstractCard), IAbstractCard) return render_template('cardlist.html', grouped=aGrpData, groupings=sorted(ALLOWED_GROUPINGS), counts=dCounts, grouping=sGroup, curfilter=sFilter)
def _get_card_keys(self, oAbsCard): """Return 'crypt' or 'lib' as approriate""" if is_crypt_card(oAbsCard): return [CRYPT] return [LIB]
def cardsetview(sCardSetName, sGrouping=None, sExpMode='Hide'): """Show the card set with the given name and parameters""" sCorrectName = urllib.unquote(sCardSetName) try: oCS = IPhysicalCardSet(sCorrectName) except SQLObjectNotFound: oCS = None if request.method == 'POST': # Form submission if 'grouping' in request.form: sFilter = request.values.get('curfilter', '') sExpMode = request.values.get('showexp', 'Hide') return redirect(url_for('change_grouping', source='cardsetview', cardsetname=sCardSetName, showexp=sExpMode, curfilter=sFilter)) elif 'filter' in request.form: sGrouping = request.values.get('curgrouping', 'Card Type') sExpMode = request.values.get('showexp', 'Hide') return redirect(url_for('filter', source='cardsetview', cardsetname=sCardSetName, showexp=sExpMode, grouping=sGrouping)) elif 'download' in request.form: if oCS: oWriter = PhysicalCardSetWriter() oXMLFile = StringIO() oWriter.write(oXMLFile, CardSetWrapper(oCS)) oXMLFile.seek(0) # reset to start return send_file(oXMLFile, mimetype="application/octet-stream", as_attachment=True, attachment_filename=safe_filename( "%s.xml" % sCorrectName)) else: return render_template('invalid.html', type='Card Set Name', requested=sCardSetName) elif 'expansions' in request.form: sFilter = request.values.get('curfilter', '') sGrouping = request.values.get('curgrouping', 'Card Type') if request.values['expansions'] == 'Hide Expansions': return redirect(url_for('cardsetview', sCardSetName=sCardSetName, sGrouping=sGrouping, sExpMode='Hide', filter=sFilter)) else: return redirect(url_for('cardsetview', sCardSetName=sCardSetName, sGrouping=sGrouping, sExpMode='Show', filter=sFilter)) elif request.method == 'GET': if oCS: dCards = {} dCounts = {'crypt': 0, 'library': 0} cGrouping = ALLOWED_GROUPINGS.get(sGrouping, CardTypeGrouping) sFilter = request.args.get('filter', None) if sFilter and sFilter != 'None': try: oCardFilter = PARSER.apply(sFilter).get_filter() oCSFilter = PhysicalCardSetFilter(oCS.name) oFilter = FilterAndBox([oCSFilter, oCardFilter]) aResults = oFilter.select(MapPhysicalCardToPhysicalCardSet) aCards = [IPhysicalCard(x) for x in aResults] except Exception: aCards = oCS.cards else: aCards = oCS.cards for oCard in aCards: iCardCount = CardCount(oCard.abstractCard) oCount = dCards.setdefault(oCard.abstractCard, iCardCount) oCount.cnt += 1 iPrintingCount = PrintingCount(oCard.printing) oPrintingCount = oCount.printings.setdefault(oCard.printing, iPrintingCount) oPrintingCount.cnt += 1 if is_crypt_card(oCard.abstractCard): dCounts['crypt'] += 1 else: dCounts['library'] += 1 aGrouped = cGrouping(dCards.values(), lambda x: x.card) bShowExpansions = (sExpMode == 'Show') if not sGrouping: sGrouping = 'Card Type' return render_template('cardsetview.html', cardset=oCS, grouped=aGrouped, counts=dCounts, quotedname=urllib.quote(oCS.name, safe=''), curfilter=sFilter, grouping=sGrouping, showexpansions=bShowExpansions) else: return render_template('invalid.html', type='Card Set Name', requested=sCardSetName)
def type_of_card(oCard): """Return either Crypt or Library as required.""" if is_crypt_card(oCard): return "Crypt" return "Library"
def _get_card_keys(self, oAbsCard): """Listen on load events & update counts""" if is_crypt_card(oAbsCard): return [CRYPT] return [LIB]
def type_of_card(oCard): """Return either Crypt or Library as required.""" if is_crypt_card(oCard): return "Crypt" else: return "Library"
def print_card_details(oCard): """Print the details of a given card""" # pylint: disable=too-many-branches # Several cases to consider, so many branches if not oCard.cardtype: print(u'CardType: Unknown') else: sOutput = u'CardType: %s' % u' / '.join( [oT.name for oT in oCard.cardtype]) print(sOutput) if oCard.clan: sOutput = u'Clan: %s' % u' / '.join([oC.name for oC in oCard.clan]) print(sOutput) if oCard.creed: sOutput = u'Creed: %s' % u' / '.join([oC.name for oC in oCard.creed]) print(sOutput) if oCard.capacity: sOutput = u'Capacity: %d' % oCard.capacity print(sOutput) if oCard.life: sOutput = u'Life: %d' % oCard.life print(sOutput) if oCard.group: if oCard.group == -1: sOutput = u'Group: Any' else: sOutput = u'Group: %d' % oCard.group print(sOutput) if oCard.cost is not None: if oCard.cost == -1: sOutput = u'Cost: X %s' % oCard.costtype else: sOutput = u'Cost: %d %s' % (oCard.cost, oCard.costtype) print(sOutput) if oCard.keywords: aKeywords = [oK.keyword for oK in oCard.keywords] aKeywords.sort(key=keyword_sort_key) sOutput = u' '.join(aKeywords) print(u'Keywords: %s' % sOutput) if oCard.discipline: if is_crypt_card(oCard): aDisciplines = [] aDisciplines.extend(sorted([oP.discipline.name for oP in oCard.discipline if oP.level != 'superior'])) aDisciplines.extend(sorted([oP.discipline.name.upper() for oP in oCard.discipline if oP.level == 'superior'])) sDisciplines = u' '.join(aDisciplines) else: aDisciplines = [oP.discipline.fullname for oP in oCard.discipline] sDisciplines = u' / '.join(aDisciplines) sOutput = u'Discipline: %s' % sDisciplines print(sOutput) if oCard.virtue: if is_crypt_card(oCard): sOutput = u'Virtue: %s' % ' '.join( [oC.name for oC in oCard.virtue]) else: sOutput = u'Virtue: %s' % ' / '.join( [oC.fullname for oC in oCard.virtue]) print(sOutput) print(format_text(oCard.text))