Module mk2lib.mk2deck
Market implementation and card decks definitions.
Classes
class Market (game: MachiKoroGame, use_promo: bool = True)-
Expand source code
class Market: """ Implementation of 5-5-5 market of Machi Koro 2. """ def __init__(self, game: MachiKoroGame, use_promo: bool = True): """ Create a new Market object, clone and shuffle decks and make an initial deal. """ self.est_low = cast(list[Establishment], Market._make_card_deck(DECK_1_6)) self.est_high = cast(list[Establishment], Market._make_card_deck(DECK_7_12)) self.landmarks = cast(list[Landmark], Market._make_card_deck(DECK_LANDMARKS)) if not use_promo: self.landmarks = list(filter(lambda c: not c.is_promo, self.landmarks)) self.dealt_low: dict[str, Establishment] = {} self.dealt_high: dict[str, Establishment] = {} self.dealt_landmarks: dict[str, Landmark] = {} self.game = game initial_deal = self.deal_to_market() self.game.emit_event(DealtCardsToMarket(initial_deal, initial=True)) @staticmethod def _make_card_deck(cards: list[Establishment] | list[Landmark]) -> list[Card]: """ Clone and shuffle card deck. :param cards: Source card deck. :return: Cloned and shuffled card deck with un-stacked cards. """ deck = [] for card in cards: for _ in range(card.quantity): deck.append(replace(card, quantity=1)) shuffle(deck) return cast(list[Card], deck) def deal_to_market(self) -> list[Card]: """ Deal cards, until there's 5 of each type. Duplicate cards are stacked. :return: List of cards that were dealt to market. """ dealt = [] for deck, _market in ( (self.est_low, self.dealt_low), (self.est_high, self.dealt_high), (self.landmarks, self.dealt_landmarks), ): market = cast(dict[str, Card], _market) while deck and len(market) < 5: card = deck.pop() dealt.append(card) if card.name in market: market[card.name].quantity += 1 else: market[card.name] = card return cast(list[Card], dealt) def can_build(self, player: Player) -> list[Card]: """ Check is player can build something. Checks that market has any cards up for building and that player can afford at least any one card. :param player: Player, whose build phase it is. """ affordable_cards = [] for market in (self.dealt_low, self.dealt_high, self.dealt_landmarks): for card in market.values(): if player.can_afford(card.get_real_price(self.game, player)): affordable_cards.append(card) return cast(list[Card], affordable_cards) def build_card(self, player: Player, card_name: str) -> Card | None: """ Build the specified card. Checks whether card is dealt to market, plus if player can afford and is allowed to build it by rules. If yes - gives card to player and emits CardBuilt event. If for any reason building is illegal - either CardUnavailable or NotEnoughMoney event is emitted. :param player: Player who builds the card. :param card_name: Name of the card that player wants to build. :return: Card instance, if built successfully. None otherwise. """ for market in (self.dealt_low, self.dealt_high, self.dealt_landmarks): if card_name in market: card = replace(market[card_name], quantity=1) real_price = card.get_real_price(self.game, player) if player.can_afford(real_price) and real_price is not None: player.spend_coins(real_price) if market[card_name].quantity > 1: market[card_name].quantity -= 1 else: market.pop(card_name) player.add_card(card) self.game.emit_event( CardBuilt( buyer=player, card=card, price_paid=real_price, ) ) dealt = self.deal_to_market() if dealt: self.game.emit_event(DealtCardsToMarket(dealt)) return card if real_price is None: self.game.emit_event( CardUnavailable( buyer=player, card_name=card_name, prohibited=True, ) ) else: self.game.emit_event( NotEnoughMoney( buyer=player, card_name=card_name, card_price=real_price, ) ) return None self.game.emit_event(CardUnavailable(buyer=player, card_name=card_name)) return None def serialize(self) -> dict: """ Prepare JSON serializable version of this Market instance. Preserves card state and order. :return: JSON-friendly dict that has enough state to reconstruct Market object. """ return { "est_low": [card.name for card in self.est_low], "est_high": [card.name for card in self.est_high], "landmarks": [card.name for card in self.landmarks], "dealt_low": {name: card.quantity for name, card in self.dealt_low.items()}, "dealt_high": { name: card.quantity for name, card in self.dealt_high.items() }, "dealt_landmarks": { name: card.quantity for name, card in self.dealt_landmarks.items() }, } @classmethod def deserialize(cls, game: MachiKoroGame, data: dict) -> Market: """ Reconstruct Market from saved dict, produced by .serialize() :return: Loaded Market from saved data. """ market = cls.__new__(cls) # bypass __init__, we restore manually market.game = game # reconstruct decks in order def rebuild_deck(names): return [replace(ALL_CARDS[name], quantity=1) for name in names] market.est_low = rebuild_deck(data["est_low"]) market.est_high = rebuild_deck(data["est_high"]) market.landmarks = rebuild_deck(data["landmarks"]) # reconstruct dealt dicts (quantities matter!) def rebuild_dealt(d): return { name: replace(ALL_CARDS[name], quantity=qty) for name, qty in d.items() } market.dealt_low = rebuild_dealt(data["dealt_low"]) market.dealt_high = rebuild_dealt(data["dealt_high"]) market.dealt_landmarks = rebuild_dealt(data["dealt_landmarks"]) return marketImplementation of 5-5-5 market of Machi Koro 2.
Create a new Market object, clone and shuffle decks and make an initial deal.
Static methods
def deserialize(game: MachiKoroGame, data: dict)-
Reconstruct Market from saved dict, produced by .serialize()
:return: Loaded Market from saved data.
Methods
def build_card(self, player: Player, card_name: str) ‑> Card | None-
Expand source code
def build_card(self, player: Player, card_name: str) -> Card | None: """ Build the specified card. Checks whether card is dealt to market, plus if player can afford and is allowed to build it by rules. If yes - gives card to player and emits CardBuilt event. If for any reason building is illegal - either CardUnavailable or NotEnoughMoney event is emitted. :param player: Player who builds the card. :param card_name: Name of the card that player wants to build. :return: Card instance, if built successfully. None otherwise. """ for market in (self.dealt_low, self.dealt_high, self.dealt_landmarks): if card_name in market: card = replace(market[card_name], quantity=1) real_price = card.get_real_price(self.game, player) if player.can_afford(real_price) and real_price is not None: player.spend_coins(real_price) if market[card_name].quantity > 1: market[card_name].quantity -= 1 else: market.pop(card_name) player.add_card(card) self.game.emit_event( CardBuilt( buyer=player, card=card, price_paid=real_price, ) ) dealt = self.deal_to_market() if dealt: self.game.emit_event(DealtCardsToMarket(dealt)) return card if real_price is None: self.game.emit_event( CardUnavailable( buyer=player, card_name=card_name, prohibited=True, ) ) else: self.game.emit_event( NotEnoughMoney( buyer=player, card_name=card_name, card_price=real_price, ) ) return None self.game.emit_event(CardUnavailable(buyer=player, card_name=card_name)) return NoneBuild the specified card.
Checks whether card is dealt to market, plus if player can afford and is allowed to build it by rules.
If yes - gives card to player and emits CardBuilt event. If for any reason building is illegal - either CardUnavailable or NotEnoughMoney event is emitted.
:param player: Player who builds the card. :param card_name: Name of the card that player wants to build. :return: Card instance, if built successfully. None otherwise.
def can_build(self, player: Player) ‑> list[Card]-
Expand source code
def can_build(self, player: Player) -> list[Card]: """ Check is player can build something. Checks that market has any cards up for building and that player can afford at least any one card. :param player: Player, whose build phase it is. """ affordable_cards = [] for market in (self.dealt_low, self.dealt_high, self.dealt_landmarks): for card in market.values(): if player.can_afford(card.get_real_price(self.game, player)): affordable_cards.append(card) return cast(list[Card], affordable_cards)Check is player can build something.
Checks that market has any cards up for building and that player can afford at least any one card.
:param player: Player, whose build phase it is.
def deal_to_market(self) ‑> list[Card]-
Expand source code
def deal_to_market(self) -> list[Card]: """ Deal cards, until there's 5 of each type. Duplicate cards are stacked. :return: List of cards that were dealt to market. """ dealt = [] for deck, _market in ( (self.est_low, self.dealt_low), (self.est_high, self.dealt_high), (self.landmarks, self.dealt_landmarks), ): market = cast(dict[str, Card], _market) while deck and len(market) < 5: card = deck.pop() dealt.append(card) if card.name in market: market[card.name].quantity += 1 else: market[card.name] = card return cast(list[Card], dealt)Deal cards, until there's 5 of each type.
Duplicate cards are stacked.
:return: List of cards that were dealt to market.
def serialize(self) ‑> dict-
Expand source code
def serialize(self) -> dict: """ Prepare JSON serializable version of this Market instance. Preserves card state and order. :return: JSON-friendly dict that has enough state to reconstruct Market object. """ return { "est_low": [card.name for card in self.est_low], "est_high": [card.name for card in self.est_high], "landmarks": [card.name for card in self.landmarks], "dealt_low": {name: card.quantity for name, card in self.dealt_low.items()}, "dealt_high": { name: card.quantity for name, card in self.dealt_high.items() }, "dealt_landmarks": { name: card.quantity for name, card in self.dealt_landmarks.items() }, }Prepare JSON serializable version of this Market instance.
Preserves card state and order.
:return: JSON-friendly dict that has enough state to reconstruct Market object.