"""Value types describing game state, options, results and events.""" from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Any from .cards import Card, Suit, card_by_id class Phase(str, Enum): TRUMP_SELECTION = "trump_selection" BIDDING = "bidding" PLAYING = "playing" FINISHED = "finished" @dataclass(frozen=True, slots=True) class GameOptions: # Note: the sum of all bids never equals the number of tricks of a round; # this is a fixed rule (see ``WizardGame._allowed_bids``), not an option. # "hiddentip": bids are revealed only once every player has bid. hidden_bids: bool = False def to_dict(self) -> dict: return {"hidden_bids": self.hidden_bids} @classmethod def from_dict(cls, data: dict[str, Any]) -> GameOptions: return cls(hidden_bids=bool(data.get("hidden_bids", False))) @dataclass(frozen=True, slots=True) class PlayedCard: player: int card: Card def to_dict(self) -> dict: return {"player": self.player, "card": self.card.to_dict()} @classmethod def from_dict(cls, data: dict[str, Any]) -> PlayedCard: return cls(player=data["player"], card=card_by_id(data["card"]["id"])) @dataclass(frozen=True, slots=True) class TrickResult: round_number: int trick_number: int cards: tuple[PlayedCard, ...] winner: int def to_dict(self) -> dict: return { "round_number": self.round_number, "trick_number": self.trick_number, "cards": [played.to_dict() for played in self.cards], "winner": self.winner, } @classmethod def from_dict(cls, data: dict[str, Any]) -> TrickResult: return cls( round_number=data["round_number"], trick_number=data["trick_number"], cards=tuple(PlayedCard.from_dict(c) for c in data["cards"]), winner=data["winner"], ) @dataclass(frozen=True, slots=True) class RoundResult: round_number: int bids: tuple[int, ...] tricks: tuple[int, ...] score_changes: tuple[int, ...] scores: tuple[int, ...] def to_dict(self) -> dict: return { "round_number": self.round_number, "bids": list(self.bids), "tricks": list(self.tricks), "score_changes": list(self.score_changes), "scores": list(self.scores), } @classmethod def from_dict(cls, data: dict[str, Any]) -> RoundResult: return cls( round_number=data["round_number"], bids=tuple(data["bids"]), tricks=tuple(data["tricks"]), score_changes=tuple(data["score_changes"]), scores=tuple(data["scores"]), ) # --- Events ----------------------------------------------------------------- # ``WizardGame.apply`` returns the events caused by an action. They are handy # for clients that want to animate what happened (e.g. pause after a trick). @dataclass(frozen=True, slots=True) class RoundStarted: round_number: int dealer: int trump_card: Card | None def to_dict(self) -> dict: return { "type": "round_started", "round_number": self.round_number, "dealer": self.dealer, "trump_card": self.trump_card.to_dict() if self.trump_card else None, } @dataclass(frozen=True, slots=True) class TrickCompleted: trick: TrickResult def to_dict(self) -> dict: return {"type": "trick_completed", "trick": self.trick.to_dict()} @dataclass(frozen=True, slots=True) class RoundCompleted: result: RoundResult def to_dict(self) -> dict: return {"type": "round_completed", "result": self.result.to_dict()} @dataclass(frozen=True, slots=True) class GameFinished: scores: tuple[int, ...] winners: tuple[int, ...] def to_dict(self) -> dict: return {"type": "game_finished", "scores": list(self.scores), "winners": list(self.winners)} Event = RoundStarted | TrickCompleted | RoundCompleted | GameFinished @dataclass(slots=True) class RoundState: """Mutable per-round state, owned by ``WizardGame``.""" round_number: int dealer: int hands: list[list[Card]] trump_card: Card | None trump_suit: Suit | None bids: list[int | None] tricks_won: list[int] current_trick: list[PlayedCard] = field(default_factory=list) trick_leader: int = 0 trick_number: int = 1 played_cards: list[Card] = field(default_factory=list)