Hashing de Zobrist: el algoritmo de 1970 que sigue vivo en el ajedrez
@programacionLo esencial
- El físico Albert Zobrist publicó la técnica en 1970 en la Universidad de Wisconsin.
- Asigna un número aleatorio de 64 bits a cada combinación de pieza y casilla del tablero.
- El hash de una posición se calcula con un XOR entre los números de las piezas presentes.
- Mover una pieza actualiza el hash con solo 2 operaciones XOR, sin recorrer todo el tablero.
- Motores como Stockfish lo usan para indexar la tabla de transposición y evitar reanalizar posiciones repetidas.
Qué es el hashing de Zobrist
Stockfish evalúa millones de posiciones por segundo y en cada una se hace la misma pregunta: ¿ya vi este tablero antes? La respuesta la da el hashing de Zobrist, una técnica que convierte un tablero completo en un solo número de 64 bits.
La inventó Albert Zobrist en 1970, en su artículo "A New Hashing Method with Application for Game Playing" durante su trabajo en la Universidad de Wisconsin. El objetivo original era simple: dar a cada posición de un juego de tablero una huella numérica única y barata de calcular, sin tener que comparar pieza por pieza contra todas las posiciones ya vistas.
Cómo funciona: un número aleatorio por pieza y casilla
Antes de jugar, el motor genera una tabla de números aleatorios de 64 bits: uno por cada combinación posible de tipo de pieza y casilla. Un tablero de ajedrez tiene 64 casillas y 12 tipos de pieza (6 piezas por 2 colores), así que la tabla base tiene 768 números. Se suman algunos extra para codificar los derechos de enroque, la casilla de captura al paso y el turno activo.
Esos números se generan una sola vez, al arrancar el programa, y se reutilizan durante toda la partida. El detalle técnico de esta tabla está documentado en Chess Programming Wiki, la referencia que usan la mayoría de los motores open source.
El hash de una posición es un XOR
Para calcular el hash de un tablero completo, el motor recorre cada pieza presente y aplica un XOR entre su número asignado y un acumulador que arranca en cero. El orden no importa: el XOR es conmutativo y asociativo, así que da igual si procesás las piezas de blancas primero o de negras.
La propiedad clave es que el XOR se cancela a sí mismo: aplicar el mismo número dos veces devuelve el valor original. Eso es lo que permite actualizar el hash sin recalcularlo desde cero cada vez que una pieza se mueve.
Código: hash de un tablero en Python
Un ejemplo simplificado de cómo se arma la tabla y se calcula el hash inicial:
import random
PIEZAS = [
"peon_blanco", "caballo_blanco", "alfil_blanco",
"torre_blanca", "dama_blanca", "rey_blanco",
"peon_negro", "caballo_negro", "alfil_negro",
"torre_negra", "dama_negra", "rey_negro",
]
random.seed(2026)
zobrist_tabla = [[random.getrandbits(64) for _ in range(64)] for _ in PIEZAS]
zobrist_turno = random.getrandbits(64)
def hash_posicion(tablero, turno_blancas):
hash_actual = 0
for casilla, pieza in tablero.items():
indice_pieza = PIEZAS.index(pieza)
hash_actual ^= zobrist_tabla[indice_pieza][casilla]
if turno_blancas:
hash_actual ^= zobrist_turno
return hash_actualEl diccionario tablero mapea cada casilla ocupada (0 a 63) a la pieza que contiene. La función recorre las piezas una vez y devuelve un entero de 64 bits que identifica esa posición exacta, incluido el turno.
Actualizar el hash al mover una pieza
Recalcular el hash completo en cada jugada sería desperdiciar el truco. La ventaja real de Zobrist es que mover una pieza solo exige deshacer su número en la casilla de origen y aplicar el mismo número en la de destino:
def mover_pieza(hash_actual, pieza, casilla_origen, casilla_destino):
indice_pieza = PIEZAS.index(pieza)
hash_actual ^= zobrist_tabla[indice_pieza][casilla_origen]
hash_actual ^= zobrist_tabla[indice_pieza][casilla_destino]
return hash_actualDos operaciones XOR bastan para actualizar el hash, en vez de recorrer las 64 casillas otra vez. Si la jugada es una captura, se agrega un tercer XOR para quitar la pieza capturada de la casilla de destino antes de colocar la pieza que se mueve.
Por qué importa: las tablas de transposición
En ajedrez, la misma posición se puede alcanzar por distintos órdenes de jugadas: eso se llama transposición. Un motor que no detecta transposiciones vuelve a analizar la misma posición una y otra vez, desperdiciando tiempo de búsqueda.
Motores como Stockfish guardan los resultados de cada posición ya analizada en una tabla de transposición, indexada justamente por el hash de Zobrist. Cuando la búsqueda llega a una posición cuyo hash ya está en la tabla, reutiliza la evaluación guardada en vez de recalcularla.
Colisiones y el límite real de la técnica
Con números de 64 bits, dos posiciones distintas pueden terminar con el mismo hash: es una colisión. Por la paradoja del cumpleaños, la probabilidad de una colisión se vuelve apreciable recién cerca de 2^32 (unos 4.000 millones) de posiciones distintas almacenadas, muy por encima de lo que cabe en una tabla de transposición típica de unos cientos de megabytes.
El gotcha real no es la colisión matemática sino la implementación: si al hash le faltan los derechos de enroque, la casilla de captura al paso o el turno activo, dos posiciones legítimamente distintas comparten hash y el motor devuelve una evaluación equivocada. Por eso ningún motor serio usa Zobrist únicamente sobre las piezas; siempre incluye esos tres componentes extra.
Conclusión
El hashing de Zobrist no nació para el ajedrez en particular, sino para cualquier juego cuyo estado se pueda descomponer en piezas independientes: funciona igual en Go, en Othello y hasta en generación de mundos de Minecraft, donde se usa para identificar chunks. La idea central (asignar números aleatorios fijos y combinarlos con XOR) sigue siendo, 56 años después, la forma más barata de saber si un sistema ya pasó por un estado determinado.
📖 Versión extendida con más detalle: https://elsolitario.org/2026/10/02/hashing-de-zobrist-ajedrez-explicado/?utm_source=telegraph&utm_medium=instant_view&utm_campaign=programacion