File size: 1,839 Bytes
4be6a52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"""Immutable types shared by the engine, players, replay, and HTTP service."""

from dataclasses import dataclass

WIDTH = 10
HEIGHT = 20
RULES_VERSION = "stackcraft-v1"
PIECES = ("I", "O", "T", "S", "Z", "J", "L")
COLORS = {piece: index + 1 for index, piece in enumerate(PIECES)}
type Cells = tuple[tuple[int, int], ...]
type Board = tuple[tuple[int, ...], ...]


@dataclass(frozen=True)
class Placement:
    id: str
    rotation: int
    x: int
    y: int
    cells: Cells


@dataclass(frozen=True)
class GameState:
    board: Board
    seed: int
    piece_index: int
    current: str
    next_piece: str
    score: int = 0
    lines: int = 0
    terminal: bool = False

    def __post_init__(self) -> None:
        if not isinstance(self.board, tuple) or len(self.board) != HEIGHT:
            raise ValueError(f"board must contain {HEIGHT} immutable rows")
        if any(
            not isinstance(row, tuple)
            or len(row) != WIDTH
            or any(type(cell) is not int or not 0 <= cell <= 7 for cell in row)
            for row in self.board
        ):
            raise ValueError(f"board rows must contain {WIDTH} integer cells from 0 to 7")
        if type(self.seed) is not int:
            raise ValueError("seed must be an integer")
        for name in ("piece_index", "score", "lines"):
            value = getattr(self, name)
            if type(value) is not int or value < 0:
                raise ValueError(f"{name} must be a nonnegative integer")
        if self.current not in PIECES or self.next_piece not in PIECES:
            raise ValueError("current and next_piece must be tetromino names")
        if type(self.terminal) is not bool:
            raise ValueError("terminal must be a boolean")


@dataclass(frozen=True)
class Transition:
    state: GameState
    action: Placement
    cleared: int