Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import type { Color, Piece, PieceType } from '../types' /** * Zobrist hashing for efficient board state comparison and repetition detection. * Each combination of (piece type, color, square) gets a unique random number. * The hash of a position is the XOR of all piece hashes. */ // Zobrist hash table: [pieceType][color][square] => hash type ZobristTable = Record<PieceType, Record<Color, Record<string, bigint>>> // Single zobrist table instance (initialized lazily) let zobristTable: ZobristTable | null = null // Turn hash (XOR this when it's Black's turn) let turnHash: bigint | null = null /** * Simple seedable PRNG using xorshift128+ */ class SeededRandom { private state0: bigint private state1: bigint constructor(seed: number) { // Initialize state from seed this.state0 = BigInt(seed) this.state1 = BigInt(seed * 2 + 1) } next(): bigint { let s1 = this.state0 const s0 = this.state1 this.state0 = s0 s1 ^= s1 << 23n s1 ^= s1 >> 17n s1 ^= s0 s1 ^= s0 >> 26n this.state1 = s1 return (s0 + s1) & 0xffffffffffffffffn // 64-bit mask } } /** * Initialize the Zobrist hash table with deterministic random values. */ function initZobristTable(): ZobristTable { const rng = new SeededRandom(0x52495448) // "RITH" as seed const table: ZobristTable = { C: { W: {}, B: {} }, T: { W: {}, B: {} }, S: { W: {}, B: {} }, P: { W: {}, B: {} }, } const pieceTypes: PieceType[] = ['C', 'T', 'S', 'P'] const colors: Color[] = ['W', 'B'] // Generate hash for each (pieceType, color, square) combination for (const type of pieceTypes) { for (const color of colors) { for (let file = 0; file < 16; file++) { for (let rank = 1; rank <= 8; rank++) { const square = `${String.fromCharCode(65 + file)}${rank}` table[type][color][square] = rng.next() } } } } return table } /** * Get the Zobrist hash table (lazy initialization). */ function getZobristTable(): ZobristTable { if (!zobristTable) { zobristTable = initZobristTable() // Also initialize turn hash const rng = new SeededRandom(0x5455524e) // "TURN" as seed turnHash = rng.next() } return zobristTable } /** * Get the turn hash value. */ function getTurnHash(): bigint { if (turnHash === null) { getZobristTable() // This will also initialize turnHash } return turnHash! } /** * Compute the Zobrist hash for a board position. */ export function computeZobristHash(pieces: Record<string, Piece>, turn: Color): string { const table = getZobristTable() let hash = 0n // XOR all piece hashes for (const piece of Object.values(pieces)) { if (piece.captured) continue const pieceHash = table[piece.type][piece.color][piece.square] if (pieceHash) { hash ^= pieceHash } } // XOR turn hash if it's Black's turn if (turn === 'B') { hash ^= getTurnHash() } // Return as hex string return hash.toString(16).padStart(16, '0') } /** * Check if a hash appears N times in the history (for repetition detection). */ export function countHashOccurrences(hashes: string[], targetHash: string): number { return hashes.filter((h) => h === targetHash).length } /** * Check for threefold repetition (hash appears 3 times). */ export function isThreefoldRepetition(hashes: string[]): boolean { if (hashes.length < 3) return false const currentHash = hashes[hashes.length - 1] return countHashOccurrences(hashes, currentHash) >= 3 } /** * Incrementally update a Zobrist hash after a move. * This is more efficient than recomputing from scratch. */ export function updateZobristHash( currentHash: string, movedPiece: Piece, fromSquare: string, toSquare: string, capturedPiece: Piece | null, newTurn: Color ): string { const table = getZobristTable() let hash = BigInt(`0x${currentHash}`) // Remove moved piece from old square const oldPieceHash = table[movedPiece.type][movedPiece.color][fromSquare] if (oldPieceHash) { hash ^= oldPieceHash } // Add moved piece to new square const newPieceHash = table[movedPiece.type][movedPiece.color][toSquare] if (newPieceHash) { hash ^= newPieceHash } // Remove captured piece (if any) if (capturedPiece) { const capturedHash = table[capturedPiece.type][capturedPiece.color][capturedPiece.square] if (capturedHash) { hash ^= capturedHash } } // Toggle turn (XOR turn hash twice = no change, once = change) hash ^= getTurnHash() return hash.toString(16).padStart(16, '0') } |