Skip to content

About

Desktop chess analysis app for Windows and macOS powered by Stockfish 18. Set up any position and get engine-recommended moves with evaluations, tactical motifs, and principal variations.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Chess Solver logo

Chess Solver

A desktop chess analysis tool powered by Stockfish 18.
Set up any board position and get the top engine-recommended moves with evaluations, tactical motifs, and principal variations.

CI Latest release License: GPL-3.0

React 19 TypeScript Vite Electron Stockfish 18 WASM

Chess Solver in the Matte theme, finding Scholar's mate with three candidate lines

The Neon theme in dark mode, flagging capture, checkmate and fork on the best line

Tactical motifs flagged on every line, here in Neon dark

Settings dialog with themes, light and dark mode, think time, updates and the version

Settings: theme, mode, think time, updates and About

All eight looks: Matte, Frutiger Aero, Neon and Neubrutalism, each in light and dark
Four themes, each in light and dark: Matte, Frutiger Aero, Neon and Neubrutalism

Features

  • Drag-and-drop piece movement with click-to-place editing
  • Paste a FEN to load a position, and copy the current FEN
  • Import a PGN game (file or paste), step through it, and export the current game
  • Move list with back, forward, start, and end
  • Stockfish 18 analysis on all your CPU cores (multi-threaded NNUE build), streaming results so the best move so far appears within milliseconds of each move
  • 1 / 3 / 5 candidate lines (fewer lines = deeper search), with eval bars and depth info
  • Adjustable think time per position (1s / 3s / 5s / 10s)
  • Tactical motif detection (forks, pins, skewers, checks, etc.)
  • Opening name recognition
  • Checkmate, stalemate, and draw detection
  • Four themes (Matte, the default; Frutiger Aero; Neon; Neubrutalism), each with light and dark. System (the default mode) follows your computer's setting live
  • An original piece set and a themed board: squares, frame, coordinates, highlights and pieces restyle with each theme
  • A wide layout that fills the window: the board grows with it, panels keep fixed heights and scroll inside, and nothing shifts as you play
  • Board flip and turn switching
  • Update check against GitHub releases: automatic at startup (a banner appears when a new version is out) and on demand from Settings → Check for updates

Checkers

A Checkers tab (top of the window) analyses American checkers / English draughts: captures are required, a jumping piece keeps jumping, and a man that reaches the far row is crowned (its move ends there).

  • Ranked best moves with evaluations: +1.00 is about one piece ahead for the side to move, and "Win in 5" is a proven forced win. Click a line to play it.
  • Set up any position with the piece row under the board, or paste position text in the PDN style (B:W21,22,K30:B1,2,K9).
  • Play vs engine as Black or White, with optional hints, and Undo.
  • Tour walks through the rules with live example positions.

The engine is written for this app and runs in a Web Worker: alpha-beta search with iterative deepening, a transposition table, killer and history move ordering, and a capture extension. Move generation is checked against the published perft counts (depth 1 to 8 from the start: 7, 49, 302, 1469, 7361, 36768, 179740, 845931). There are no endgame tables yet; small king endings are solved by search instead.

Tamerlane

A Tamerlane tab plays Tamerlane chess (Shatranj al-Kabir, "great chess"), the large chess of Timur's time: an 11 x 10 board plus two citadels, 28 pieces a side, and a different pawn for every piece.

The Tamerlane tab in the Matte theme: the 11 by 10 board with its two citadels, the start position, and the engine's ranked moves

  • Ranked best moves with evaluations: +1.00 is about one pawn ahead for the side to move, and "Win in 3" is a forced win. Click a line to play it; hover it to see it on the board.
  • Three starting setups: the masculine array (the default), the feminine array and the third array, under Starting setup. The choice is saved in the position text and remembered for next time; switching starts a new game, and asks first if the game has moves or edits.
  • Board style: Plain (historical), the default, as the sources describe an uncheckered board, or Checkered, with two-tone squares in the theme's colours (a1 dark) for easier reading of diagonals. The citadels keep a hatched fill and a heavier edge in the checkered style. The choice is remembered.
  • Set up any position: pick a colour and a piece under the board (every piece and pawn, including each stage of the pawn of pawns), or paste position text.
  • Play vs engine as White or Black, with optional hints, plus Undo, Flip, a move list, Rules and a Tour with live examples.
  • Pieces are drawn for this app in the same style as its chess set, with names and how each one moves on hover. Pawns are small copies of their own piece, as the historical sets made them; the pawn of pawns carries a 1, 2 or 3 badge for its stage.

The rule set

The rules follow Hans Bodlaender's Tamerlane chess page on chessvariants.com, which draws on H. J. R. Murray's A History of Chess (1913) and John Gollon's Chess Variations: Ancient, Regional and Modern (1968). Details were checked against Wikipedia's summary of Cazaux and Knowlton's A World of Chess (2017). Where the sources disagree or say nothing, the app's choice is marked "Our choice". The same text is in the tab's Rules panel.

Board and setup

  • An uncheckered board of 11 files (a to k) and 10 ranks, plus two citadels: White’s to the right of k2 and Black’s to the left of a9. 112 squares in all.
  • Each side has 28 pieces: king, general, vizier, two each of giraffe, picket, knight, rook, elephant, camel and war engine, and 11 pawns.
  • Three historical starting arrays are known; pick one under Starting setup. All three keep every pawn on its own file and keep Black as White’s array turned around, so both kings start on the f-file. White moves first.
White, a to k Masculine (default) Feminine Third (Ibn Arabshah)
Rank 1 elephant, –, camel, –, war engine, –, war engine, –, camel, –, elephant elephant, –, camel, –, general, king, vizier, –, camel, –, elephant as the feminine
Rank 2 rook, knight, picket, giraffe, general, king, vizier, giraffe, picket, knight, rook rook, knight, picket, giraffe, war engine, pawn of kings, war engine, giraffe, picket, knight, rook rook, knight, war engine, picket, giraffe, pawn of kings, giraffe, picket, war engine, knight, rook
Rank 3 pawns of pawns, war engines, camels, elephants, generals, kings, viziers, giraffes, pickets, knights, rooks the same, with f3 empty the same, with f3 empty
  • Why masculine is the default: Jean-Louis Cazaux calls it the best known; it is the array chessvariants.com gives, the one Wikipedia lists first as "a common one", and the standard board on tamerlanechess.online, a site where people play the game today. The names masculine and feminine come from Nathaniel Bland (1850), who reported the first two; the diagrams here follow Cazaux and Knowlton (2017).
  • Our choice: the citadels stay where the sources draw them in every array (White’s right of k2, Black’s left of a9). The Arabshah manuscript in Paris shows its array on a board without citadels.

How the pieces move

  • King: one step any way.
  • General (ferz): one step diagonally.
  • Vizier (wazir): one step straight.
  • Giraffe: one step diagonally, then at least three squares straight on in one of the two directions that step pointed. It cannot jump: every square it passes must be empty.
  • Picket (tali’a): like a chess bishop, but always at least two squares. A piece right next to it blocks that diagonal.
  • Knight: like a chess knight.
  • Rook: like a chess rook.
  • Elephant (alfil): jumps exactly two squares diagonally.
  • Camel: jumps three squares one way and one to the side (a long knight).
  • War engine (dabbaba): jumps exactly two squares straight.
  • Pawns: one step forward, take one step diagonally forward. No double first step and no en passant. There is no castling.

Pawns and promotion

  • Every pawn belongs to a piece and is drawn as a small copy of it. On the last rank it becomes that piece: the pawn of rooks becomes a rook, the pawn of camels a camel, and so on.
  • The pawn of kings becomes a prince, which moves like a king and is royal.

The pawn of pawns

  • First time on the last rank it stays there and cannot be taken (badge 1).
  • From there it may jump to any square from which, as a pawn, it forks two enemy pieces, or attacks an enemy piece that has no move at all. Whatever stood on that square, friend or foe, is removed. It cannot jump onto a royal piece or a citadel. Our choice: the jump is allowed, not forced (chessvariants.com says "may"; some summaries say "must"), and "cannot escape" is read as "has no move", which the app can check exactly.
  • Then it moves as a pawn again (badge 2). Second time on the last rank it is sent at once to the pawn of kings’ start square: f3 for White or f8 for Black in the masculine array, f2 or f9 in the feminine and third, where that pawn starts a rank further back. Our choice, since the sources are silent: whatever stands there is removed, but it cannot be sent onto a royal piece, so that step is not allowed while one stands there.
  • Third run (badge 3): on the last rank it becomes an adventitious king.

Kings, princes and adventitious kings

  • The king, prince and adventitious king are all royal. While you have two or more, any of them may be left attacked and taken like a normal piece; once you are down to one, it must be kept out of check.
  • Once a game, when your king is in check (and it is your only royal piece), you may swap it with any of your own pieces instead of moving. Our choice: only when in check, as chessvariants.com has it (some summaries also allow it against stalemate), and a pawn may not be swapped onto its own last rank.

Citadels

  • If your king steps into the enemy citadel, the game is drawn at once. Only the king may go there; no other piece may enter the enemy citadel.
  • Your own citadel is closed to everything except your adventitious king, which cannot be taken while it is inside and so keeps the enemy king out.
  • Our choice: the draw is automatic. Cazaux and Knowlton add a variant where a king with a prince or adventitious king may swap with it there and play on; the app does not use it.

Winning and drawing

  • Checkmate your opponent’s last royal piece to win.
  • Stalemate also wins: a player with no legal move loses, in check or not.
  • No bare-king rule (Gollon assumes none), no repetition or 50-move rule. The only draw is a king in the enemy citadel. The engine scores repeating a position as level so it does not go round in circles; that is an engine habit, not a rule.

Position text

Ten ranks from 10 down to 1, separated by /; White in upper case, Black in lower case; numbers for runs of empty squares. Pieces: K king, H prince, A adventitious king, F general, V vizier, G giraffe, T picket, N knight, R rook, E elephant, C camel, W war engine. Pawns are two letters, P plus the piece they belong to (PR pawn of rooks, PK pawn of kings, ...); the pawn of pawns is PP, then P1 waiting on the last rank, P2 and P3 on its second and third runs. Then the side to move (w/b), the king swaps still unused (wb, w, b or -), what is in White's and Black's citadels (-,-), and the starting setup (masculine, feminine or third; text without it is read as masculine). The setup matters after the start too, because it decides where a second-run pawn of pawns is sent. The three start positions:

e1c1w1w1c1e/rntgvkfgtnr/prpnptpgpvpkpfpepcpwpp/11/11/11/11/PPPWPCPEPFPKPVPGPTPNPR/RNTGFKVGTNR/E1C1W1W1C1E w wb -,- masculine
e1c1vkf1c1e/rntgwpkwgtnr/prpnptpgpv1pfpepcpwpp/11/11/11/11/PPPWPCPEPF1PVPGPTPNPR/RNTGWPKWGTNR/E1C1FKV1C1E w wb -,- feminine
e1c1vkf1c1e/rnwtgpkgtwnr/prpnptpgpv1pfpepcpwpp/11/11/11/11/PPPWPCPEPF1PVPGPTPNPR/RNWTGPKGTWNR/E1C1FKV1C1E w wb -,- third

The engine

Written for this app and run in a Web Worker: negamax alpha-beta with principal-variation search, iterative deepening, a transposition table, killer and history move ordering, a check extension and a capture-only quiescence search. It ranks 1, 3 or 5 moves and reports after every finished depth.

Material values (100 = one pawn) are worked out, not looked up, because there are no well-established published values for these pieces:

Piece Value Where it comes from
Elephant 135 Short-range: 300 x average moves on an empty Tamerlane board / the knight's (2.62 / 5.85)
War engine 165 Same rule (3.24 / 5.85)
General 170 Same rule (3.27 / 5.85)
Vizier 185 Same rule (3.62 / 5.85)
Picket 240 A chess bishop (325) x its moves / a bishop's on this board (8.73 / 12.0)
Camel 265 Short-range rule (5.16 / 5.85)
Knight 300 The anchor, as in chess
Prince, adventitious king 400 A king's moves by the short-range rule (6.89, so 353) plus 50 for being a spare royal piece
Giraffe 440 The rook (500) x its moves / the rook's (16.8 / 19.0)
Rook 500 Kept at its chess value
Pawns 100 The pawn of kings and pawn of pawns 120 (special promotions); the waiting pawn of pawns 150 (it cannot be taken)

Long-range pieces are anchored to chess rather than scaled by raw mobility, since an empty-board count overrates pieces that are usually blocked. Pawns also gain a little for each rank advanced and, close to the last rank, part of the worth of what they will become. These are reasoned estimates, not tuned or proven values.

How the move generator is checked

src/tamerlane/rules.test.ts checks every piece's moves by hand on small positions, every special rule (each pawn-of-pawns stage, the king swap, citadels, two royal pieces, checkmate and stalemate), and perft counts. From the masculine start the counts are 24, 576, 14,518 and 365,893 for depths 1 to 4; from the feminine start 24, 576 and 14,494, and from the third 32, 980 and 30,641, for depths 1 to 3. Fourteen special positions are checked to depth 3, three of them for where a second-run pawn of pawns goes in the feminine and third arrays. Fairy-Stockfish does not support Tamerlane chess, so there are no outside reference numbers; instead every count was matched against scripts/tamerlane_perft_check.py, a separately written move counter (python3 scripts/tamerlane_perft_check.py "<position text>" <depth>). Both follow the same reading of the rules, so the check catches coding mistakes, not a different reading of the sources.

Quick Start (Development)

npm install
npm run dev

Opens the app in your browser at http://localhost:5173.

The Stockfish engine (stockfish-18.js + its ~113 MB .wasm) comes from the stockfish npm package: the dev server serves it straight from node_modules, and npm run build copies it into dist/. Nothing engine-related is committed to the repo.

Testing

npm test        # unit tests (vitest)
npm run lint    # eslint
npm run build   # type-check + production build

The same three steps run in CI on every push and pull request.

Web build (static hosting)

npm run build:web   # type-check, build dist/ for the web, check file sizes

The web build runs entirely in the browser and needs no server code, so any static host works (for example Cloudflare Pages with output directory dist). It differs from the desktop build in three ways:

  • Engine: Stockfish 18 lite (about 7 MB) instead of the full 113 MB build, which is over the 25 MiB per-file limit of hosts like Cloudflare Pages. Lite is a little weaker but still very strong.
  • Threads: public/_headers sends Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp, so the page gets SharedArrayBuffer and the multi-threaded lite engine. On a host that ignores _headers, the page falls back to the single-threaded lite engine: slower, but analysis still works.
  • Updates: the update check links to what's new and suggests a reload instead of offering an installer.

scripts/check-dist.mjs runs after the build and fails if any file in dist/ is over 25 MiB. CI runs the web build too. If the engine can't start, the analysis panel says so: you can still set up positions and read games, and Retry reloads the page.

Download

Prebuilt binaries are on the releases page:

  • Windows — Chess.Solver.Setup.<version>.exe, a per-user installer (no administrator rights required). Run it and the app is added to the Start Menu with a desktop shortcut (SmartScreen may warn on first run because the binary is unsigned; choose "More info → Run anyway").
  • macOS — Chess.Solver-<version>-universal.dmg, a universal (Intel + Apple Silicon) disk image. The app is not notarized, so on first launch right-click the app → Open → Open to get past Gatekeeper.

Release binaries are built by the release workflow on tagged commits.

Build Standalone Desktop App

npm install
npm run electron

Builds the production bundle and launches it as a standalone Electron desktop app.

Build Installers

npm run dist

Builds for the platform you run it on and outputs to release/:

  • On Windows: release/Chess Solver Setup <version>.exe — per-user NSIS installer — plus release/win-unpacked/, the unpacked app folder it installs from
  • On macOS: release/Chess Solver-<version>-universal.dmg — universal disk image for Intel and Apple Silicon

.dmg files can only be built on macOS; CI's release workflow uses a macOS runner for that.

First-time build (Windows)

The first npm run dist on a machine downloads the winCodeSign toolchain, which contains symbolic links into a macOS subfolder. Windows blocks symlink creation unless one of these is true:

  • The PowerShell session is running as Administrator, or
  • Developer Mode is enabled in Settings → Privacy & Security → For developers

Run the first build under one of those conditions. Subsequent builds reuse the cached toolchain and do not need elevated privileges.

Install as a Desktop App (Windows)

npm run dist produces the installer itself — run release/Chess Solver Setup <version>.exe.

It installs per user into %LOCALAPPDATA%\Programs\Chess Solver (no administrator rights, no UAC prompt), creates desktop and Start Menu shortcuts, and registers an entry under Settings -> Apps so it can be uninstalled normally. Re-running a newer installer upgrades in place.

To pin to the taskbar, right-click the desktop shortcut -> Pin to taskbar (Windows blocks programmatic taskbar pinning).

Look and feel

Themes

Open Settings (the gear, top right) to pick a theme and a mode:

Theme Feel
Matte (default) Warm paper grain, a walnut board frame, small crafted corners with a hairline double edge
Frutiger Aero Glossy glass panels over a sky backdrop, glossy pieces, soft top corners
Neon Deep night surfaces, glowing cyan and violet edges, chamfered corners
Neubrutalism Flat colour, thick outlines, hard offset shadows, square corners

Each theme has light and dark. Light, Dark or System; System is the default and follows the OS as it changes. Your choice is saved. ?skin=neon (or matte, aero, brutal) in the URL picks a theme for that visit.

Every colour, radius, shadow and corner is a CSS variable in src/theme/tokens.css; components never hard-code colours. The board squares, coordinates, highlight, frame and piece colours are tokens too, so the board restyles with the theme. Tests (src/theme/*.test.ts) check that all eight variants define every token and keep body text, muted text, links, evaluations, warnings, opening names and dropdowns at WCAG AA contrast or better, and that board coordinates and pieces stand out from their squares.

Board and pieces

The pieces are an original geometric set drawn for this app (src/components/pieceArt.ts), not a stock set. The same shapes are dressed per theme: ivory and ebony in Matte, glossy porcelain and navy in Aero, glowing outlines in Neon, and bold black outlines in Neubrutalism.

Layout

The board sizes itself to the window. The analysis list and the move list have fixed heights and scroll inside, the controls stay in place, and the page always reserves room for its scrollbar, so switching 1/3/5 lines, a long game or a warning never moves anything.

Title, logo and icons

The title is set in Bungee (SIL OFL), bundled in src/assets/fonts so it works offline and in the desktop app, and styled per theme. The logo, an isometric board with the app's knight and an orange solve arrow, is src/assets/logo.svg. It is also the favicon (public/favicon.svg), the app icons (public/icon-192.png, public/icon-512.png), the desktop icons (build/icon.ico for Windows, build/icon.png at 1024×1024 for macOS) and the social card (public/og-image.png). After editing the SVG, re-export the PNG and ICO sizes from it and rerun npm run dist.

How to Use

  1. The board starts with the standard chess position. Stockfish begins analyzing automatically.
  2. Select a piece from the row below the board, then click any square to place it.
  3. Use Remove to erase pieces from the board.
  4. Toggle White/Black to move to change the side to analyze.
  5. The analysis panel shows the top engine moves with evaluations, tactical motifs, and principal variations. Use the 1 / 3 / 5 selector to trade breadth for depth.
  6. Click any analysis line to play that move on the board.
  7. Hover over a line to highlight the move on the board.
  8. Use Reset to restore the starting position or Clear to empty the board.
  9. Paste a FEN into the FEN field in the Position and game panel and press Load. Copy puts the current position on the clipboard. A FEN the board cannot represent shows an error and does not change the position.
  10. Import PGN reads a .pgn file. Paste PGN does the same from text. The move list starts at the beginning of the game; use the arrows or click a move to step through it. Export PGN downloads the current game. Import keeps the main line.
  11. Open Settings (the gear, top right) to pick a theme and light, dark or system mode, change the engine's think time, check for updates, or see the version under About. A dot on the gear and a banner under the header mean a new release is available.

How it's built

Chess Solver is an Electron desktop app. The window loads the React UI from a small static server in the main process (electron/main.cjs), bound to 127.0.0.1. Every response from that server sends Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Browsers only expose SharedArrayBuffer on a cross-origin-isolated page, and the multi-threaded Stockfish build needs it to start its pthread workers. Electron's window does not reliably apply those headers (crossOriginIsolated stays false), so the main process also enables SharedArrayBuffer with a Chromium command-line switch before the app is ready. The dev server sends the same two headers.

The engine is the Stockfish 18 multi-threaded NNUE build from the stockfish npm package. It is not committed to the repo. In development, Vite serves stockfish-18.js and stockfish-18.wasm from node_modules. The production build copies them into dist/ as stockfish.js and stockfish.wasm. The UI runs that script in a Web Worker (src/engine/stockfish.ts) and speaks UCI to it.

The board is React, drawn with react-chessboard using the app's own piece set and theme colours. Legal play goes through chess.js, which keeps castling rights, en passant, and the move counters. The piece editor is allowed to build positions a real game cannot reach, including ones chess.js refuses to load. Those edits go through the raw FEN helpers in src/engine/fen.ts.

Lost stops, and restarting a stuck search

Stockfish answers every go with exactly one bestmove. The worker queues a new go while the previous search is still unwinding, but it runs stop immediately. One stop can therefore arrive before the go it was meant to cancel, and that search continues with nothing waiting on it. The engine treats that as a lost stop: it sends stop again every 100ms until the matching bestmove arrives. If none arrives within 8 seconds, the worker is taken to be stuck, torn down, and started again. The position the user asked for is held in a pending slot and runs on the new worker, so a restart does not need a manual refresh. A second watchdog covers a go that never answers at all, and retries that search once before reporting failure. Rebuilding the worker recompiles the WASM binary (about 113 MB) and clears the hash table, so both timeouts wait longer than a healthy engine needs.

Why the Windows download got smaller

The v1.1.1 portable .exe was a 304 MB download. electron-builder was packing every Stockfish build shipped in node_modules next to the single copy in dist/ that the app actually loads. Excluding node_modules from the package, then switching Windows from a portable executable (it unpacked its whole payload on every launch) to a per-user NSIS installer, brought that download to about 165 MB. Those two figures are the ones recorded with that change.

Limitations and roadmap

  • Windows and macOS builds are unsigned and not notarized, so SmartScreen and Gatekeeper warn on first launch.
  • The editor can produce positions a game cannot reach. Analysis still runs when the board has one king of each color and eight ranks of eight squares. Tactic labels are omitted when chess.js rejects the FEN, which is what a pawn on the first or eighth rank does.
  • PGN import follows the main line. Variations, comments, and annotations are dropped, and export writes that main line back out.
  • Opening names come from a table of common positions in the app, not a full opening book.
  • There is no online play and no engine-versus-engine mode.
  • Tamerlane: the engine has no opening book or endgame tables. Its piece values are reasoned estimates, not tuned ones.

Still to do: code signing and notarization, and PGN variations and comments.

Tech Stack

  • React 19 + TypeScript
  • Vite (dev server serves COOP/COEP headers so SharedArrayBuffer is available for the threaded Stockfish build)
  • Stockfish 18 WASM (multi-threaded NNUE build, pthread workers), from the stockfish npm package
  • chess.js for move validation
  • react-chessboard for the board UI
  • Electron for desktop runtime, electron-builder for packaging
  • Vitest for unit tests, GitHub Actions for CI

License and credits

Chess Solver is free software under the GNU General Public License, version 3 only. See LICENSE.

The desktop app bundles Stockfish 18, which is itself under the GNU GPL version 3. An MIT license on this repository did not cover distributing that engine, so the project is GPL-3.0-only. THIRD_PARTY_NOTICES names the other components and their licenses.

The Stockfish developers write the engine: https://github.com/official-stockfish/Stockfish. The WebAssembly build comes from stockfish.js by Nathan Rugg (npm package stockfish): https://github.com/nmrugg/stockfish.js.

Packaged builds carry the GPL text, the third-party notices, and the Copying.txt from the stockfish package. They are placed in resources/licenses/ beside the app, and again under dist/licenses/ inside the app archive. The packager's !node_modules/**/* exclusion would otherwise omit the package's own license file; those copies are what replace it.

Source for this application: https://github.com/ccharafeddine/chess-solver

About

Desktop chess analysis app for Windows and macOS powered by Stockfish 18. Set up any position and get engine-recommended moves with evaluations, tactical motifs, and principal variations.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages