A powerful browser-only chess engine package that extends chess.js with built-in Stockfish evaluation capabilities. This package bundles Stockfish for position evaluation and analysis directly in the browser using Web Workers and WebAssembly.
- 🚀 Built on chess.js: All standard chess.js functionality included
- 🧠 Stockfish Integration: Bundled Stockfish engine for position evaluation
- 🌐 Browser-Only: Designed specifically for browser environments with Web Worker support
- ⚡ WebAssembly Support: Automatically uses WASM when available for better performance
- 🎯 Position Evaluation: Get numerical evaluations and mate scores
- 📈 Multi-PV Analysis: Get multiple principal variations for deeper insight
- 🔍 Suggested Lines: Get full suggested lines of play in SAN format
- ⚙️ Flexible Options: Configure search depth, time limits, and more
- 📦 Multiple Formats: CommonJS and ES Module builds included
pnpm install @chessle/chess.js-extended
import { ChessEngine } from "@chessle/chess.js-extended";
const engine = new ChessEngine();
// Make some moves
engine.move("e4");
engine.move("e5");
engine.move("Nf3");
// Evaluate the position
const lines = await engine.evaluatePosition();
if (lines.length > 0) {
const bestLine = lines[0];
console.log(`Evaluation: ${bestLine.evaluation}`);
console.log(`Best move: ${bestLine.line[0]}`);
console.log(`Suggested line: ${bestLine.line.join(" ")}`);
}
ChessEngine
offers two distinct modes for analysis to suit different use cases: a simple Promise-based mode for one-shot evaluations and a powerful Streaming API for continuous analysis.
Important: A single ChessEngine
instance can only perform one analysis at a time. Attempting to start a new evaluation or analysis while one is already in progress will result in an error.
The evaluatePosition()
method is perfect for when you need a single, final evaluation of the current board state. It returns a promise that resolves once Stockfish has completed its analysis.
import { ChessEngine } from "@chessle/chess.js-extended";
const engine = new ChessEngine();
engine.load("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2");
async function getEvaluation() {
console.log("Evaluating position...");
const lines = await engine.evaluatePosition({ depth: 18 });
console.log("Evaluation complete.");
if (lines.length > 0) {
const bestLine = lines[0];
console.log(`Evaluation: ${bestLine.evaluation}`);
console.log(`Best move: ${bestLine.line[0]}`);
}
}
getEvaluation();
The streaming API is ideal for applications that need to display real-time engine analysis, such as a live chessboard UI.
-
startAnalysis(options)
: Begins an infinite analysis of the current position. -
on('analysis', callback)
: Listens for analysis updates. The callback receives an array of the latestAnalysisLine
objects as the engine deepens its search. -
stopAnalysis()
: Sends thestop
command to Stockfish, which will then emit a finalbestmove
and terminate the analysis.
import { ChessEngine } from "@chessle/chess.js-extended";
const engine = new ChessEngine();
engine.load("r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
// 1. Listen for analysis updates
engine.on("analysis", (lines) => {
console.clear();
console.log("Current Analysis:");
lines.forEach((line) => {
console.log(`- ${line.line.join(" ")} (Eval: ${line.evaluation})`);
});
});
// 2. Start the analysis
console.log("Starting continuous analysis...");
engine.startAnalysis({ multiPV: 3 });
// 3. Stop the analysis after a few seconds
setTimeout(() => {
console.log("Stopping analysis...");
engine.stopAnalysis();
console.log("Analysis stopped.");
}, 5000);
The ChessEngine
class extends the standard chess.js Chess
class with evaluation capabilities.
const engine = new ChessEngine();
-
lines: AnalysisLine[]
- An array of the most recent final analysis lines from the last completedevaluatePosition
call.
Evaluates the current position using Stockfish and returns a single result.
await engine.evaluatePosition(options?: StockfishOptions): Promise<AnalysisLine[]>;
Starts a continuous, infinite analysis of the current position. Use the on('analysis', ...)
method to receive updates.
engine.startAnalysis(options?: StockfishOptions): void;
Stops a running analysis that was started with startAnalysis()
.
engine.stopAnalysis(): void;
Subscribes to engine events. Currently, only the analysis
event is supported.
engine.on('analysis', (lines: AnalysisLine[]) => void): void;
Unsubscribes from engine events.
engine.off('analysis', (lines: AnalysisLine[]) => void): void;
interface StockfishOptions {
/** Search depth (default: 15) */
depth?: number;
/** Thinking time in milliseconds */
time?: number;
/** Exact time to think in milliseconds */
movetime?: number;
/** Number of nodes to search */
nodes?: number;
/** Number of principal variations to search for (default: 1) */
multiPV?: number;
/** Skill level of the engine (0-20) */
skillLevel?: number;
/** Contempt value for the engine (-100 to 100) */
contempt?: number;
/** Number of threads to use (1-512) */
threads?: number;
/** Hash table size in MB (1-32768) */
hash?: number;
/** Custom Stockfish worker URL (optional) */
stockfishUrl?: string;
}
interface AnalysisLine {
/** The evaluation of the position (e.g., 0.5, -1.2, "M5", "M-3") */
evaluation: number | string;
/** The suggested sequence of moves in SAN format */
line: string[];
}
import { ChessEngine } from "chess.js-extended";
const engine = new ChessEngine();
engine.load("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1");
const lines = await engine.evaluatePosition();
if (lines.length > 0) {
console.log(`Position evaluation: ${lines[0].evaluation}`);
// Output: Position evaluation: 0.15
}
Get the top 3 best moves and their evaluations.
const lines = await engine.evaluatePosition({ multiPV: 3 });
lines.forEach((line, index) => {
console.log(
`Rank ${index + 1}: ${line.line.join(" ")} (Eval: ${line.evaluation})`,
);
});
// Output:
// Rank 1: Nc6 ... (Eval: 0.15)
// Rank 2: c5 ... (Eval: 0.20)
// Rank 3: e6 ... (Eval: 0.22)
// Analyze for exactly 5 seconds
const lines = await engine.evaluatePosition({ movetime: 5000 });
if (lines.length > 0) {
console.log(`Timed evaluation: ${lines[0].evaluation}`);
}
You can fine-tune the engine's performance and playing style by providing additional options.
const lines = await engine.evaluatePosition({
skillLevel: 10, // Set skill level to 10 (0-20)
contempt: 20, // Set contempt to 20 (-100-100)
threads: 4, // Use 4 threads
hash: 128, // Use 128MB of hash memory
});
if (lines.length > 0) {
console.log(`Evaluation with custom parameters: ${lines[0].evaluation}`);
}
const lines = await engine.evaluatePosition();
if (lines.length > 0) {
const bestLine = lines[0];
console.log(`Best move: ${bestLine.line[0]}`);
engine.move(bestLine.line[0]);
console.log(`Principal variation: ${bestLine.line.join(" ")}`);
// Output: Principal variation: Nf3 Nc6 Bc4 Bc5
}
// Load a position with checkmate
engine.load("rnb1kbnr/pppp1ppp/4p3/8/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3");
const lines = await engine.evaluatePosition();
if (lines.length > 0) {
console.log(`Evaluation: ${lines[0].evaluation}`);
// Output: Evaluation: M-1 (mate in 1 for black)
}
Since ChessEngine
extends Chess
, you have access to all standard chess.js functionality:
const engine = new ChessEngine();
// Standard chess.js methods work as expected
console.log(engine.ascii());
console.log(engine.moves());
console.log(engine.inCheck());
console.log(engine.isGameOver());
// Make moves
engine.move("e4");
engine.move({ from: "e7", to: "e5" });
// Undo moves
engine.undo();
// Get FEN
console.log(engine.fen());
// And then evaluate
const lines = await engine.evaluatePosition();
This package requires:
- Web Worker support
- WebAssembly support (recommended, falls back to JavaScript if unavailable)
- Modern browser with ES6+ support
- WebAssembly: Automatically detected and used when available
- Web Workers: Stockfish runs in a separate thread to avoid blocking the main thread
- Memory Management: Worker instances are properly cleaned up after analysis
- Timeout Protection: Analysis automatically times out after 30 seconds to prevent hanging
try {
const lines = await engine.evaluatePosition();
if (lines.length > 0) {
console.log(lines[0].evaluation);
}
} catch (error) {
if (error.message.includes("browser environment")) {
console.log("This package only works in browsers");
} else if (error.message.includes("worker failed")) {
console.log("Stockfish failed to load");
}
}
-
Positive numbers: Advantage for White (e.g.,
+1.5
= White is ahead by 1.5 pawns) -
Negative numbers: Advantage for Black (e.g.,
-0.8
= Black is ahead by 0.8 pawns) -
Mate scores:
M3
= mate in 3,M-2
= mate in 2 for opponent - Zero: Equal position
GPL-3.0 - See LICENSE file for details.
This package is part of the Chessle project. Contributions welcome!