nima1's picture
Publish verified checkpoint and losslessly compressed study evidence
4be6a52 verified
Raw History Blame Contribute Delete
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))
@lru_cache(maxsize=7)
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)
@lru_cache(maxsize=4096)
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]