JavaScript chess move generator with strictly legal move generation
English | Español
- ✅ Strictly legal move generation - No pseudo-moves requiring post-validation
- ✅ 100% Perft Accuracy - All implementations verified against standard test suites
- ✅ 5 Multi-language implementations: JS (x88, Bitboard), AssemblyScript (x88), Rust (x88, Bitboard)
- ✅ High Performance: Up to 25M+ NPS with Rust Bitboards in the browser
- ✅ Complete UCI engine - Compatible with standard chess interfaces
- ✅ Advanced web interface - Visual demo with engine selector (JS, AS, Rust)
- ✅ Web Workers - Calculations without blocking the UI
Or open visualizer/engine.html in your browser locally.
npm install chess-movegen-jsconst { Board } = require('chess-movegen-js');
const board = new Board();
board.loadFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
board.generateMoves();
console.log(`Legal moves: ${board.moves.length}`); // 20If you are already using chess.js, you can use ChessJSAdapter as a high-performance drop-in replacement. It provides the same API (.moves(), .move(), .fen(), .isCheck(), etc.) but uses the x88 engine under the hood, making it ~50x faster.
const { ChessJSAdapter } = require('chess-movegen-js/chessjs');
// Works exactly like chess.js
const chess = new ChessJSAdapter();
chess.move('e4');
console.log(chess.fen());
console.log(chess.moves()); // Standard Algebraic Notation (SAN)Performance measured in Node.js (Depth 4 avg):
| Generator | NPS (Nodes Per Second) | Speedup vs chess.js |
|---|---|---|
| chess.js (Original) | ~75,000 | 1x (Base) |
| ChessJSAdapter (x88) | ~3,500,000 | 47x Faster |
| JS x88 Generator | ~3,500,000 | 47x Faster |
| AssemblyScript x88 | ~3,800,000 | 51x Faster |
| Rust X88 | ~10,800,000 | 145x Faster |
| Rust Bitboard | ~15,800,000 | 213x Faster |
// Create a board
const board = new Board();
// Load a FEN position
board.loadFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
// Generate all legal moves
board.generateMoves();
// View the moves
console.log(board.moves);Moves include tactical information:
board.generateMoves();
board.moves.forEach(move => {
const moveStr = board.getMoveStr(move);
console.log(moveStr);
// Tactical information in move.mask:
// - mask_check: Gives check
// - mask_safe: Safe square
// - mask_hanging: Piece would be hanging
// - mask_freecapture: Undefended capture
// - mask_winningcapture: Winning capture
});// Make a move
const move = board.moves[0];
board.makemove(move);
// Unmake
board.undomove();// Count nodes at depth 5
const nodes = board.perft(5);
console.log(`Nodes: ${nodes}`); // 4,865,609 from initial position
// Divide (show nodes per move)
board.divide(4);movegen/
├── js/
│ ├── x88.js # x88 representation generator (JS)
│ ├── bitboard.js # Bitboard generator (JS)
│ └── magic-tables.js # Magic tables for bitboard
├── rust-movegen/ # Rust implementation (x88 & Bitboard, WASM)
├── asmovegen/ # AssemblyScript implementation (x88, WASM)
├── visualizer/ # Interactive web interface
│ ├── engine.html # Main demo page
│ └── js/ # Engine workers and UI logic
├── tests/ # Comprehensive Perft test suite
├── ANALISIS.md # Detailed technical analysis
└── README.md # This file
The project includes a complete UCI engine running in a Web Worker:
// Create engine
const w = new Worker("js/engine.js");
// UCI communication
w.postMessage('uci');
w.postMessage('position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
w.postMessage('perft 6');
// Listen for responses
w.onmessage = function(event) {
console.log(event.data);
};uci- Initialize engineisready- Check availabilityucinewgame- New gameposition [fen|startpos] [moves ...]- Set positionmove <move>- Make move (e.g., e2e4)undo- Unmake moveperft <depth>- Move generation test
Benchmarks from initial position (Depth 5):
| Implementation | Platform | NPS |
|---|---|---|
| Rust Bitboard | WASM/Browser | ~25.8M |
| Rust x88 | WASM/Browser | ~18.0M |
| AssemblyScript | WASM/Browser | ~12.5M |
| JS x88 | Node.js / Browser | ~5.6M |
| JS Bitboard | Node.js / Browser | ~4.2M |
Perft from initial position (Node.js v20+, no debug): All versions pass 100% of Perft tests up to depth 6+.
Pinned pieces are detected during generation. Illegal moves are never generated:
// pinDirection[side][square] indicates if a piece is pinned
// and in which directionEach move contains flags indicating:
- If it gives check or checkmate
- If the piece would be hanging
- If it's a winning capture
- If it's a safe square
- ✅ En passant capture with horizontal pins
- ✅ Discovered checks (including in castling)
- ✅ Mate-in-one detection
- ✅ Multiple promotions
- 128-position array (16×8)
- Ultra-fast validation:
if (sq & 0x88) continue - More readable and easier to understand code
- Available in JS, AssemblyScript, and Rust.
- 64-bit bitboard representation
- Faster and optimized for modern CPUs
- Uses Magic Bitboards for sliding pieces
- Available in JS and Rust.
For a complete technical analysis of the code, see ANALISIS.md.
The project uses Perft to validate move generation:
// From browser console in engine.html
w.postMessage('perft 5');
// Or in code
const board = new Board();
board.loadFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
console.log(board.perft(5)); // Should be 4,865,609The project includes a comprehensive Perft test suite to validate move generation correctness and measure performance.
# Run quick test suite (depths 1-4, ~1 minute)
node tests/perft-test.js --quick
# Test specific position
node tests/perft-test.js --position 0 --depth 5
# Test specific generator
node tests/perft-test.js --generator rust-bb --depth 6node tests/perft-test.js [options]
Options:
--generator <x88|bb|as|rust-x88|rust-bb|all> Select generator to test (default: all)
--position <n> Test only position n (default: all)
--depth <n> Test up to depth n (default: 6)
--quick Quick test mode (depths 1-4)
--help Show help messageThe test suite includes 7 standard positions from Chess Programming Wiki:
| Position | Description | Max Depth Tested |
|---|---|---|
| 0 | Initial position | 10 |
| 1 | Kiwipete (complex middle game) | 6 |
| 2 | En passant edge cases | 8 |
| 3 | Promotions | 6 |
| 4 | Promotions (mirrored) | 6 |
| 5 | Complex tactical position | 5 |
| 6 | Symmetrical position | 6 |
Chess Move Generator - Perft Test Suite
Configuration:
Generator: all
Positions: 7
Max depth: 4
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total tests: 140 (5 generators * 7 positions * 4 depths)
Passed: 140
Failed: 0
Pass rate: 100.0%
- Automated tests with Perft suite ✅
- Publish as NPM package ✅
- WebAssembly optimization (AssemblyScript & Rust WASM) ✅
- Repetition check with Zobrist hashing
- Badge: Added at the top of this
README. - Workflow: The
CIworkflow automatically runs quick tests (npm test) onpushandpull_request. Full bitboard tests (npm run test:bb) are configured for manual execution to avoid long runs on every push.
How to launch the full test from GitHub UI:
- Go to the Actions tab of the repository.
- Select the
CIworkflow. - Click Run workflow, choose the branch (
mainormaster), and execute.
Using the gh CLI (GitHub CLI):
gh workflow run ci.yml --ref mainNote: The CI workflow includes a matrix of Node versions for quick tests and reserves a manual job (test-bitboard-full) with a longer timeout for intensive testing.
Contributions are welcome. Please:
- Fork the project
- Create a branch for your feature (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is under the MIT License - see the LICENSE file for details.
Mario Raúl Carbonell Martínez
⭐ If you find this project useful, consider giving it a star on GitHub!