shawkinsl/mtga-tracker

View on GitHub
app/models/set.py

Summary

Maintainability
B
6 hrs
Test Coverage
import re
import app.models.card as mcard
from app.models.card import GameCard


class Set(object):

    def __init__(self, set_name, cards=None):
        self.set_name = set_name
        self.mtga_ids = set()
        self.cards_in_set = list()

        if cards:
            for card in cards:
                self.add_card(card)

    def add_card(self, card):
        if card.mtga_id in self.mtga_ids:
            raise ValueError("This set already has MTGA ID {}".format(card.mtga_id))
        self.cards_in_set.append(card)
        self.mtga_ids.add(card.mtga_id)


class Pool(object):

    def __init__(self, pool_name, cards=None, side=None):
        self.pool_name = pool_name
        if cards is None:
            cards = []
        if side is None:
            side = []
        self.cards = cards
        self.side = side

    def __repr__(self):
        return "<Pool {}: {} cards>".format(self.pool_name, len(self.cards))

    def __hash__(self):
        _hash = 0
        for idx, element in enumerate(self.cards):
            hashable = element.name + str(idx)
            _hash += hash(hashable)
        return _hash

    @property
    def total_count(self):
        return len(self.cards)

    def count_cards_owned_by(self, seat):
        total = 0
        for card in self.cards:
            if card.owner_seat_id == seat:
                total += 1
        return total

    def count(self, mtga_id):
        return len([card for card in self.cards if card.mtga_id == mtga_id])

    def group_cards(self):
        grouped = {}
        for card in self.cards:
            if card not in grouped:
                grouped[card] = 0
            grouped[card] += 1
        return grouped

    def transfer_all_to(self, other_pool):
        for card in self.cards:
            other_pool.cards.append(card)
        while self.cards:
            self.cards.pop()

    def transfer_cards_to(self, cards, other_pool):
        # TODO: make this "session safe" (ie if we error on the third card, we should not have transferred the first 2)
        for card in cards:
            self.transfer_card_to(card, other_pool)

    def transfer_card_to(self, card, other_pool):
        # TODO: make this atomic, somehow?
        res = card
        if not isinstance(card, mcard.Card):  # allow you to pass in cards or ids or searches
            res = self.find_one(card)
        self.cards.remove(res)
        other_pool.cards.append(res)

    @classmethod
    def from_sets(cls, pool_name, sets):
        cards = []
        for set in sets:
            for card in set.cards_in_set:
                cards.append(card)
        return Pool(pool_name, cards)

    def find_one(self, id_or_keyword):
        result = set(self.search(id_or_keyword))
        if len(result) < 1:
            raise ValueError("Pool does not contain {}".format(id_or_keyword))
        elif len(result) > 1:
            raise ValueError("Pool search '{}' not narrow enough, got: {}".format(id_or_keyword, result))
        return result.pop()

    def search(self, id_or_keyword, direct_match_returns_single=False):
        keyword_as_int = None
        keyword_as_str = str(id_or_keyword)
        try:
            keyword_as_int = int(id_or_keyword)
            if keyword_as_int < 10000:
                keyword_as_int = None
        except (ValueError, TypeError):
            pass
        results = []
        for card in self.cards:
            if keyword_as_int == card.mtga_id or keyword_as_int == card.set_number:
                return [card]

            keyword_clean = re.sub('[^0-9a-zA-Z_]', '', keyword_as_str.lower())
            if keyword_clean == card.name and direct_match_returns_single:
                return [card]
            if keyword_clean in card.name:
                results.append(card)
        return results


class Zone(Pool):
    def __init__(self, pool_name, zone_id=-1):
        super().__init__(pool_name)
        self.zone_id = zone_id
        self.abilities = []

    def match_game_id_to_card(self, instance_id, card_id):
        from app.mtga_app import mtga_logger
        for card in self.cards:
            assert isinstance(card, GameCard)
            if card.game_id == instance_id or instance_id in card.previous_iids:
                # if card.mtga_id != -1 and card.mtga_id != card_id:
                #     raise Exception("WHOA. tried to match iid {} to {}, but already has card {}".format(str(instance_id), str(card_id), str(card.mtga_id)))
                card.transform_to(card_id)
            elif card.mtga_id == card_id:
                # only allowed to set it if it's still -1 (should probably never hit this!)
                if card.game_id == -1:
                    mtga_logger.info("What the hell?! How'd we get a card ID without an instance ID?")
                    card.game_id = instance_id


class Deck(Pool):

    def __init__(self, pool_name, deck_id):
        super().__init__(pool_name)
        self.deck_id = deck_id

    def generate_library(self, owner_id=-1):
        library = Library(self.pool_name, self.deck_id, owner_id, -1)
        for card in self.cards:
            game_card = mcard.GameCard(card.name, card.pretty_name, card.cost, card.color_identity, card.card_type,
                                       card.sub_types, card.set, card.rarity, card.set_number, card.mtga_id, owner_id, -1)
            library.cards.append(game_card)
        return library

    def to_serializable(self, transform_to_counted=False):
        obj = {
            "deck_id": self.deck_id,
            "pool_name": self.pool_name,
        }
        if transform_to_counted:
            card_dict = {}
            for card in self.cards:
                card_dict[card.mtga_id] = card_dict.get(card.mtga_id, card.to_serializable())
                card_dict[card.mtga_id]["count_in_deck"] = card_dict[card.mtga_id].get("count_in_deck", 0) + 1
            obj["cards"] = [v for v in card_dict.values()]
            obj["cards"].sort(key=lambda x: x["count_in_deck"])
            obj["cards"].reverse()
            obj["cards"] = obj["cards"]
        else:
            obj["cards"] = [c.to_serializable() for c in self.cards]
        return obj

    def to_min_json(self):
        min_deck = {}
        min_side = {}
        for card in self.cards:
            if card.mtga_id not in min_deck:
                min_deck[card.mtga_id] = 0
            min_deck[card.mtga_id] += 1
        for card in self.side:
            if card.mtga_id not in min_side:
                min_side[card.mtga_id] = 0
            min_side[card.mtga_id] += 1
        return {"deckID": self.deck_id, "poolName": self.pool_name, "cards": min_deck, "sideboard": min_side}

    @classmethod
    def from_dict(cls, obj):
        deck = Deck(obj["pool_name"], obj["deck_id"])
        for card in obj["cards"]:
            deck.cards.append(mcard.Card.from_dict(card))
        return deck


class Library(Deck, Zone):
    def __init__(self, pool_name, deck_id, owner_seat_id, zone_id=-1):
        super().__init__(pool_name, deck_id)
        self.owner_seat_id = owner_seat_id
        self.zone_id = zone_id

    def set_seat_id(self, seat_id):
        self.owner_seat_id = seat_id
        for card in self.cards:
            assert isinstance(card, GameCard)
            card.owner_seat_id = seat_id