def normalize(d: Deck) -> str: try: name = d.original_name name = name.lower() name = replace_space_alternatives(name) name = remove_pd(name) name = remove_hashtags(name) name = remove_brackets(name) name = strip_leading_punctuation(name) unabbreviated = expand_common_abbreviations(name) if unabbreviated != name or name in ABBREVIATIONS.values(): name = unabbreviated elif whitelisted(name): pass elif name and d.get('archetype_name') and name == d.get( 'archetype_name', '').lower(): pass else: name = add_colors_if_no_deckname(name, d.get('colors')) name = normalize_colors(name) name = add_archetype_if_just_colors(name, d.get('archetype_name')) name = remove_mono_if_not_first_word(name) name = remove_profanity(name) name = ucase_trailing_roman_numerals(name) name = titlecase.titlecase(name) return correct_case_of_color_names(name) except ValueError: raise InvalidDataException('Failed to normalize {d}'.format(d=repr(d)))
def set_colors(d: Deck) -> None: deck_colors: Set[str] = set() deck_colored_symbols: List[str] = [] for c in [entry['card'] for entry in d.maindeck + d.sideboard]: for cost in c.get('mana_cost') or (): if c.layout == 'split' or c.layout == 'aftermath': continue # They might only be using one half so ignore it. card_symbols = mana.parse(cost) card_colors = mana.colors(card_symbols) deck_colors.update(card_colors['required']) card_colored_symbols = mana.colored_symbols(card_symbols) deck_colored_symbols += card_colored_symbols['required'] d.colors = mana.order(deck_colors) d.colored_symbols = deck_colored_symbols
def vivify(decklist: DecklistType) -> Deck: validated: DecklistType = {'maindeck': {}, 'sideboard': {}} invalid_names = set() for section in ['maindeck', 'sideboard']: for name, n in decklist[section].items(): try: validated[section][oracle.valid_name(name)] = n except InvalidDataException: invalid_names.add(name) if invalid_names: raise InvalidDataException('Invalid cards: {invalid_names}'.format( invalid_names='; '.join(invalid_names))) validated_names = list(validated['maindeck'].keys()) + list( validated['sideboard'].keys()) cards = {c.name: c for c in oracle.load_cards(validated_names)} d = Deck({'maindeck': [], 'sideboard': []}) for section in ['maindeck', 'sideboard']: for name, n in validated[section].items(): d[section].append({'n': n, 'name': name, 'card': cards[name]}) return d
def prize(d: Deck) -> int: return prize_by_finish(d.get('finish') or sys.maxsize)
def load_decks(where='1 = 1', order_by=None, limit='', season_id=None) -> List[Deck]: if order_by is None: order_by = 'active_date DESC, d.finish IS NULL, d.finish' sql = """ SELECT d.id, d.name AS original_name, d.created_date, d.updated_date, SUM(CASE WHEN dm.games > IFNULL(odm.games, 0) THEN 1 ELSE 0 END) AS wins, SUM(CASE WHEN dm.games < odm.games THEN 1 ELSE 0 END) AS losses, SUM(CASE WHEN dm.games = odm.games THEN 1 ELSE 0 END) AS draws, d.finish, d.archetype_id, d.url AS source_url, d.competition_id, c.name AS competition_name, c.end_date AS competition_end_date, c.top_n AS competition_top_n, ct.name AS competition_type_name, d.identifier, {person_query} AS person, p.id AS person_id, p.banned, p.discord_id, d.decklist_hash, d.retired, s.name AS source_name, IFNULL(a.name, '') AS archetype_name, cache.normalized_name AS name, cache.colors, cache.colored_symbols, cache.legal_formats, season.id AS season_id, IFNULL(MAX(m.date), d.created_date) AS active_date FROM deck AS d LEFT JOIN person AS p ON d.person_id = p.id LEFT JOIN source AS s ON d.source_id = s.id LEFT JOIN archetype AS a ON d.archetype_id = a.id {competition_join} LEFT JOIN deck_cache AS cache ON d.id = cache.deck_id LEFT JOIN deck_match AS dm ON d.id = dm.deck_id LEFT JOIN `match` AS m ON dm.match_id = m.id LEFT JOIN deck_match AS odm ON odm.deck_id <> d.id AND dm.match_id = odm.match_id {season_join} WHERE ({where}) AND ({season_query}) GROUP BY d.id, season.id -- In theory this is not necessary as all decks are in a single season and we join on the date but MySQL cannot work that out so give it the hint it needs. ORDER BY {order_by} {limit} """.format(person_query=query.person_query(), competition_join=query.competition_join(), where=where, order_by=order_by, limit=limit, season_query=query.season_query(season_id), season_join=query.season_join()) db().execute('SET group_concat_max_len=100000') rows = db().execute(sql) decks = [] for row in rows: d = Deck(row) d.maindeck = [] d.sideboard = [] d.competition_top_n = Top(d.competition_top_n or 0) d.colored_symbols = json.loads(d.colored_symbols or '[]') d.colors = json.loads(d.colors or '[]') d.legal_formats = set(json.loads(d.legal_formats or '[]')) d.active_date = dtutil.ts2dt(d.active_date) d.created_date = dtutil.ts2dt(d.created_date) d.updated_date = dtutil.ts2dt(d.updated_date) if d.competition_end_date: d.competition_end_date = dtutil.ts2dt(d.competition_end_date) d.can_draw = 'Divine Intervention' in [card.name for card in d.all_cards()] decks.append(d) load_cards(decks) load_competitive_stats(decks) return decks
def set_legality(d: Deck) -> None: d.legal_formats = legality.legal_formats(d)
def deserialize_deck(sdeck: Container) -> Deck: deck = Deck(sdeck) deck.active_date = dtutil.ts2dt(deck.active_date) deck.created_date = dtutil.ts2dt(deck.created_date) deck.updated_date = dtutil.ts2dt(deck.updated_date) if deck.competition_end_date is not None: deck.competition_end_date = dtutil.ts2dt(deck.competition_end_date) deck.wins = int(deck.wins) deck.losses = int(deck.losses) deck.draws = int(deck.draws) if deck.get('omw') is not None: deck.omw = float(deck.omw) cards_by_name = oracle.cards_by_name() for entry in deck.maindeck: entry['card'] = cards_by_name[entry['card']['name']] for entry in deck.sideboard: entry['card'] = cards_by_name[entry['card']['name']] return deck
def set_stars_and_top8(d: Deck) -> None: if d.finish == 1 and d.competition_top_n >= 1: d.top8_safe = '<span title="Winner">①</span>' d.stars_safe = '★★★' elif d.finish == 2 and d.competition_top_n >= 2: d.top8_safe = '<span title="Losing Finalist">②</span>' d.stars_safe = '★★' elif d.finish == 3 and d.competition_top_n >= 3: d.top8_safe = '<span title="Losing Semifinalist">④</span>' d.stars_safe = '★★' elif d.finish == 5 and d.competition_top_n >= 5: d.top8_safe = '<span title="Losing Quarterfinalist">⑧</span>' d.stars_safe = '★' else: d.top8_safe = '' if d.get('wins') is not None and d.get('losses') is not None: if d.wins - 5 >= d.losses: d.stars_safe = '★★' elif d.wins - 3 >= d.losses: d.stars_safe = '★' else: d.stars_safe = '' else: d.stars_safe = '' if len(d.stars_safe) > 0: d.stars_safe = '<span class="stars" title="Success Rating">{stars}</span>'.format(stars=d.stars_safe)
def prepare_deck(self, d: Deck) -> None: set_stars_and_top8(d) if d.get('colors') is not None: d.colors_safe = colors_html(d.colors, d.colored_symbols) d.person_url = '/people/{id}/'.format(id=d.person_id) d.date_sort = dtutil.dt2ts(d.active_date) d.display_date = dtutil.display_date(d.active_date) d.show_record = d.wins or d.losses or d.draws if d.competition_id: d.competition_url = '/competitions/{id}/'.format(id=d.competition_id) d.url = '/decks/{id}/'.format(id=d.id) d.export_url = '/export/{id}/'.format(id=d.id) d.cmc_chart_url = '/charts/cmc/{id}-cmc.png'.format(id=d.id) if d.is_in_current_run(): d.active_safe = '<span class="active" title="Active in the current league">⊕</span>' d.stars_safe = '{active} {stars}'.format(active=d.active_safe, stars=d.stars_safe).strip() d.source_sort = '1' d.source_is_external = False if d.source_name == 'League' else True d.comp_row_len = len('{comp_name} (Piloted by {person}'.format(comp_name=d.competition_name, person=d.person)) if d.get('archetype_id', None): d.archetype_url = '/archetypes/{id}/'.format(id=d.archetype_id) if d.get('omw') is not None: d.omw = str(int(d.omw)) + '%' else: d.omw = '' d.has_legal_format = len(d.legal_formats) > 0 d.pd_legal = 'Penny Dreadful' in d.legal_formats d.legal_icons = '' sets = rotation.SEASONS if 'Penny Dreadful' in d.legal_formats: icon = rotation.current_season_code().lower() n = sets.index(icon.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{code} ss-rare ss-grad">S{n}</i></a>'.format(url='/seasons/{id}/'.format(id=n), code=icon, n=n) past_pd_formats = [fmt.replace('Penny Dreadful ', '') for fmt in d.legal_formats if 'Penny Dreadful ' in fmt] past_pd_formats.sort(key=lambda code: -sets.index(code)) for code in past_pd_formats: n = sets.index(code.upper()) + 1 d.legal_icons += '<a href="{url}"><i class="ss ss-{set} ss-common ss-grad">S{n}</i></a>'.format(url='/seasons/{id}/'.format(id=n), set=code.lower(), n=n) if 'Commander' in d.legal_formats: # I think C16 looks the nicest. d.legal_icons += '<i class="ss ss-c16 ss-uncommon ss-grad">CMDR</i>' if session.get('admin') or not d.is_in_current_run(): d.decklist = str(d).replace('\n', '<br>') else: d.decklist = '' total, num_cards = 0, 0 for c in d.maindeck: if 'Land' not in c.card.type: num_cards += c['n'] total += c['n'] * c.card.cmc d.average_cmc = round(total / max(1, num_cards), 2)
def test_legal_formats() -> None: swamp = oracle.load_card('Swamp') think_twice = oracle.load_card('Think Twice') fork = oracle.load_card('Fork') d = Deck({'id': 0}) d.maindeck = [{'n': 59, 'card': swamp}] d.sideboard = [] assert len(d.all_cards()) == 59 formats = legality.legal_formats(d) assert len(formats) == 0 d.maindeck = [{'n': 60, 'card': swamp}] formats = legality.legal_formats(d) assert 'Penny Dreadful' in formats assert 'Legacy' in formats assert 'Penny Dreadful EMN' in formats formats = legality.legal_formats(d, {'Penny Dreadful'}) assert len(formats) == 1 assert 'Penny Dreadful' in formats assert 'Legacy' not in formats d.maindeck = [{'n': 55, 'card': swamp}, {'n': 5, 'card': think_twice}] formats = legality.legal_formats(d) assert len(d.all_cards()) == 60 assert len(legality.legal_formats(d)) == 0 d.maindeck = [{'n': 56, 'card': swamp}, {'n': 4, 'card': think_twice}] formats = legality.legal_formats(d) assert 'Legacy' in formats assert 'Modern' in formats d.sideboard = [{'n': 15, 'card': swamp}, {'n': 1, 'card': think_twice}] formats = legality.legal_formats(d) assert len(legality.legal_formats(d)) == 0 d.maindeck = [{'n': 56, 'card': swamp}, {'n': 4, 'card': fork}] d.sideboard = [{'n': 15, 'card': swamp}] formats = legality.legal_formats(d) assert 'Legacy' in formats assert 'Modern' not in formats d.maindeck = [{'n': 60, 'card': swamp}] d.sideboard = [{'n': 15, 'card': swamp}] formats = legality.legal_formats(d) assert 'Standard' in formats assert 'Modern' in formats assert 'Legacy' in formats assert 'Vintage' in formats assert 'Penny Dreadful' in formats assert 'Penny Dreadful EMN' in formats