Download code/src/stackcraft/pieces.py from nima1/stackcraft-clef-flash-lora: direct link, hf CLI and curl.
- Browser
- Download file 2.19 kB
-
https://huggingface.co/nima1/stackcraft-clef-flash-lora/resolve/main/code/src/stackcraft/pieces.py
- Command line
-
hf download hf://nima1/stackcraft-clef-flash-lora/code/src/stackcraft/pieces.py
-
curl -L -o pieces.py https://huggingface.co/nima1/stackcraft-clef-flash-lora/resolve/main/code/src/stackcraft/pieces.py
2.19 kB
| """Normalized tetromino rotations and the versioned deterministic seven-bag.""" | |
| from functools import lru_cache | |
| from random import Random | |
| from stackcraft.schema import PIECES, RULES_VERSION, Cells | |
| _SHAPES: dict[str, Cells] = { | |
| "I": ((0, 0), (1, 0), (2, 0), (3, 0)), | |
| "O": ((0, 0), (1, 0), (0, 1), (1, 1)), | |
| "T": ((1, 0), (0, 1), (1, 1), (2, 1)), | |
| "S": ((1, 0), (2, 0), (0, 1), (1, 1)), | |
| "Z": ((0, 0), (1, 0), (1, 1), (2, 1)), | |
| "J": ((0, 0), (0, 1), (1, 1), (2, 1)), | |
| "L": ((2, 0), (0, 1), (1, 1), (2, 1)), | |
| } | |
| def _normalize(cells: Cells) -> Cells: | |
| left = min(x for x, _ in cells) | |
| top = min(y for _, y in cells) | |
| return tuple(sorted((x - left, y - top) for x, y in cells)) | |
| def rotations(piece: str) -> tuple[Cells, ...]: | |
| """Return distinct clockwise rotations, normalized to top-left (0, 0).""" | |
| if piece not in _SHAPES: | |
| raise ValueError(f"unknown piece: {piece!r}") | |
| current = _normalize(_SHAPES[piece]) | |
| result: list[Cells] = [] | |
| for _ in range(4): | |
| if current not in result: | |
| result.append(current) | |
| current = _normalize(tuple((-y, x) for x, y in current)) | |
| return tuple(result) | |
| def _bag(seed: int, index: int) -> tuple[str, ...]: | |
| # Each bag has its own local PRNG: random access does not depend on call order. | |
| # Freeze the shuffle algorithm as Fisher-Yates using Random.random(), whose | |
| # compatible-seeder sequence Python guarantees, rather than randrange(). | |
| rng = Random(f"{RULES_VERSION}:{seed}:{index}") | |
| bag = list(PIECES) | |
| for position in range(len(bag) - 1, 0, -1): | |
| other = int(rng.random() * (position + 1)) | |
| bag[position], bag[other] = bag[other], bag[position] | |
| return tuple(bag) | |
| def piece_at(seed: int, index: int) -> str: | |
| """Read an indexed piece without revealing or advancing global RNG state.""" | |
| if type(seed) is not int: | |
| raise ValueError("seed must be an integer") | |
| if type(index) is not int or index < 0: | |
| raise ValueError("piece index must be a nonnegative integer") | |
| bag_index, offset = divmod(index, len(PIECES)) | |
| return _bag(seed, bag_index)[offset] | |