Exemple #1
0
def single_card_text_internal(client: Client, requested_card: Card,
                              disable_emoji: bool) -> str:
    mana = emoji.replace_emoji('|'.join(requested_card.mana_cost or []),
                               client)
    mana = mana.replace('|', ' // ')
    legal = ' — ' + emoji.info_emoji(requested_card, verbose=True)
    if disable_emoji:
        legal = ''
    if requested_card.get('mode', None) == '$':
        text = '{name} {legal} — {price}'.format(
            name=requested_card.name,
            price=card_price.card_price_string(requested_card),
            legal=legal)
    else:
        text = '{name} {mana} — {type}{legal}'.format(
            name=requested_card.name,
            mana=mana,
            type=requested_card.type_line,
            legal=legal)
    if requested_card.bugs:
        for bug in requested_card.bugs:
            text += '\n:beetle:{rank} bug: {bug}'.format(
                bug=bug['description'], rank=bug['classification'])
            if bug['last_confirmed'] < (dtutil.now() -
                                        datetime.timedelta(days=60)):
                time_since_confirmed = (dtutil.now() -
                                        bug['last_confirmed']).total_seconds()
                text += ' (Last confirmed {time} ago.)'.format(
                    time=dtutil.display_time(time_since_confirmed, 1))
    return text
def read_rotation_files() -> Tuple[int, int, List[Card]]:
    runs_str = redis.get_str('decksite:rotation:summary:runs')
    runs_percent_str = redis.get_str('decksite:rotation:summary:runs_percent')
    cards = redis.get_list('decksite:rotation:summary:cards')
    if runs_str is not None and runs_percent_str is not None and cards is not None:
        return int(runs_str), int(runs_percent_str), [
            Card(c, predetermined_values=True) for c in cards
        ]
    lines = []
    fs = files()
    if len(fs) == 0:
        if not os.path.isdir(configuration.get_str('legality_dir')):
            raise DoesNotExistException(
                'Invalid configuration.  Could not find legality_dir.')
        return (0, 0, [])
    latest_list = open(fs[-1], 'r').read().splitlines()
    for filename in fs:
        for line in get_file_contents(filename):
            line = text.sanitize(line)
            lines.append(line.strip())
    scores = Counter(lines).most_common()
    runs = scores[0][1]
    runs_percent = round(round(runs / TOTAL_RUNS, 2) * 100)
    cs = oracle.cards_by_name()
    cards = []
    for name, hits in scores:
        c = process_score(name, hits, cs, runs, latest_list)
        if c is not None:
            cards.append(c)
    redis.store('decksite:rotation:summary:runs', runs, ex=604800)
    redis.store('decksite:rotation:summary:runs_percent',
                runs_percent,
                ex=604800)
    redis.store('decksite:rotation:summary:cards', cards, ex=604800)
    return (runs, runs_percent, cards)
Exemple #3
0
def read_rotation_files() -> Tuple[int, int, List[Card]]:
    runs_str = redis.get_str('decksite:rotation:summary:runs')
    runs_percent_str = redis.get_str('decksite:rotation:summary:runs_percent')
    cards = redis.get_list('decksite:rotation:summary:cards')
    if runs_str is not None and runs_percent_str is not None and cards is not None:
        return int(runs_str), int(runs_percent_str), [Card(c, predetermined_values=True) for c in cards]
    return rotation_redis_store()
def if_todays_prices(out: bool = True) -> List[Card]:
    current_format = multiverse.get_format_id('Penny Dreadful')
    if out:
        not_clause = ''
        compare = '<'
    else:
        not_clause = 'NOT'
        compare = '>='

    where = """
        c.id {not_clause} IN
            (SELECT card_id FROM card_legality
                WHERE format_id = {format})
        AND c.name in (SELECT name FROM `{prices_database}`.cache WHERE week {compare} 0.5)
        AND c.layout IN ({layouts})
    """.format(not_clause=not_clause,
               format=current_format,
               prices_database=configuration.get('prices_database'),
               compare=compare,
               layouts=', '.join([
                   sqlescape(layout)
                   for layout in multiverse.playable_layouts()
               ]))

    rs = db().select(multiverse.cached_base_query(where=where))
    cards = [Card(r) for r in rs]
    return sorted(cards, key=lambda card: card['name'])
def load_cards(names: Iterable[str] = None,
               where: Optional[str] = None) -> List[Card]:
    if names == []:
        return []
    if names:
        setnames = set(names)
    else:
        setnames = set()
    if setnames:
        names_clause = 'c.name IN ({names})'.format(names=', '.join(
            sqlescape(name) for name in setnames))
    else:
        names_clause = '(1 = 1)'
    if where is None:
        where = '(1 = 1)'
    sql = multiverse.cached_base_query('({where} AND {names})'.format(
        where=where, names=names_clause))
    rs = db().select(sql)
    if setnames and len(setnames) != len(rs):
        missing = setnames.symmetric_difference([r['name'] for r in rs])
        raise TooFewItemsException(
            'Expected `{namelen}` and got `{rslen}` with `{names}`.  missing=`{missing}`'
            .format(namelen=len(setnames),
                    rslen=len(rs),
                    names=setnames,
                    missing=missing))
    return [Card(r) for r in rs]
async def download_scryfall_card_image(c: Card,
                                       filepath: str,
                                       version: str = '') -> bool:
    try:
        if c.is_double_sided():
            paths = [
                re.sub('.jpg$', '.a.jpg', filepath),
                re.sub('.jpg$', '.b.jpg', filepath)
            ]
            await fetch_tools.store_async(scryfall_image(c, version=version),
                                          paths[0])
            if c.layout == 'transform' or c.layout == 'modal_dfc':
                await fetch_tools.store_async(
                    scryfall_image(c, version=version, face='back'), paths[1])
            if c.layout == 'meld':
                await fetch_tools.store_async(
                    scryfall_image(c, version=version, face='meld'), paths[1])
            if (fetch_tools.acceptable_file(paths[0])
                    and fetch_tools.acceptable_file(paths[1])):
                save_composite_image(paths, filepath)
        else:
            await fetch_tools.store_async(scryfall_image(c, version=version),
                                          filepath)
    except FetchException as e:
        print('Error: {e}'.format(e=e))
    return fetch_tools.acceptable_file(filepath)
Exemple #7
0
def search(query: str) -> List[Card]:
    where = parse(tokenize(query))
    sql = """{base_query}
        ORDER BY pd_legal DESC, name
    """.format(base_query=multiverse.cached_base_query(where))
    rs = db().select(sql)
    return [Card(r) for r in rs]
def deck_sort(c: Card) -> str:
    s = ''
    if c.is_creature():
        s += 'A'
    elif c.is_land():
        s += 'C'
    else:
        s += 'B'
    m = 'A'
    for cost in c.get('mana_cost') or ():
        if mana.has_x(cost):
            m = 'X'
    s += m
    s += str(c.cmc).zfill(10)
    s += c.name
    return s
Exemple #9
0
def test_noimageavailable() -> None:
    c = Card({
        'name': "Barry's Land",
        'id': 0,
        'multiverseid': 0,
        'names': "Barry's Land",
        'layout': 'normal'
    })
    assert image_fetcher.download_image([c]) is None
def query_diff_formats(f1: int, f2: int) -> Sequence[Card]:
    where = """
    c.id IN
        (SELECT card_id FROM card_legality
            WHERE format_id = {format1})
    AND c.id NOT IN
        (SELECT card_id FROM card_legality WHERE format_id = {format2})
    """.format(format1=f1, format2=f2)

    rs = db().select(multiverse.cached_base_query(where=where))
    out = [Card(r) for r in rs]
    return sorted(out, key=lambda card: card['name'])
async def add_cards_and_update_async(printings: List[CardDescription]) -> None:
    if not printings:
        return
    ids = await multiverse.insert_cards_async(printings)
    multiverse.add_to_cache(ids)
    cs = [
        Card(r) for r in db().select(
            multiverse.cached_base_query('c.id IN (' +
                                         ','.join([str(id)
                                                   for id in ids]) + ')'))
    ]
    whoosh_write.reindex_specific_cards(cs)
    for c in cs:
        CARDS_BY_NAME[c.name] = c
def scryfall_image(c: Card, version: str = '', face: str = None) -> str:
    if face == 'meld':
        name = c.names[1]
    elif ' // ' in c.name:
        name = c.name.replace(' // ', '/')
    else:
        name = c.name
    p = oracle.get_printing(c, c.get('preferred_printing'))
    if p is not None:
        u = f'https://api.scryfall.com/cards/{p.set_code}/{p.number}?format=image'
    else:
        u = 'https://api.scryfall.com/cards/named?exact={c}&format=image'.format(c=escape(name))
    if version:
        u += '&version={v}'.format(v=escape(version))
    if face and face != 'meld':
        u += '&face={f}'.format(f=escape(face))
    return u
def test_hits_needed_score() -> None:
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Undecided',
            'hits': 33
        })) == 135
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Undecided',
            'hits': 1
        })) == 167
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Legal',
            'hits': 84
        })) == 252
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Legal',
            'hits': 130
        })) == 298
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Legal',
            'hits': 168
        })) == 336
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Not Legal',
            'hits': 83
        })) == 421
    assert rotation.hits_needed_score(
        Card({
            'name': 'Test',
            'status': 'Not Legal',
            'hits': 1
        })) == 503
Exemple #14
0
 def prepare_card_urls(self, c: Card) -> None:
     c.url = self.url_for_card(c)
     c.img_url = self.url_for_image(c.name)
Exemple #15
0
    def prepare_card(self, c: Card) -> None:
        self.prepare_card_urls(c)
        c.card_img_class = 'two-faces' if c.layout in ['transform', 'meld'
                                                       ] else ''
        c.pd_legal = c.legalities.get(
            'Penny Dreadful',
            False) and c.legalities['Penny Dreadful'] != 'Banned'
        c.legal_formats = {k for k, v in c.legalities.items() if v != 'Banned'}
        c.non_pd_legal_formats = {
            k
            for k, v in c.legalities.items()
            if 'Penny Dreadful' not in k and v != 'Banned'
        }
        c.has_legal_format = len(c.legal_formats) > 0
        prepare.set_legal_icons(c)
        if c.get('num_decks') is not None:
            c.show_record = c.get('wins') or c.get('losses') or c.get('draws')

        c.has_decks = len(c.get('decks', [])) > 0
        if not c.has_decks:
            c.has_most_common_cards = False
            return

        counter = Counter()  # type: ignore
        for d in c.get('decks', []):
            for c2 in d.maindeck:
                if not c2.card.type_line.startswith(
                        'Basic Land') and not c2['name'] == c.name:
                    counter[c2['name']] += c2['n']
        most_common_cards = counter.most_common(NUM_MOST_COMMON_CARDS_TO_LIST)
        c.most_common_cards = []
        cs = oracle.cards_by_name()
        for v in most_common_cards:
            self.prepare_card(cs[v[0]])
            c.most_common_cards.append(cs[v[0]])
        c.has_most_common_cards = len(c.most_common_cards) > 0
def bugged_cards() -> List[Card]:
    sql = multiverse.cached_base_query('bugs IS NOT NULL')
    rs = db().select(sql)
    return [Card(r) for r in rs]
Exemple #17
0
    def prepare_card(self, c: Card) -> None:
        try:
            tournament_only = self.tournament_only #type: ignore
            # mypy complains we haven't declared tournament_only, but that's fine since we're
            # catching the error if it isn't defined by a subclass
        except AttributeError:
            tournament_only = False

        if tournament_only:
            c.url = url_for('.card_tournament', name=c.name)
        else:
            c.url = url_for('.card', name=c.name)

        c.img_url = url_for('image', c=c.name)
        c.card_img_class = 'two-faces' if c.layout in ['transform', 'meld'] else ''
        c.pd_legal = c.legalities.get('Penny Dreadful', False) and c.legalities['Penny Dreadful'] != 'Banned'
        c.legal_formats = {k for k, v in c.legalities.items() if v != 'Banned'}
        c.has_legal_format = len(c.legal_formats) > 0
        if c.get('num_decks') is not None:
            c.show_record = c.get('wins') or c.get('losses') or c.get('draws')
        c.has_decks = len(c.get('decks', [])) > 0
        counter = Counter() # type: ignore
        for d in c.get('decks', []):
            for c2 in d.maindeck:
                if not c2.card.type_line.startswith('Basic Land') and not c2['name'] == c.name:
                    counter[c2['name']] += c2['n']
        most_common_cards = counter.most_common(NUM_MOST_COMMON_CARDS_TO_LIST)
        c.most_common_cards = []
        cs = oracle.cards_by_name()
        for v in most_common_cards:
            self.prepare_card(cs[v[0]])
            c.most_common_cards.append(cs[v[0]])
        c.has_most_common_cards = len(c.most_common_cards) > 0
def get_all_cards() -> List[Card]:
    rs = db().select(cached_base_query())
    return [Card(r) for r in rs]
def prepare_card_urls(c: Card, tournament_only: bool = False) -> None:
    c.url = url_for_card(c, tournament_only)
    c.img_url = url_for_image(c.name)
def test_rotation_sort_func() -> None:
    # First, set up the test data we are going to use. An imaginary scenario 120 runs in to the rotation process.
    num_runs_so_far = 120
    remaining_runs = rotation.TOTAL_RUNS - num_runs_so_far
    target = rotation.TOTAL_RUNS / 2
    cs = [
        Card({
            'name': 'Sylvan Library',
            'hit_in_last_run': True,
            'hits': 30
        }),
        Card({
            'name': 'Black Lotus',
            'hit_in_last_run': True,
            'hits': 10
        }),
        Card({
            'name': 'Murderous Cut',
            'hit_in_last_run': False,
            'hits': 90
        }),
        Card({
            'name': 'Wrenn and Six',
            'hit_in_last_run': True,
            'hits': 90
        }),
        Card({
            'name': 'Abandon Hope',
            'hit_in_last_run': True,
            'hits': 90
        }),
        Card({
            'name': 'Channel',
            'hit_in_last_run': False,
            'hits': 20
        }),
        Card({
            'name': 'Brain in a Jar',
            'hit_in_last_run': True,
            'hits': 60
        }),
        Card({
            'name': 'Firebolt',
            'hit_in_last_run': False,
            'hits': 40
        }),
        Card({
            'name': 'Charming Prince',
            'hit_in_last_run': True,
            'hits': 80
        }),
        Card({
            'name': 'Life from the Loam',
            'hit_in_last_run': True,
            'hits': 50
        }),
        Card({
            'name': 'Fury Charm',
            'hit_in_last_run': False,
            'hits': 70
        }),
        Card({
            'name': 'Dark Confidant',
            'hit_in_last_run': True,
            'hits': 100
        }),
        Card({
            'name': 'Colossal Dreadmaw',
            'hit_in_last_run': True,
            'hits': 1
        }),
    ]
    for c in cs:
        c.hits_needed = max(target - c.hits, 0)
        c.percent_needed = str(
            round(round(c.hits_needed / remaining_runs, 2) * 100))
        if c.hits_needed == 0:
            c.status = 'Legal'
        elif c.hits_needed < remaining_runs:
            c.status = 'Undecided'
        else:
            c.status = 'Not Legal'

    rotation.rotation_sort(cs, 'hitInLastRun', 'DESC')
    # Hit in last run and still possible to make it, but not yet confirmed, most hits first.
    assert cs[0].name == 'Charming Prince'
    assert cs[1].name == 'Brain in a Jar'
    assert cs[2].name == 'Life from the Loam'
    # Followed by hit in last run and confirmed, least hits first.
    assert cs[3].name == 'Abandon Hope'
    assert cs[4].name == 'Wrenn and Six'
    assert cs[5].name == 'Dark Confidant'
    # Hit in last run but eliminated, most hits first.
    assert cs[6].name == 'Sylvan Library'
    assert cs[7].name == 'Black Lotus'
    assert cs[8].name == 'Colossal Dreadmaw'
    # No hit in last run but still possible to make it, most hits first.
    assert cs[9].name == 'Fury Charm'
    assert cs[10].name == 'Firebolt'
    # No hit in last run but confirmed, least hits first
    assert cs[11].name == 'Murderous Cut'
    # No hit in last run, confirmed out, most hits first.
    assert cs[12].name == 'Channel'

    rotation.rotation_sort(cs, 'hits', 'DESC')
    assert cs[0].name == 'Dark Confidant'
    assert cs[1].name == 'Abandon Hope'
    assert cs[2].name == 'Murderous Cut'
    assert cs[3].name == 'Wrenn and Six'
    assert cs[4].name == 'Charming Prince'
    assert cs[5].name == 'Fury Charm'
    assert cs[6].name == 'Brain in a Jar'
    assert cs[7].name == 'Life from the Loam'
    assert cs[8].name == 'Firebolt'
    assert cs[9].name == 'Sylvan Library'
    assert cs[10].name == 'Channel'
    assert cs[11].name == 'Black Lotus'
    assert cs[12].name == 'Colossal Dreadmaw'

    rotation.rotation_sort(cs, 'hitsNeeded', 'ASC')
    # First we expect the cards that are nearly there, closest first.
    assert cs[0].name == 'Charming Prince'  # 80 / 120
    assert cs[1].name == 'Fury Charm'  # 70 / 120
    assert cs[2].name == 'Brain in a Jar'  # …
    assert cs[3].name == 'Life from the Loam'
    assert cs[4].name == 'Firebolt'
    # Then cards that have made it, least hits first, to maximize the chance of showing cards that recently made it in near the top.
    assert cs[5].name == 'Abandon Hope'
    assert cs[6].name == 'Murderous Cut'
    assert cs[7].name == 'Wrenn and Six'
    assert cs[8].name == 'Dark Confidant'
    # Then cards that have been eliminated, most hits first.
    assert cs[9].name == 'Sylvan Library'
    assert cs[10].name == 'Channel'
    assert cs[11].name == 'Black Lotus'
    assert cs[12].name == 'Colossal Dreadmaw'

    rotation.rotation_sort(cs, 'name', 'ASC')
    assert cs[0].name == 'Abandon Hope'
    assert cs[1].name == 'Black Lotus'
    assert cs[2].name == 'Brain in a Jar'
    assert cs[3].name == 'Channel'
    assert cs[4].name == 'Charming Prince'
    assert cs[5].name == 'Colossal Dreadmaw'
    assert cs[6].name == 'Dark Confidant'
    assert cs[7].name == 'Firebolt'
    assert cs[8].name == 'Fury Charm'
    assert cs[9].name == 'Life from the Loam'
    assert cs[10].name == 'Murderous Cut'
    assert cs[11].name == 'Sylvan Library'
    assert cs[12].name == 'Wrenn and Six'