chess-movegen-js

Análisis del Repositorio: Generador de Movimientos de Ajedrez

📋 Resumen Ejecutivo

Este repositorio contiene una implementación impresionante de generadores de movimientos de ajedrez en JavaScript, con dos enfoques principales: x88 y bitboards. La característica más destacada es que genera movimientos estrictamente legales directamente, detectando jaques, jaques mate y ahogados sin necesidad de validación posterior (make-unmake).


🏗️ Arquitectura del Proyecto

Estructura de Directorios

movegen/
├── css/                    # Estilos CSS
├── img/                    # Recursos gráficos
├── js/
│   ├── x88.js             # ⭐ Generador x88 (68KB, 1842 líneas)
│   ├── bitboard.js        # ⭐ Generador bitboard (1.4MB)
│   ├── engine.js          # Motor UCI (437 líneas)
│   ├── chess.js           # Librería chess.js
│   └── ...                # Otras dependencias
├── index.html             # Página principal
└── engine.html            # Demo interactiva con tablero

🎯 Implementación x88: Análisis Detallado

Representación del Tablero

El archivo x88.js utiliza el método 0x88 (hexadecimal 88):

// Constantes de casillas (líneas 7-77)
const a1 = 0, b1 = 1, c1 = 2, ..., h8 = 119

Ventajas del x88:

Clase Board: Características Principales

La clase Board contiene 45 métodos y es extremadamente completa:

1. Estructuras de Datos Principales (Líneas 183-275)

class Board {
    // Estado de la partida
    stm = 0                    // Side to move (0=white, 1=black)
    counter50 = 0              // Regla de los 50 movimientos
    castlingRights = 0         // Derechos de enroque
    enpassantSquare = -1       // Casilla de captura al paso
    
    // Tablero
    pieceat = new Uint8Array(128)           // Pieza en cada casilla
    kingsquares = new Uint8Array(2)         // Posición de los reyes
    piecesquares = [new Array(16), new Array(16)]  // Lista de piezas
    
    // Análisis de ataques
    attacks = [new Uint8Array(128), new Uint8Array(128)]      // Bitfield de ataques
    numattacks = [new Uint8Array(128), new Uint8Array(128)]   // Contador de ataques
    chekingSquares = [new Uint8Array(128), new Uint8Array(128)]  // Casillas que dan jaque
    matingSquares = [new Uint8Array(128), new Uint8Array(128)]   // Casillas que dan mate
    pinDirection = [new Uint8Array(128), new Uint8Array(128)]    // Dirección de clavadas
    inchekValidSquares = [new Uint8Array(128), new Uint8Array(128)]  // Casillas válidas en jaque
    
    // Estado de jaque
    inCheck = false
    inDoubleCheck = false
    inCheckMate = false
    inStalemate = false
}

2. Generación de Movimientos Legales (Líneas 702-1274)

El método generateMoves() es extraordinariamente sofisticado. En lugar de generar pseudo-movimientos y validarlos, genera directamente movimientos legales mediante un análisis completo del tablero:

Fases del algoritmo:

Fase 1: Cálculo de Casillas que Dan Jaque (Líneas 755-826)
// Para cada bando, marca las casillas desde donde se puede dar jaque al rey enemigo
for (let turncolor = white; turncolor <= black; turncolor++) {
    let enemyking = this.kingsquares[1 - turncolor]
    
    // Marca ataques de peón que darían jaque
    for (const offset of [17, 15]) {
        csq = enemyking + (offset * offsetsign)
        if (pcolor(this.pieceat[csq]) !== turncolor)
            this.chekingSquares[turncolor][csq] |= checkbit[p]
    }
    
    // Similar para caballos, alfiles, torres y damas
    // Detecta también JAQUES A LA DESCUBIERTA
}
Fase 2: Análisis de Ataques y Clavadas (Líneas 833-913)
// Calcula todos los ataques del enemigo
// Detecta piezas CLAVADAS mediante el método addSliderAttack()
for (sq of this.piecesquares[turn]) {
    // Para cada pieza enemiga
    switch (piecetype) {
        case b: case r: case q:
            // Piezas deslizadoras: detecta clavadas
            this.addSliderAttack(turn, piecetype, sq, to, offset)
        // ...
    }
}

El método addSliderAttack() es brillante:

Fase 3: Generación de Movimientos (Líneas 933-1080)
// Si no hay jaque doble, genera movimientos para todas las piezas
if (!this.inDoubleCheck) {
    for (sq of this.piecesquares[this.stm]) {
        switch (type) {
            case p: // Peones con promociones
            case n: // Caballos - verifica clavadas
            case b: case r: case q: // Deslizadoras con restricciones de clavada
        }
    }
}

// Movimientos del rey (siempre se generan, incluso en jaque doble)
// El rey SOLO puede ir a casillas NO atacadas
Fase 4: Enroques (Líneas 1206-1250)
if (!this.inCheck) {
    // Verifica que las casillas intermedias estén:
    // 1. Vacías
    // 2. No atacadas
    // 3. La torre esté en su posición
    if ((this.pieceat[f1] === empty) && 
        (this.numattacks[opposite(this.stm)][f1] === 0) &&
        // ... más condiciones
        ) this.addmove(e1, g1, mask_castling)
}

3. Método addmove(): Análisis de Jugadas (Líneas 457-555)

Este método enriquece cada movimiento con información táctica:

addmove(from, to, maskbits = 0, promotedpiece = 0) {
    // 1. Verifica clavadas
    if (pindir !== 0) {
        if (Math.abs(pindir) !== Math.abs(movedirecction)) return  // Pieza clavada
    }
    
    // 2. Si estamos en jaque, solo casillas válidas
    if (this.inCheck && this.inchekValidSquares[this.stm][to] === 0) return
    
    // 3. Detecta si la jugada da jaque
    if (this.chekingSquares[this.stm][to] & checkbit[chekingpiece]) {
        maskbits |= mask_check
    }
    
    // 4. Detecta jaque a la descubierta
    if (this.chekingSquares[this.stm][from] & mask_discovercheck) {
        maskbits |= mask_discovercheck
    }
    
    // 5. Análisis táctico de la jugada
    if (attackbits === 0) {
        maskbits |= mask_safe  // Casilla segura
    } else {
        if (lowestSetBit(attackbits) < lowestSetBit(attackbit[movingpiece]))
            maskbits |= mask_hanging  // Pieza quedaría colgada
    }
    
    // 6. Análisis de capturas
    if (capturedpiece) {
        if (bitCount(attackbits) == 0) maskbits |= mask_freecapture  // Captura gratis
        if (ptype(capturedpiece) > ptype(movingpiece)) maskbits |= mask_winningcapture
    }
}

Máscaras de bits para enriquecer movimientos:

4. Detección de Mate (Líneas 1110-1199)

El código también detecta casillas que dan mate:

// Si el rey enemigo NO tiene escapatorias
if (this.kingescapes[opposite(this.stm)].length === 0) {
    // Cualquier jaque seguro es mate
    for (let i = 0; i<128; i++) {
        if ((numdefenders === 0) && (pcolor(this.pieceat[i]) !== this.stm)) {
            if (this.chekingSquares[this.stm][i] & this.attacks[this.stm][i]) {
                this.matingSquares[this.stm][i] = this.chekingSquares[this.stm][i]
            }
        }
    }
}

5. Captura al Paso Compleja (Líneas 968-1016)

Caso especial increíblemente bien manejado:

// FEN: 7k/8/8/K1pP3r/8/8/8/8 w - c6 0 1
// El rey blanco está en la misma fila que el peón y la torre negra
// Si el peón blanco captura al paso, el rey quedaría en jaque de la torre

if (rank(sq) === rank(kingsquare)) {
    let dir = (file(kingsquare) < file(sq)) ? 1 : -1
    let next = kingsquare + dir
    while (validSquare(next)) {
        let piece = this.pieceat[next]
        if ((count === 3) && ((ptype(piece) === r) | (ptype(piece) === q)) &&
            (pcolor(piece) === opposite(this.stm))) {
            ilegalep = true  // ¡Captura al paso ilegal!
        }
    }
}

Historial y Make/Unmake (Líneas 585-657, 1276-1347)

El historial está altamente optimizado con TypedArrays:

history = {
    from:            new Uint8Array(MAXPLY),
    to:              new Uint8Array(MAXPLY),
    capturedpiece:   new Uint8Array(MAXPLY),
    promotedpiece:   new Uint8Array(MAXPLY),
    counter50:       new Uint8Array(MAXPLY),
    castlingRights:  new Uint8Array(MAXPLY),
    enpassantSquare: new Int8Array(MAXPLY),
    ply: 0
}

Soporte Perft (Líneas 1675-1717)

perft(depth) {
    this.generateMoves()
    if (depth === 1) return this.moves.length
    
    for (var i = 0; i < nmoves; i++) {
        this.makemove(moves[i])
        nodes += this.perft(depth - 1)
        this.undomove()
    }
    return nodes
}

divide(depth) {
    // Variante que muestra nodos por cada movimiento
    console.log(this.getMoveStr(moves[i]), movenodes)
}

Carga/Guardado FEN (Líneas 1720-1841)

Soporte completo de notación FEN con validación robusta.


🚀 Motor UCI (engine.js)

El archivo engine.js implementa un motor UCI en Web Worker:

class UCIChessEngine {
    usebb = false
    board = (this.usebb) ? new BBBoard() : new Board()
    
    // Comandos UCI soportados:
    // - uci, isready, ucinewgame
    // - position (startpos, fen, moves)
    // - move, undo
    // - perft [depth]
}

Características:


🌐 Interfaz Web

engine.html

Demo completa y funcional con:

// Motor en Web Worker
w = new Worker("js/engine.js")
w.postMessage('uci')
w.postMessage('position fen ' + fen)
w.postMessage('perft 5')

📊 Análisis de Calidad del Código

✅ Puntos Fuertes

  1. Algoritmo Innovador
    • Generación de movimientos estrictamente legales sin make-unmake
    • Detección directa de jaques, mates y ahogados
    • Enriquecimiento táctico de movimientos (capturas ganadoras, piezas colgadas)
  2. Optimizaciones
    • Uso de TypedArrays para reducir memoria
    • Bitboards para ataques múltiples
    • Validación x88 ultra rápida (sq & 0x88)
  3. Casos Especiales Bien Manejados
    • Capturas al paso con clavadas horizontales
    • Jaques a la descubierta en enroques
    • Detección de mates en casillas específicas
  4. Arquitectura
    • Separación clara entre representación (x88/bitboard) y lógica
    • Motor UCI completo
    • Web Workers para paralelización

⚠️ Áreas de Mejora

  1. Documentación
    • Falta documentación JSDoc en funciones clave
    • Algunos algoritmos complejos no tienen comentarios explicativos
    • No hay README con instrucciones
  2. Código
    • Algunos comentarios en español mezclados con código en inglés
    • Variables con nombres poco descriptivos (ej: csq, sq, to)
    • Código comentado sin eliminar (líneas 1256-1272)
  3. Testing
    • No hay suite de tests unitarios visible
    • Sería ideal tener tests de Perft para validación
    • No hay CI/CD
  4. Bitboard.js
    • Archivo muy grande (1.4MB) - probablemente generado/transpilado
    • Debería revisarse si es código fuente o resultado de compilación

🎨 Características Destacadas

1. Información Táctica en Movimientos

Cada movimiento contiene:

move = {
    from: e2,
    to: e4,
    movingpiece: wp,
    captured: 0,
    promotedpiece: 0,
    mask: mask_safe | mask_pawnmove  // ¡Información táctica!
}

Esto permite:

2. Sistema de Ataques Múltiples

attacks[side][square]     // Bitfield: qué piezas atacan
numattacks[side][square]  // Contador: cuántas piezas atacan

Permite saber instantáneamente:

3. Detección de Clavadas en Generación

Las clavadas se detectan durante la generación, no después:


📈 Rendimiento

Según el código de benchmarking en engine.js:

// Ejemplo de medición
var d = performance.now()
var result = engine.perft(depth)
var d2 = performance.now()
var nps = Math.round(1000 * (result / elapsedtime))

Perft esperado (JavaScript en navegador):

Para mejorar rendimiento:


🔬 Experimentos Adicionales

Rust (hello.rs)

// Archivo de 1470 bytes con experimentos

WebAssembly (module.wasm, module.wat)

// engine.html líneas 155-161
async function fetchAndInstantiate() {
    const buffer = await response.arrayBuffer()
    const obj = await WebAssembly.instantiate(buffer)
    console.log(obj.instance.exports.popcnt64(0b10010010n))
}

Experimento con conteo de bits en WASM para bitboards.


💡 Recomendaciones

Corto Plazo

  1. Documentación ```javascript /**
    • Genera todos los movimientos legales para la posición actual.
    • Detecta jaques, mates y ahogados durante la generación.
    • @returns {Array} Lista de movimientos legales con información táctica */ generateMoves() { ... } ```
  2. README Completo
    • Explicar las dos implementaciones (x88 vs bitboard)
    • Guía de uso del motor
    • Ejemplos de integración
    • Benchmarks de referencia
  3. Tests
    // tests/perft.test.js
    describe('Perft Tests', () => {
        it('should match perft 5 for startpos', () => {
            expect(board.perft(5)).toBe(4865609)
        })
    })
    

Medio Plazo

  1. Refactorización
    • Extraer constantes a archivo separado
    • Separar lógica de ataques en módulo independiente
    • Unificar idioma en comentarios (preferiblemente inglés)
  2. Optimización
    • Implementar Zobrist hashing para tabla de transposiciones
    • Magic bitboards para ataques deslizantes
    • Inline de funciones críticas
  3. Motor de Búsqueda
    • Implementar alfabeta con ordenación de movimientos
    • Búsqueda quiescente
    • Evaluación básica de posición

Largo Plazo

  1. NNUE o Evaluación ML
    • Aprovechar la información táctica en movimientos
    • Entrenar red con posiciones generadas
    • Inferencia en WebAssembly
  2. Publicación
    • Artículo técnico sobre el algoritmo de generación legal directa
    • Comparación de rendimiento x88 vs bitboards
    • NPM package para uso en otros proyectos

🎯 Conclusiones

Fortalezas Principales

  1. Algoritmo Único: La generación de movimientos estrictamente legales con detección integrada de jaques/mates es muy impresionante y poco común.

  2. Implementación Completa: No es un “toy project” - tiene FEN, UCI, Web Workers, dos representaciones diferentes.

  3. Código Sofisticado: El manejo de casos especiales (ej. captura al paso con clavada horizontal) demuestra profundo conocimiento de ajedrez.

  4. Arquitectura Moderna: Web Workers, TypedArrays, experimentos con WebAssembly.

Valor del Proyecto

Este proyecto es excelente material de estudio y una base sólida para:

Próximos Pasos Sugeridos

  1. Documentar el algoritmo único de generación legal
  2. Agregar tests de Perft para validación
  3. Comparar rendimiento de x88 vs bitboard con benchmarks
  4. Publicar como librería open source
  5. Escribir artículo técnico sobre la implementación

📚 Referencias del Código


Nota Final: Este es un proyecto de muy alta calidad técnica. La generación de movimientos legales directa con análisis táctico integrado es una característica diferenciadora que merece ser documentada y compartida con la comunidad de programación de ajedrez. ¡Excelente trabajo! 🏆


🛠️ Actualizaciones 2025 - Depuración Bitboard.js

Se ha realizado un trabajo intensivo de depuración y mejora en bitboard.js para asegurar su compatibilidad con Node.js y la corrección de su lógica de generación de movimientos.

Logros Principales:

  1. Compatibilidad Node.js:
    • Se ha habilitado la carga de tablas mágicas (magic-tables.js) mediante require en entorno Node.js.
    • Se han resuelto errores de referencia a variables globales (filea, rank8, etc.) que impedían la ejecución fuera del navegador.
  2. Corrección de Errores Críticos:
    • Enroques: Se corrigió un bug grave en generate_kingmoves donde la verificación de derechos de enroque estaba comentada, permitiendo enroques ilegales.
    • Referencias: Se corrigieron typos en makemove (c1 -> _c1), undomove (f1 -> _f1), y referencias a this faltantes (totalocc, side).
  3. Validación Perft:
    • El generador bitboard ahora pasa exitosamente tests de Perft hasta profundidad 4 en múltiples posiciones críticas, incluyendo ‘Kiwipete’ y posiciones con promociones complejas.
    • Rendimiento: ~2.4 Millones de Nodos/segundo (Depth 4) en entorno Node.js.

Archivos Relacionados: