Source code for pyartifact.deck

"""
Deck wrapper with an overview class.
"""
from collections import defaultdict
from typing import List, Union, Dict

from ._context import ctx
from .deck_encoding.decode import decode_deck_string
from .deck_encoding.encode import encode_deck
from .exceptions import PyArtifactException
from .filtering import CardFilter
from .sets_and_cards import Hero, CardTypesInstanced, Item, Spell, Improvement, Creep
from .types.deck_types import HeroDeckType, CardDeckType, DeckContents


[docs]class Deck: """ Class for holding information about a deck. To use this, all sets must be loaded. As this library isn't supposed to be a fully fledged deck constructor, accessing deck_contents directly is recommended. Compared to the out-of-the-box data encoded in the deck string, this object enriches them with an additional key `instance` that holds an instance of the deck from :py:class:`pyartifact.Cards` As of now, the deck object does no validations, the rules to follow are: * Some cards have `includes`, that are automatically added to the deck and they shouldn't be in the cards portion of deck contents. * Abilities and Passive abilities aren't able to be included in a deck, as they come with another cards and are more of a traits than cards. * Heroes have their own part of deck contents, don't put them into the cards section **Deck code versions** Deck codes currently have two versions. We are able to load both, but only dump to version 2. +---------+--------+-------+---------------+ | Version | Heroes | Cards | Deck name | +=========+========+=======+===============+ | 1 | yes | yes | no | +---------+--------+-------+---------------+ | 2 | yes | yes | 63 characters | +---------+--------+-------+---------------+ When loading version 1, this library will still provide a name, which will be an empty string. """ def __init__(self, deck_contents: DeckContents) -> None: """ :param deck_contents: dict of deck contents """ if sorted(ctx.loaded_sets) != sorted(ctx.all_sets): raise PyArtifactException("To instantiate a Deck, all sets musts be loaded.") self.deck_contents = deck_contents self._enrich() def __repr__(self) -> str: return f'<Artifact deck: {self.deck_contents}>'
[docs] @classmethod def from_code(cls, deck_code: str) -> 'Deck': """ Constructs the deck object from a deck code string. `Deck.from_code(deck_code)` does the exact same thing as `Deck.loads(deck_code)` :param deck_code: Version 1 or 2 deck code """ return cls(deck_contents=decode_deck_string(deck_code))
loads = from_code
[docs] @classmethod def new(cls, name: str, heroes: List[HeroDeckType], cards: List[CardDeckType]): """ Constructs the deck object from the insides of deck contents, for when you can't be bothered to make a dict. :param name: Name of the deck :param heroes: List of dictionaries holding information about the heroes :param cards: List of dictionaries holding information about the cards """ deck_contents = DeckContents(name=name, heroes=heroes, cards=cards) return cls(deck_contents)
@property def deck_code(self) -> str: """Returns the latest version of the deck code.""" return encode_deck(self.deck_contents) __str__ = deck_code
[docs] def dumps(self) -> str: """ Returns the latest version of the deck code, same as deck_code property. For people who are used to json/yaml api :). """ return self.deck_code
@property def name(self) -> str: """Name of the deck or an empty string of there is no name.""" return self.deck_contents.get('name', '') @name.setter def name(self, new_name: str) -> None: """Sets a new name of the deck""" self.deck_contents['name'] = new_name @property def cards(self) -> List[CardDeckType]: """List of dictionaries with all the cards and their information""" return self.deck_contents['cards'] @property def heroes(self) -> List[HeroDeckType]: """List of dictionaries with all the heroes and their information""" return self.deck_contents['heroes'] @property def overview(self) -> 'Overview': """ Returns an overview of the deck, used for quick glances at it's contents. The overview holds an instance of the deck and will change with any changes made to the deck. """ return Overview(self)
[docs] def expand_cards(self, hero_includes: bool = True) -> List[CardTypesInstanced]: """ Expands all the cards in the deck into a list of their instances, once for each count. :param hero_includes: Whether also expand auto-includes coming with the heroes """ expanded: List[CardTypesInstanced] = [] for card in self.cards: for _ in range(card['count']): expanded.append(card['instance']) if hero_includes: for hero in self.heroes: expanded += hero['instance'].includes return expanded
def _enrich(self) -> None: """Adds instance key to all heroes and cards.""" enriched_heroes: List[HeroDeckType] = [] for hero in self.heroes: hero_instance = ctx.cards_by_id[hero['id']] enriched_heroes.append(HeroDeckType(instance=hero_instance, turn=hero['turn'], id=hero['id'])) enriched_cards: List[CardDeckType] = [] for card in self.cards: card_instance = ctx.cards_by_id[card['id']] enriched_cards.append(CardDeckType(instance=card_instance, count=card['count'], id=card['id'])) enriched_deck = DeckContents(name=self.name, heroes=enriched_heroes, cards=enriched_cards) self.deck_contents = enriched_deck
[docs] @staticmethod def new_hero_dict(hero: Hero, turn: int) -> HeroDeckType: """ Construction of a hero dict compatible with the encoding process and Deck object internals :param hero: Instance of a hero card :param turn: Which turn the hero will be deployed """ return HeroDeckType(instance=hero, turn=turn, id=hero.id)
[docs] @staticmethod def new_card_dict(card: CardTypesInstanced, count: int) -> CardDeckType: """ Construction of a card dict compatible with the encoding process and Deck object internals :param card: Instance of a card :param count: How many copies are in the deck """ return CardDeckType(instance=card, count=count, id=card.id)
[docs]class Overview: """A helper object for quick glances over the deck contents.""" def __init__(self, deck: Deck) -> None: """ :param deck: An instantiated deck object """ self.deck = deck
[docs] def items(self, sub_type: str = None, return_filter: bool = False) -> Union[List[Item], CardFilter]: """ All the items in the deck. :param sub_type: Whether to list only certain sub type of the items :param return_filter: Whether to return a :py:class:`pyartifact.CardFilter` object or just a List of cards """ items = CardFilter(cards=self.deck.expand_cards(hero_includes=False)).type(Item) if sub_type is not None: items = items.sub_type(sub_type) if not return_filter: return items.cards return items
[docs] def spells(self, return_filter: bool = False) -> Union[List[Spell], CardFilter]: """ All the spells in the deck. :param return_filter: Whether to return a :py:class:`pyartifact.CardFilter` object or just a List of cards """ spells = CardFilter(cards=self.deck.expand_cards()).type(Spell) if not return_filter: return spells.cards return spells
[docs] def heroes(self, return_filter: bool = False) -> Union[List[Hero], CardFilter]: """ All the heroes in the deck, sorted by their turns of deployment. :param return_filter: Whether to return a :py:class:`pyartifact.CardFilter` object or just a List of cards """ heroes = [h['instance'] for h in sorted(self.deck.heroes, key=lambda x: x['turn'])] if return_filter: return CardFilter(cards=heroes) return heroes
[docs] def improvements(self, return_filter: bool = False) -> Union[List[Improvement], CardFilter]: """ All the improvements in the deck. :param return_filter: Whether to return a :py:class:`pyartifact.CardFilter` object or just a List of cards """ improvements = CardFilter(cards=self.deck.expand_cards()).type(Improvement) if not return_filter: return improvements.cards return improvements
[docs] def creeps(self, return_filter: bool = False) -> Union[List[Creep], CardFilter]: """ All the creeps in the deck. :param return_filter: Whether to return a :py:class:`pyartifact.CardFilter` object or just a List of cards """ creeps = CardFilter(cards=self.deck.expand_cards()).type(Creep) if not return_filter: return creeps.cards return creeps
[docs] def items_per_subtype(self) -> Dict[str, List[Item]]: """ A more detailed overview of items on the deck in a form of defaultdict(list) where each key is a sub type and it's value is a list of those items. """ result: Dict[str, List[Item]] = defaultdict(list) for item in self.items(): result[item.sub_type].append(item) return result