"""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}")