Module mk2lib.player

A class for representing Machi Koro 2 player.

Classes

class Player (player_id: int,
coins: int = 5,
initial_build_turns: int = 3,
extra_turn: bool = False,
exchange_establishments: int = 0,
have_loan_office: bool = False,
have_launch_pad: bool = False,
give_establishment: bool = False,
earned_coins_this_turn: bool = False,
establishments: list[Establishment] = <factory>,
landmarks: list[Landmark] = <factory>)
Expand source code
@dataclass
class Player:
    """
    Class representing a player and their state.
    """

    player_id: int
    coins: int = 5
    initial_build_turns: int = 3
    extra_turn: bool = False
    exchange_establishments: int = 0
    have_loan_office: bool = False
    have_launch_pad: bool = False
    give_establishment: bool = False
    earned_coins_this_turn: bool = False
    establishments: list[Establishment] = field(default_factory=list)
    landmarks: list[Landmark] = field(default_factory=list)

    def get_activated_establishments(
        self, number: int, order: ActivationOrder
    ) -> list[Establishment]:
        """
        Return all activated establishments for specified roll and order/color.

        Sort them by minimal of activation numbers.

        :param number: Sum of rolled dice.
        :param order: Order (color) of cards that would be activated.
        :return: List of establishments activated.
        """
        activated = []
        for card in self.establishments:
            if card.order == order and number in card.activation_numbers:
                activated.append(card)
        return sorted(activated, key=lambda x: min(x.activation_numbers))

    def add_card(self, card: Establishment | Landmark) -> None:
        """
        Give a card to player.

        :param card: Card given to a player.
        :return: None.
        """
        if isinstance(card, Establishment):
            self.establishments.append(card)
        elif isinstance(card, Landmark):
            self.landmarks.append(card)
        else:
            raise TypeError(f"Invalid object passed into add_card: {card!r}")

    def has_card(self, card_name: str, only_establishments: bool = False) -> bool:
        """
        Check if player has a card by name.

        Note that if used with only_establishments == False, it would return landmarks,
        which might be unwanted (e.g. if checking for presence of establishment for
        give/exchange). Make sure this is really what you want to do.

        :param card_name: Name of card to check for.
        :param only_establishments: Check only in establishments.
        :return: Boolean, indicating if player has this card.
        """
        for establishment in self.establishments:
            if establishment.name == card_name:
                return True
        if only_establishments:
            return False
        for landmark in self.landmarks:
            if landmark.name == card_name:
                return True
        return False

    def pop_card(self, card_name: str) -> Establishment | Landmark:
        """
        Pop and return card from player's hand.

        :param card_name: Name of card to take.
        :return: Card, removed from player's hand.
        """
        for idx, establishment in enumerate(self.establishments):
            if establishment.name == card_name:
                return self.establishments.pop(idx)
        for idx, landmark in enumerate(self.landmarks):
            if landmark.name == card_name:
                return self.landmarks.pop(idx)
        raise KeyError(f'Player doesn\'t have card "{card_name}"')

    def count_cards_by_category(self, category: Kind) -> int:
        """
        Helper to count establishments of particular category.

        :param category: Card symbol that's being counted.
        :return: Number of cards with matching category in player's possession.
        """
        return sum(1 for card in self.establishments if card.category == category)

    def can_afford(self, price: int | None) -> bool:
        """
        Check if player can pay a specific price.

        Note that None is assumes player can't afford purchase by rules,
        hence always returns False.

        :param price: How much player has to pay.
        :return: Boolean, indicating whether player has enough money.
        """
        if price is None:
            return False
        return self.coins >= price

    def take_coins(self, amount: int) -> int:
        """
        Take up to amount coins from player and return amount taken.

        :param amount: How many coins should be taken from player.
        :return: How many coins were actually taken.
        """
        amount_taken = min(self.coins, amount)
        self.coins -= amount_taken
        return amount_taken

    def earn_coins(self, amount: int) -> int:
        """
        Give player amount of coins. Returns the amount (to allow chaining
        with take_coins, e.g. `player.earn_coins(other_player.take_coins(N))`)

        This method also sets per-turn flag, indicating player has earned coins.

        :param amount: How many coins to give player.
        :return: How many coins were given (useful for chaining with take_coins).
        """
        self.coins += amount
        self.earned_coins_this_turn = True
        return amount

    def spend_coins(self, amount: int) -> bool:
        """
        Spend coins. Returns, whether purchase was successful.

        :param amount: How many coins player is about to spend.
        :return: Boolean, indicating whether purchase was successful.
        """
        if self.coins >= amount:
            self.coins -= amount
            return True
        return False

    def new_turn(self) -> None:
        """
        Called before player's turn begins. Resets some per-turn flags.

        :return: None.
        """
        self.extra_turn = False
        self.earned_coins_this_turn = False

    def end_turn(self) -> None:
        """
        Called when player's turn ends. Decrements initial build phase
        counter and earning flag.

        :return: None.
        """
        self.earned_coins_this_turn = False
        if self.initial_build_turns > 0:
            self.initial_build_turns -= 1

    def is_winner(self) -> bool:
        """
        Returns when win condition is hit - 3 landmarks or launch pad built.

        :return: Whether player is winner.
        """
        if len(self.landmarks) >= 3 or self.have_launch_pad:
            return True
        return False

    def serialize(self) -> dict:
        """
        Convert player's state into JSON-serializable dict.

        :return: Dict holding current state of Player.
        """
        return {
            "player_id": self.player_id,
            "coins": self.coins,
            "initial_build_turns": self.initial_build_turns,
            "extra_turn": self.extra_turn,
            "exchange_establishments": self.exchange_establishments,
            "have_loan_office": self.have_loan_office,
            "have_launch_pad": self.have_launch_pad,
            "give_establishment": self.give_establishment,
            "earned_coins_this_turn": self.earned_coins_this_turn,
            "establishments": [card.name for card in self.establishments],
            "landmarks": [card.name for card in self.landmarks],
        }

    @classmethod
    def deserialize(cls, data: dict) -> Player:
        """
        Restore player's state from JSON-serializable dict.

        :param data: Dict with serialized Player object.
        :return: Deserialized from saved data Player object.
        """
        player = cls(
            player_id=data["player_id"],
            coins=data["coins"],
            initial_build_turns=data["initial_build_turns"],
            extra_turn=data["extra_turn"],
            exchange_establishments=data["exchange_establishments"],
            have_loan_office=data["have_loan_office"],
            have_launch_pad=data["have_launch_pad"],
            give_establishment=data["give_establishment"],
            earned_coins_this_turn=data["earned_coins_this_turn"],
        )

        for card_name in data["establishments"] + data["landmarks"]:
            player.add_card(replace(ALL_CARDS[card_name], quantity=1))

        return player

Class representing a player and their state.

Static methods

def deserialize(data: dict) ‑> Player

Restore player's state from JSON-serializable dict.

:param data: Dict with serialized Player object. :return: Deserialized from saved data Player object.

Instance variables

var coins : int

The type of the None singleton.

var earned_coins_this_turn : bool

The type of the None singleton.

var establishments : list[Establishment]

The type of the None singleton.

var exchange_establishments : int

The type of the None singleton.

var extra_turn : bool

The type of the None singleton.

var give_establishment : bool

The type of the None singleton.

var have_launch_pad : bool

The type of the None singleton.

var have_loan_office : bool

The type of the None singleton.

var initial_build_turns : int

The type of the None singleton.

var landmarks : list[Landmark]

The type of the None singleton.

var player_id : int

The type of the None singleton.

Methods

def add_card(self, card: Establishment | Landmark) ‑> None
Expand source code
def add_card(self, card: Establishment | Landmark) -> None:
    """
    Give a card to player.

    :param card: Card given to a player.
    :return: None.
    """
    if isinstance(card, Establishment):
        self.establishments.append(card)
    elif isinstance(card, Landmark):
        self.landmarks.append(card)
    else:
        raise TypeError(f"Invalid object passed into add_card: {card!r}")

Give a card to player.

:param card: Card given to a player. :return: None.

def can_afford(self, price: int | None) ‑> bool
Expand source code
def can_afford(self, price: int | None) -> bool:
    """
    Check if player can pay a specific price.

    Note that None is assumes player can't afford purchase by rules,
    hence always returns False.

    :param price: How much player has to pay.
    :return: Boolean, indicating whether player has enough money.
    """
    if price is None:
        return False
    return self.coins >= price

Check if player can pay a specific price.

Note that None is assumes player can't afford purchase by rules, hence always returns False.

:param price: How much player has to pay. :return: Boolean, indicating whether player has enough money.

def count_cards_by_category(self, category: Kind) ‑> int
Expand source code
def count_cards_by_category(self, category: Kind) -> int:
    """
    Helper to count establishments of particular category.

    :param category: Card symbol that's being counted.
    :return: Number of cards with matching category in player's possession.
    """
    return sum(1 for card in self.establishments if card.category == category)

Helper to count establishments of particular category.

:param category: Card symbol that's being counted. :return: Number of cards with matching category in player's possession.

def earn_coins(self, amount: int) ‑> int
Expand source code
def earn_coins(self, amount: int) -> int:
    """
    Give player amount of coins. Returns the amount (to allow chaining
    with take_coins, e.g. `player.earn_coins(other_player.take_coins(N))`)

    This method also sets per-turn flag, indicating player has earned coins.

    :param amount: How many coins to give player.
    :return: How many coins were given (useful for chaining with take_coins).
    """
    self.coins += amount
    self.earned_coins_this_turn = True
    return amount

Give player amount of coins. Returns the amount (to allow chaining with take_coins, e.g. player.earn_coins(other_player.take_coins(N)))

This method also sets per-turn flag, indicating player has earned coins.

:param amount: How many coins to give player. :return: How many coins were given (useful for chaining with take_coins).

def end_turn(self) ‑> None
Expand source code
def end_turn(self) -> None:
    """
    Called when player's turn ends. Decrements initial build phase
    counter and earning flag.

    :return: None.
    """
    self.earned_coins_this_turn = False
    if self.initial_build_turns > 0:
        self.initial_build_turns -= 1

Called when player's turn ends. Decrements initial build phase counter and earning flag.

:return: None.

def get_activated_establishments(self, number: int, order: ActivationOrder) ‑> list[Establishment]
Expand source code
def get_activated_establishments(
    self, number: int, order: ActivationOrder
) -> list[Establishment]:
    """
    Return all activated establishments for specified roll and order/color.

    Sort them by minimal of activation numbers.

    :param number: Sum of rolled dice.
    :param order: Order (color) of cards that would be activated.
    :return: List of establishments activated.
    """
    activated = []
    for card in self.establishments:
        if card.order == order and number in card.activation_numbers:
            activated.append(card)
    return sorted(activated, key=lambda x: min(x.activation_numbers))

Return all activated establishments for specified roll and order/color.

Sort them by minimal of activation numbers.

:param number: Sum of rolled dice. :param order: Order (color) of cards that would be activated. :return: List of establishments activated.

def has_card(self, card_name: str, only_establishments: bool = False) ‑> bool
Expand source code
def has_card(self, card_name: str, only_establishments: bool = False) -> bool:
    """
    Check if player has a card by name.

    Note that if used with only_establishments == False, it would return landmarks,
    which might be unwanted (e.g. if checking for presence of establishment for
    give/exchange). Make sure this is really what you want to do.

    :param card_name: Name of card to check for.
    :param only_establishments: Check only in establishments.
    :return: Boolean, indicating if player has this card.
    """
    for establishment in self.establishments:
        if establishment.name == card_name:
            return True
    if only_establishments:
        return False
    for landmark in self.landmarks:
        if landmark.name == card_name:
            return True
    return False

Check if player has a card by name.

Note that if used with only_establishments == False, it would return landmarks, which might be unwanted (e.g. if checking for presence of establishment for give/exchange). Make sure this is really what you want to do.

:param card_name: Name of card to check for. :param only_establishments: Check only in establishments. :return: Boolean, indicating if player has this card.

def is_winner(self) ‑> bool
Expand source code
def is_winner(self) -> bool:
    """
    Returns when win condition is hit - 3 landmarks or launch pad built.

    :return: Whether player is winner.
    """
    if len(self.landmarks) >= 3 or self.have_launch_pad:
        return True
    return False

Returns when win condition is hit - 3 landmarks or launch pad built.

:return: Whether player is winner.

def new_turn(self) ‑> None
Expand source code
def new_turn(self) -> None:
    """
    Called before player's turn begins. Resets some per-turn flags.

    :return: None.
    """
    self.extra_turn = False
    self.earned_coins_this_turn = False

Called before player's turn begins. Resets some per-turn flags.

:return: None.

def pop_card(self, card_name: str) ‑> Establishment | Landmark
Expand source code
def pop_card(self, card_name: str) -> Establishment | Landmark:
    """
    Pop and return card from player's hand.

    :param card_name: Name of card to take.
    :return: Card, removed from player's hand.
    """
    for idx, establishment in enumerate(self.establishments):
        if establishment.name == card_name:
            return self.establishments.pop(idx)
    for idx, landmark in enumerate(self.landmarks):
        if landmark.name == card_name:
            return self.landmarks.pop(idx)
    raise KeyError(f'Player doesn\'t have card "{card_name}"')

Pop and return card from player's hand.

:param card_name: Name of card to take. :return: Card, removed from player's hand.

def serialize(self) ‑> dict
Expand source code
def serialize(self) -> dict:
    """
    Convert player's state into JSON-serializable dict.

    :return: Dict holding current state of Player.
    """
    return {
        "player_id": self.player_id,
        "coins": self.coins,
        "initial_build_turns": self.initial_build_turns,
        "extra_turn": self.extra_turn,
        "exchange_establishments": self.exchange_establishments,
        "have_loan_office": self.have_loan_office,
        "have_launch_pad": self.have_launch_pad,
        "give_establishment": self.give_establishment,
        "earned_coins_this_turn": self.earned_coins_this_turn,
        "establishments": [card.name for card in self.establishments],
        "landmarks": [card.name for card in self.landmarks],
    }

Convert player's state into JSON-serializable dict.

:return: Dict holding current state of Player.

def spend_coins(self, amount: int) ‑> bool
Expand source code
def spend_coins(self, amount: int) -> bool:
    """
    Spend coins. Returns, whether purchase was successful.

    :param amount: How many coins player is about to spend.
    :return: Boolean, indicating whether purchase was successful.
    """
    if self.coins >= amount:
        self.coins -= amount
        return True
    return False

Spend coins. Returns, whether purchase was successful.

:param amount: How many coins player is about to spend. :return: Boolean, indicating whether purchase was successful.

def take_coins(self, amount: int) ‑> int
Expand source code
def take_coins(self, amount: int) -> int:
    """
    Take up to amount coins from player and return amount taken.

    :param amount: How many coins should be taken from player.
    :return: How many coins were actually taken.
    """
    amount_taken = min(self.coins, amount)
    self.coins -= amount_taken
    return amount_taken

Take up to amount coins from player and return amount taken.

:param amount: How many coins should be taken from player. :return: How many coins were actually taken.