
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
js-chess-engine
Advanced tools
Simple and fast Node.js chess engine with configurable AI and no dependencies
Complete TypeScript chess engine without dependencies for Node.js >=24 and browsers. Includes configurable AI with difficulty levels 1-5.
⚠️ Upgrading from v1? See Breaking Changes section at the end of this document
Install with npm
npm i js-chess-engine --save
or install with yarn
yarn add js-chess-engine
Node.js Requirement: Node.js >=24 is required for v2.
// Import Game class and stateless functions
import { Game, moves, status, move, ai, getFen } from 'js-chess-engine'
// Import types for TypeScript
import type { BoardConfig, PieceSymbol, MovesMap, AILevel } from 'js-chess-engine'
const game = new Game()
const { Game, moves, status, move, ai, getFen } = require('js-chess-engine')
const game = new Game()
Two full example applications are available:
--> Server — js-chess-engine-app — React + Node.js REST API, engine runs on the server — LIVE DEMO
->> Browser — js-chess-engine-fe-app — Pure frontend React app, engine runs entirely in the browser, no server needed — LIVE DEMO
You have two options for using this engine:
Both options use the same transposition table cache for AI performance. The difference is that the Game class stores the board state internally and tracks move history, while stateless functions require a board configuration on each call.
Use the Game class to manage chess game state. All methods use the internally stored board — no need to pass configuration each time. The class also tracks full move history.
import { Game } from 'js-chess-engine'
import type { BoardConfig, MovesMap } from 'js-chess-engine'
const game = new Game()
You can export your game to JSON or FEN at any time and use these formats to restore your game later.
constructor
new Game(configuration) - Create a new game with optional initial configuration.
Params:
configuration BoardConfig | string (optional) - Chess board configuration (JSON object or FEN string). Default is standard starting position.import { Game } from 'js-chess-engine'
import type { BoardConfig } from 'js-chess-engine'
// New game with starting position
const game1 = new Game()
// From FEN string
const game2 = new Game('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1')
// From BoardConfig object
const config: BoardConfig = { /* ... */ }
const game3 = new Game(config)
move
game.move(from, to) - Perform a move on the chessboard and recalculate game state. Returns full BoardConfig object (breaking change from v1).
Params:
from string (mandatory) - Starting square (case-insensitive, e.g., 'E2' or 'e2')to string (mandatory) - Destination square (case-insensitive, e.g., 'E4' or 'e4')Returns: BoardConfig - Full board configuration after the move
import type { BoardConfig } from 'js-chess-engine'
const newConfig: BoardConfig = game.move('E2', 'E4')
console.log(newConfig.pieces) // {"E4": "P", "E1": "K", ...}
moves
game.moves(from?) - Get all legal moves for the current player. Optionally filter by a specific square.
Params:
from string (optional) - Square to filter moves from (case-insensitive, e.g., 'E2'). If omitted, returns moves for all pieces.Returns: MovesMap - Object mapping from-squares to arrays of to-squares
import type { MovesMap } from 'js-chess-engine'
// Get all moves (no parameter)
const allMoves: MovesMap = game.moves()
// {"E2": ["E3", "E4"], "B1": ["A3", "C3"], ...}
// Get moves for specific square
const pawnMoves: MovesMap = game.moves('E2')
// {"E2": ["E3", "E4"]}
setPiece
game.setPiece(location, piece) - Add or replace a chess piece at the specified location.
Params:
location string (mandatory) - Square location (case-insensitive, e.g., 'E2')piece PieceSymbol (mandatory) - Piece symbol using FEN notation (K, Q, R, B, N, P for white; k, q, r, b, n, p for black)import type { PieceSymbol } from 'js-chess-engine'
const piece: PieceSymbol = 'Q'
game.setPiece('E5', piece)
removePiece
game.removePiece(location) - Remove a piece from the specified location.
Params:
location string (mandatory) - Square location (case-insensitive, e.g., 'E2')game.removePiece('E5')
aiMove
⚠️ DEPRECATED: This method will be removed in v3.0.0. Use
ai()instead, which returns both the move and board state.
game.aiMove(level) - Calculate and perform the best move for the current player using AI. Returns only the move (v1 API compatible).
Params:
level AILevel (optional) - AI difficulty level (1-5). See Computer AI section. Default: 3Returns: HistoryEntry - The played move (e.g., {"E2": "E4"})
import type { HistoryEntry, AILevel } from 'js-chess-engine'
const level: AILevel = 4
const move: HistoryEntry = game.aiMove(level)
console.log(move) // {"E2": "E4"}
// To get board state after move, use exportJson()
const board = game.exportJson()
// RECOMMENDED: Use ai() instead
const result = game.ai({ level: 4 })
console.log(result.move) // {"E2": "E4"}
console.log(result.board) // Full board state
ai
game.ai(options?) - Calculate the best move using AI. Returns both the move and board state.
Params:
options object (optional) - Configuration options:
level number (optional) - AI difficulty level (1-5). See Computer AI section. Default: 3play boolean (optional) - Whether to apply the move to the game. Default: true. If false, returns the move without modifying the game state, and board will contain the current state (before the move).analysis boolean (optional) - If true, also returns an analysis payload containing all root legal moves scored by the engine's search (sorted best → worst). Default: false.ttSizeMB number (optional) - Transposition table size in MB (0 to disable, min 0.25 MB). Default: auto-scaled by AI level. See Auto-Scaling Transposition Table for details.randomness number (optional) - Centipawn threshold for move variety. The engine picks randomly among all moves scoring within this many centipawns of the best move. Makes games less predictable without playing blunders. Default: 0 (fully deterministic). Reference values: 10 very subtle · 30 slight variety · 80 noticeable · 200 chaotic.depth object (optional) - Override AI search depth parameters. Omitted fields fall back to the level's defaults (see Computer AI table).
base number (optional) - Base search depth. Integer > 0.extended number (optional) - Max adaptive extension depth. Integer 0-3.check boolean (optional) - Enable check extensions.quiescence number (optional) - Quiescence search depth. Integer >= 0.Returns: { move: HistoryEntry, board: BoardConfig, analysis?: Array<{ move: HistoryEntry, score: number }>, depth?: number, nodesSearched?: number, bestScore?: number } - Object containing the move and board state (current state if play=false, updated state if play=true). Extra fields are returned only when analysis: true.
import type { HistoryEntry, BoardConfig } from 'js-chess-engine'
// Play the move (default behavior)
const result1 = game.ai({ level: 4 })
console.log(result1.move) // {"E2": "E4"}
console.log(result1.board.turn) // "black" (updated after move)
// Analysis mode: get move without applying it
const result2 = game.ai({ level: 4, play: false })
console.log(result2.move) // {"E2": "E4"}
console.log(result2.board.turn) // "white" (current state, before move)
// Root move scoring (debug/inspection)
const result2b = game.ai({ level: 4, play: false, analysis: true })
console.log(result2b.analysis?.slice(0, 5)) // [{ move: {"E2": "E4"}, score: 12 }, ...]
console.log(result2b.bestScore)
console.log(result2b.depth)
console.log(result2b.nodesSearched)
// Use default level 3
const result3 = game.ai()
console.log(result3.move) // AI move with level 3
// TT size auto-scales by level (see Auto-Scaling Transposition Table section)
const result4 = game.ai({ level: 5 })
console.log(result4.move) // Level 5: 40 MB Node.js / 20 MB browser (auto)
// Override TT size manually if needed
const result5 = game.ai({ level: 3, ttSizeMB: 128 })
console.log(result5.move) // Force 128MB cache
// Ultra-lightweight mode for low-end devices
const result6 = game.ai({ level: 2, ttSizeMB: 0.5 })
console.log(result6.move) // Force 512KB cache
// Custom depth overrides (use level 3 defaults, but increase base depth)
const result7 = game.ai({ level: 3, depth: { base: 5 } })
// Full depth control: deep search, no extensions, no quiescence
const result8 = game.ai({ level: 1, depth: { base: 4, extended: 0, check: false, quiescence: 0 } })
// Randomness: vary move selection among nearly-equal scoring moves (default: 0)
const result9 = game.ai({ level: 3 }) // fully deterministic (default)
const result10 = game.ai({ level: 3, randomness: 10 }) // very subtle variety
const result11 = game.ai({ level: 3, randomness: 80 }) // noticeable variety, casual play
getHistory
game.getHistory() - Get all played moves with board states.
Returns: Array of objects containing move and resulting board configuration
const history = game.getHistory()
// [
// { move: {"E2": "E4"}, pieces: {...}, turn: "black", ... },
// { move: {"E7": "E5"}, pieces: {...}, turn: "white", ... }
// ]
printToConsole
game.printToConsole() - Print an ASCII representation of the chessboard to console.
game.printToConsole()
// +---+---+---+---+---+---+---+---+
// 8 | r | n | b | q | k | b | n | r |
// +---+---+---+---+---+---+---+---+
// 7 | p | p | p | p | p | p | p | p |
// +---+---+---+---+---+---+---+---+
// ...
exportJson
game.exportJson() - Export current game state as a JSON BoardConfig object.
Returns: BoardConfig - Full board configuration
import type { BoardConfig } from 'js-chess-engine'
const config: BoardConfig = game.exportJson()
exportFEN
game.exportFEN() - Export current game state as a FEN string.
Returns: string - FEN notation
const fen: string = game.exportFEN()
// "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
Call functions directly without creating a Game object. Each function takes a board configuration as its first parameter. Ideal for serverless environments or when you manage state externally (REST APIs, Redux, etc.).
import { move, moves, status, ai, aiMove, getFen } from 'js-chess-engine'
import type { BoardConfig, MovesMap } from 'js-chess-engine'
The stateless functions are wrappers around the same engine used by the Game class — the only signature difference is the boardConfiguration first parameter.
moves
moves(boardConfiguration) - Get all legal moves for the current player.
Params:
boardConfiguration BoardConfig | string (mandatory) - Board configuration (JSON object or FEN string)Returns: MovesMap
import { moves } from 'js-chess-engine'
import type { MovesMap, BoardConfig } from 'js-chess-engine'
const config: BoardConfig = { /* ... */ }
const allMoves: MovesMap = moves(config)
// {"E2": ["E3", "E4"], "B1": ["A3", "C3"], ...}
// From FEN string
const fenMoves: MovesMap = moves('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1')
status
status(boardConfiguration) - Get calculated board configuration with current game status. Useful for converting FEN to JSON.
Params:
boardConfiguration BoardConfig | string (mandatory) - Board configurationReturns: BoardConfig - Full board configuration with status
import { status } from 'js-chess-engine'
import type { BoardConfig } from 'js-chess-engine'
// Convert FEN to JSON
const config: BoardConfig = status('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1')
console.log(config.turn) // "black"
console.log(config.check) // false
console.log(config.checkMate) // false
getFen
getFen(boardConfiguration) - Convert board configuration to FEN string.
Params:
boardConfiguration BoardConfig | string (mandatory) - Board configurationReturns: string - FEN notation
import { getFen } from 'js-chess-engine'
import type { BoardConfig } from 'js-chess-engine'
const config: BoardConfig = { /* ... */ }
const fen: string = getFen(config)
move
move(boardConfiguration, from, to) - Perform a move and get the new board state. Returns full BoardConfig object (breaking change from v1).
Params:
boardConfiguration BoardConfig | string (mandatory) - Board configurationfrom string (mandatory) - Starting square (case-insensitive)to string (mandatory) - Destination square (case-insensitive)Returns: BoardConfig - Full board configuration after the move
import { move } from 'js-chess-engine'
import type { BoardConfig } from 'js-chess-engine'
// Move from FEN string
const config1: BoardConfig = move('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', 'E2', 'E4')
// Chain moves
const config2: BoardConfig = move(config1, 'E7', 'E5')
aiMove
⚠️ DEPRECATED: This function will be removed in v3.0.0. Use
ai()instead, which returns both the move and board state.
aiMove(boardConfiguration, level) - Calculate and return the best move using AI. Returns only the move (v1 API compatible).
Params:
boardConfiguration BoardConfig | string (mandatory) - Board configurationlevel AILevel (optional) - AI difficulty level (1-5). Default: 3Returns: HistoryEntry - The played move (e.g., {"E2": "E4"})
import { aiMove, ai } from 'js-chess-engine'
const fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
// Deprecated
const played = aiMove(fen, 4) // {"E2": "E4"}
// RECOMMENDED: Use ai() instead
const result = ai(fen, { level: 4 })
console.log(result.move) // {"E2": "E4"}
console.log(result.board) // Full board state
ai
ai(boardConfiguration, options?) - Stateless equivalent of game.ai(). Same options and return type — see the Game class ai() docs for full parameter documentation.
import { ai } from 'js-chess-engine'
import type { HistoryEntry, BoardConfig } from 'js-chess-engine'
const fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
// Play the move (default behavior)
const result1 = ai(fen, { level: 4 })
console.log(result1.move) // {"E2": "E4"}
console.log(result1.board.turn) // "black" (updated after move)
// Analysis mode: get move without applying it
const result2 = ai(fen, { level: 4, play: false, analysis: true })
console.log(result2.analysis?.slice(0, 5)) // [{ move: {"E2": "E4"}, score: 12 }, ...]
console.log(result2.bestScore)
console.log(result2.depth)
console.log(result2.nodesSearched)
// Custom depth overrides
const result3 = ai(fen, { level: 3, depth: { base: 5, quiescence: 3 } })
// Randomness: deterministic by default, opt in for variety
const result4 = ai(fen, { level: 3 }) // fully deterministic (default)
const result5 = ai(fen, { level: 3, randomness: 10 }) // very subtle variety
Board configuration can be represented as either a JSON object (BoardConfig) or a FEN string.
The JSON format is convenient for modern applications where state is represented as objects (React, Redux, etc.).
import type { BoardConfig } from 'js-chess-engine'
const config: BoardConfig = {
"turn": "black",
"pieces": {
"E1": "K",
"C1": "B",
"E8": "k"
},
"isFinished": false,
"check": false,
"checkMate": false,
"staleMate": false,
"castling": {
"whiteLong": true,
"whiteShort": true,
"blackLong": true,
"blackShort": true
},
"enPassant": "E6",
"halfMove": 0,
"fullMove": 1
}
turn - Player to move next. Values: "white" (default) or "black"
isFinished - true when the game is over (checkmate or stalemate). Default: false
check - true when the current player is in check. Default: false
checkMate - true when the current player is checkmated. Default: false
staleMate - true when the current player is stalemated (no legal moves but not in check). Default: false
castling - Castling availability for each side. true means castling is still possible. Default: all true
whiteLong (queenside) - White king moves from E1 to C1whiteShort (kingside) - White king moves from E1 to G1blackLong (queenside) - Black king moves from E8 to C8blackShort (kingside) - Black king moves from E8 to G8enPassant - If a pawn just made a two-square move, this is the square "behind" the pawn for en passant capture. Default: null
halfMove - Number of halfmoves since the last capture or pawn advance. Used for the fifty-move rule. Default: 0
fullMove - Full move number. Starts at 1 and increments after Black's move. Default: 1
pieces - Pieces on the board using FEN notation:
| Piece | White | Black |
|---|---|---|
| Pawn | P | p |
| Knight | N | n |
| Bishop | B | b |
| Rook | R | r |
| Queen | Q | q |
| King | K | k |
You can also use Forsyth–Edwards Notation (FEN):
import { move } from 'js-chess-engine'
const fen = 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1'
const newConfig = move(fen, 'H7', 'H5')
console.log(newConfig)
// BoardConfig object with updated position
The engine includes a sophisticated AI based on the Minimax algorithm with alpha-beta pruning, enhanced with advanced performance optimizations. There are five difficulty levels:
| Level | Alias | Description | Base Depth | Extended Depth | Check Ext. | Quiescence | Total Max |
|---|---|---|---|---|---|---|---|
| 1 | Beginner | Very weak play, minimal lookahead | 1 ply | +0 ply | +0 ply | +0 ply | 1 ply |
| 2 | Easy | Suitable for new chess players | 2 ply | +0 ply | +1 ply | +0 ply | 3 ply |
| 3 | Intermediate | Balanced difficulty (default) | 2 ply | +1 ply | +1 ply | +1 ply | 5 ply |
| 4 | Advanced | Strong play with deeper search | 3 ply | +2 ply | +1 ply | +2 ply | 8 ply |
| 5 | Expert | Very strong play, deep search | 4 ply | +3 ply | +1 ply | +4 ply | 12 ply |
Depth Components:
Performance: Response time increases with level (deeper search + larger transposition table). Exact timings vary a lot by CPU, position complexity, and cache size, so the repo includes a benchmark script—run npm run benchmark to measure performance on your machine.
import { Game } from 'js-chess-engine'
const game = new Game()
// Different difficulty levels
game.ai({ level: 1 }) // Beginner
game.ai({ level: 2 }) // Easy
game.ai({ level: 3 }) // Intermediate (default)
game.ai({ level: 4 }) // Advanced
game.ai({ level: 5 }) // Expert
Implementation Highlights:
The engine automatically adjusts cache size based on AI level and environment:
| AI Level | Node.js Cache | Browser Cache | Use Case |
|---|---|---|---|
| 1 | 0.5 MB | 0.25 MB | Lightweight, fast responses |
| 2 | 1 MB | 0.5 MB | Mobile-friendly performance |
| 3 | 4 MB | 2 MB | Balanced (default) |
| 4 | 16 MB | 8 MB | Strong tactical play |
| 5 | 40 MB | 20 MB | Maximum strength |
Lower levels use less memory for faster responses, higher levels use more for better move quality. Browser cache sizes are appropriate for modern devices (2024+). Override with ttSizeMB option if needed.
Note: TT size directly affects AI strength. Larger cache = better move ordering and fewer redundant searches, resulting in stronger play.
📚 Complete AI Implementation Guide →
For comprehensive technical documentation including algorithms, data structures, optimization techniques, and developer guides, see the AI implementation documentation.
Pawn Promotion: When a pawn reaches the opposite end of the board, it is automatically promoted to a Queen. If you want the player to choose the promotion piece in your application, use the setPiece() method to replace the queen with the desired piece.
Castling: Castling moves are included in the moves returned by moves(). When a king moves two squares (castling), the rook automatically moves as well.
Fifty-Move Rule: The halfMove counter is calculated automatically, but the fifty-move rule is not enforced by the engine. You can implement this rule in your application if needed.
Version 2.0 is written entirely in TypeScript and exports all necessary types:
import { Game } from 'js-chess-engine'
import type {
// Board types
BoardConfig,
PieceSymbol,
Square,
Color,
MovesMap,
CastlingRights,
HistoryEntry,
// AI types
AILevel,
// Piece types
PieceType
} from 'js-chess-engine'
See /src/types/board.types.ts and /src/types/ai.types.ts for complete type definitions.
Collaborators are welcome. Please ensure your code passes TypeScript type checking and all tests before submitting a pull request:
npm run typecheck # TypeScript type checking
npm run test # Run test suite
If possible, use commit message prefixes like feat: or fix: - the changelog is generated from these.
Changelog can be found HERE.
I am not a chess pro. My father is.
When I was ten, I had an Atari (with Turbo Basic), and I was hoping for a new PC. My father told me: "Make me a computer program which beats me in chess, and I'll buy you a new PC."

Obviously, it was a trap and I failed. Twenty years later, it came back to my mind, and I decided to finish what I started. This is version 2.0 - a complete TypeScript rewrite with improved performance and architecture.
Version 2.0 is a complete TypeScript rewrite with significant API changes. While method names remain the same, several return types have changed:
moves(square) Return Type Changedv1 Behavior:
const game = new Game()
game.moves('E2') // Returns array: ["E3", "E4"]
v2 Behavior:
const game = new Game()
game.moves('E2') // Returns object: {"E2": ["E3", "E4"]}
move() Return Type Changedv1 Behavior:
game.move('E2', 'E4') // Returns move object: {"E2": "E4"}
v2 Behavior:
game.move('E2', 'E4') // Returns full BoardConfig object
aiMove() API - Restored for Migration (Deprecated)The aiMove() function has been restored to v1 API compatibility to ease migration, but is deprecated and will be removed in v3.0.0.
v1 Behavior:
aiMove(config, 2) // Returns move object: {"E2": "E4"}
v2 Behavior (Current):
aiMove(config, 3) // Returns move object: {"E2": "E4"} ✅ v1 compatible
New ai() function - For users who need both move and board state:
ai(config, { level: 3 }) // Returns: { move: {"E2": "E4"}, board: {...} }
⚠️ DEPRECATION NOTICE:
aiMove()is deprecated and will be removed in v3.0.0. Migrate toai()for better functionality.Migration:
// Old (deprecated) const move = game.aiMove(3) const board = game.exportJson() // New (recommended) const result = game.ai({ level: 3 }) console.log(result.move) // Same move object console.log(result.board) // Board state included
Migration:
The default level has changed from 2 to 3 to maintain similar difficulty in the middle range.
staleMate Fieldv2 adds explicit staleMate: boolean field to BoardConfig. In v1, stalemate was inferred from isFinished && !checkMate. isFinished remains for backward compatibility but can be removed in future versions.
FAQs
Simple and fast Node.js chess engine with configurable AI and no dependencies
The npm package js-chess-engine receives a total of 874 weekly downloads. As such, js-chess-engine popularity was classified as not popular.
We found that js-chess-engine demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.