/** * Deck creation and deterministic shuffling for the poker engine. * Pure: randomness is always injected; no module-level state. */ import type { Card, Rank, Suit } from "./types"; const SUITS: readonly Suit[] = ["s", "h", "d", "c"]; const MIN_RANK: Rank = 2; const MAX_RANK: Rank = 14; /** A fresh, ordered 52-card deck (order is deterministic before shuffling). */ export function createDeck(): Card[] { const deck: Card[] = []; for (const suit of SUITS) { for (let rank = MIN_RANK; rank <= MAX_RANK; rank += 1) { deck.push({ rank: rank as Rank, suit }); } } return deck; } /** * Small, fast seeded PRNG (mulberry32). Used so a whole multi-hand game can * be replayed from a single integer seed stored in the table state. */ export function mulberry32(seed: number): () => number { let a = seed >>> 0; return () => { a = (a + 0x6d2b79f5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } /** Fisher-Yates shuffle; returns a new array, never mutates the input. */ export function shuffleDeck(cards: readonly Card[], rng: () => number): Card[] { const deck = [...cards]; for (let i = deck.length - 1; i > 0; i -= 1) { const j = Math.floor(rng() * (i + 1)); const tmp = deck[i]; deck[i] = deck[j]; deck[j] = tmp; } return deck; } const GOLDEN_RATIO_32 = 0x9e3779b9; /** Per-hand seed derived from the table's base seed and the hand number. */ export function handSeed(baseSeed: number, handNumber: number): number { return (baseSeed + Math.imul(handNumber, GOLDEN_RATIO_32)) >>> 0; } /** The exact shuffled deck used for a given hand of a given game seed. */ export function createShuffledDeck(baseSeed: number, handNumber: number): Card[] { return shuffleDeck(createDeck(), mulberry32(handSeed(baseSeed, handNumber))); }