- Deal the hands from the shuffled stack and draw the trump card from it, so the trump card is in nobody's hand and cannot be played - A wizard or jester as trump card means no trump suit (previously a wizard let the dealer choose); remove the trump selection phase, the ChooseTrump action, the trump picker UI and the AI trump actions - Completed tricks carry their round's trump card, so the table keeps showing the matching trump card while the last trick of a round is displayed - Card corners are no longer rotated, so 6 and 9 cannot be confused - Trump label explains why there is no trump (wizard, jester, last round) - Tests: trump card never dealt, special cards give no trump, tricks keep their trump card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
1.7 KiB
Python
63 lines
1.7 KiB
Python
"""Actions a player can take and their (de)serialization.
|
|
|
|
Actions are serialized as small JSON objects which are shared by the web
|
|
frontend, the game server and the AI player:
|
|
|
|
- ``{"type": "bid", "value": 2}``
|
|
- ``{"type": "play", "card": 17}``
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from .cards import Card, card_by_id
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Bid:
|
|
value: int
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"type": "bid", "value": self.value}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PlayCard:
|
|
card: Card
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"type": "play", "card": self.card.id}
|
|
|
|
|
|
Action = Bid | PlayCard
|
|
|
|
|
|
def action_to_dict(action: Action) -> dict:
|
|
return action.to_dict()
|
|
|
|
|
|
def action_from_dict(data: dict[str, Any]) -> Action:
|
|
"""Parse an action from its JSON representation.
|
|
|
|
Raises ``ValueError`` if the data is malformed.
|
|
"""
|
|
if not isinstance(data, dict):
|
|
raise ValueError("Action must be an object")
|
|
kind = data.get("type")
|
|
try:
|
|
if kind == "bid":
|
|
value = data["value"]
|
|
if not isinstance(value, int) or isinstance(value, bool):
|
|
raise ValueError("Bid value must be an integer")
|
|
return Bid(value)
|
|
if kind == "play":
|
|
card_id = data["card"]
|
|
if not isinstance(card_id, int) or isinstance(card_id, bool):
|
|
raise ValueError("Card must be an integer id")
|
|
return PlayCard(card_by_id(card_id))
|
|
except KeyError as exc:
|
|
raise ValueError(f"Missing field {exc} in action") from exc
|
|
raise ValueError(f"Unknown action type: {kind!r}")
|