167 lines
4.6 KiB
Python
167 lines
4.6 KiB
Python
"""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:
|
|
# "notequal": the sum of all bids must not equal the number of tricks
|
|
# of the round. The last bidder has to adjust their bid.
|
|
not_equal: bool = False
|
|
# "hiddentip": bids are revealed only once every player has bid.
|
|
hidden_bids: bool = False
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"not_equal": self.not_equal, "hidden_bids": self.hidden_bids}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> GameOptions:
|
|
return cls(
|
|
not_equal=bool(data.get("not_equal", False)),
|
|
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)
|