first prototype
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""Cards and deck of the Wizard card game.
|
||||
|
||||
The deck consists of 60 cards:
|
||||
- 52 number cards: four suits (red, yellow, green, blue) with ranks 1-13
|
||||
- 4 wizards (highest cards)
|
||||
- 4 jesters (lowest cards)
|
||||
|
||||
Every card has a stable integer id in the range 0-59 which is used for
|
||||
serialization and as an index for machine learning encodings:
|
||||
- 0-51: number cards, ``suit_index * 13 + (rank - 1)``
|
||||
- 52-55: wizards
|
||||
- 56-59: jesters
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Suit(str, Enum):
|
||||
RED = "red"
|
||||
YELLOW = "yellow"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
|
||||
@property
|
||||
def index(self) -> int:
|
||||
return SUITS.index(self)
|
||||
|
||||
|
||||
SUITS: tuple[Suit, ...] = (Suit.RED, Suit.YELLOW, Suit.GREEN, Suit.BLUE)
|
||||
|
||||
|
||||
class CardKind(str, Enum):
|
||||
NUMBER = "number"
|
||||
WIZARD = "wizard"
|
||||
JESTER = "jester"
|
||||
|
||||
|
||||
NUM_RANKS = 13
|
||||
NUM_NUMBER_CARDS = len(SUITS) * NUM_RANKS
|
||||
NUM_WIZARDS = 4
|
||||
NUM_JESTERS = 4
|
||||
DECK_SIZE = NUM_NUMBER_CARDS + NUM_WIZARDS + NUM_JESTERS
|
||||
|
||||
WIZARD_ID_OFFSET = NUM_NUMBER_CARDS
|
||||
JESTER_ID_OFFSET = NUM_NUMBER_CARDS + NUM_WIZARDS
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Card:
|
||||
id: int
|
||||
kind: CardKind
|
||||
suit: Suit | None = None
|
||||
rank: int = 0
|
||||
|
||||
@property
|
||||
def is_wizard(self) -> bool:
|
||||
return self.kind is CardKind.WIZARD
|
||||
|
||||
@property
|
||||
def is_jester(self) -> bool:
|
||||
return self.kind is CardKind.JESTER
|
||||
|
||||
@property
|
||||
def is_number(self) -> bool:
|
||||
return self.kind is CardKind.NUMBER
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"kind": self.kind.value,
|
||||
"suit": self.suit.value if self.suit else None,
|
||||
"rank": self.rank,
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.is_wizard:
|
||||
return f"Wizard{self.id - WIZARD_ID_OFFSET + 1}"
|
||||
if self.is_jester:
|
||||
return f"Jester{self.id - JESTER_ID_OFFSET + 1}"
|
||||
assert self.suit is not None
|
||||
return f"{self.suit.value.capitalize()}{self.rank}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Card({self})"
|
||||
|
||||
|
||||
def _build_deck() -> tuple[Card, ...]:
|
||||
cards: list[Card] = []
|
||||
for suit in SUITS:
|
||||
for rank in range(1, NUM_RANKS + 1):
|
||||
cards.append(Card(len(cards), CardKind.NUMBER, suit, rank))
|
||||
for _ in range(NUM_WIZARDS):
|
||||
cards.append(Card(len(cards), CardKind.WIZARD))
|
||||
for _ in range(NUM_JESTERS):
|
||||
cards.append(Card(len(cards), CardKind.JESTER))
|
||||
return tuple(cards)
|
||||
|
||||
|
||||
ALL_CARDS: tuple[Card, ...] = _build_deck()
|
||||
|
||||
|
||||
def card_by_id(card_id: int) -> Card:
|
||||
if not 0 <= card_id < DECK_SIZE:
|
||||
raise ValueError(f"Invalid card id: {card_id}")
|
||||
return ALL_CARDS[card_id]
|
||||
|
||||
|
||||
def new_shuffled_deck(rng: random.Random) -> list[Card]:
|
||||
deck = list(ALL_CARDS)
|
||||
rng.shuffle(deck)
|
||||
return deck
|
||||
Reference in New Issue
Block a user