tonnetto
v1.0.2
Published
Tonnetto is a TypeScript-based chess engine. It can be natively run in the browser, benefitting from a small bundle size. It can also be run directly in the terminal, with UCI protocol support.
Maintainers
Readme
Tonnetto Chess Engine
Tonnetto is a lightweight and small JavaScript-based chess engine, designed to be a more compact alternative to heavyweight engines like Stockfish. Inspired by the name Stockfish, which translates to "cod" in Norwegian, Tonnetto is named after "little tuna" — a playful and fitting tribute to the original engine.
Unlike many current JavaScript chess engines that are written in WebAssembly (WASM) and optimised for higher-performance server-side usage, Tonnetto focuses on being more lightweight and runs natively in the browser, making it an excellent choice for web applications that require minimal footprint. It also aims to be more performant than "tiny" JS chess engines at the cost of a slightly larger bundle size.
[!NOTE] The engine's current ELO is approximately 1600 minimum, using depth=3 (instant move generation).
Higher depths provide a much stronger engine, but move generation times at depth>5 are currently too slow due to combinatorial explosion.
With further improvements such as Null Move Pruning / Zobrist Hashing / better move ordering, the search time should significantly reduce, allowing for a dramatic bump in the playing strength!
Features
- Small Bundle Size: Unlike alternatives such as stockfish.js which can be around 6MB-66MB in bundle size (depending if lite-mode or full-performance), Tonnetto's JavaScript implementation is significantly smaller, at only ~20kB.
- Perft Valid: The engine is perft (performance test) valid, in accordance with the chess programming community's standard Perft Results.
- Typed Arrays for Speed: Tonnetto leverages JavaScript's typed arrays for faster operations, avoiding the performance-limitations of standard arrays for a chess engine. This results in a more efficient implementation for piece movement and board state calculations.
- Encoding: The board state, moves, and pieces are encoded directly into the typed array's integer's bits using a custom design. This concept is described here, and is helpful in the performance demanded by a chess engine.
- 10x12 Mailbox Representation: The engine is board-centric (not piece-centric), with the board position being represented in a 10x12 mailbox, which improves the speed of position generation.
- Negamax Search Algorithm: Tonnetto uses the negamax algorithm, an implementation of the minmax search algorithm.
- Alpha-Beta Pruning: Optimises search performance by eliminating branches that do not need to be evaluated, speeding up decision-making. The framework is described here.
- PeSTO Evaluation: Tonnetto uses PeSTO evaluation, a well-established heuristic function that significantly improves positional play and the engine’s understanding of the game.
- MVV-LVA Sorting: The engine employs MVV-LVA (Most Valuable Victim, Least Valuable Attacker) move sorting to prioritise more impactful moves, reducing search time by pruning branches quicker.
- Quiescence Search: Prevents evaluation blunders by searching deeper when the position is unstable, ensuring more accurate evaluations in complex positions. It avoids the "horizon effect", as described here.
- Native Browser Support: Runs directly in the browser without requiring a server or WebAssembly, making it easy to integrate into web-based chess applications.
- Wide-Support: By using native typed arrays, as mentioned earlier, there is much better compatability with older browsers. This comes at the cost of not using bitboards, which may provide faster move generation. However, bitboards would require 64-bit integers, which are not natively supported in JavaScript until BigInt, which has slightly less compatability. BigInt also had potential limitations with bitwise operations, although I'm not sure that is correct as I didn't go down this route anyway.
- TypeScript Source: The source code is written in TypeScript, allowing for better type-safety, maintainability, and bug-prevention. Once transpiled into JavaScript, it runs completely native to the browser environment.
- UCI Compatibility: Tonnetto can also run directly in the terminal (with Node.js installed), and supports the UCI protocol, allowing it to be used with third-party GUI applications that support UCI engines.
- GUI Demo: A simple graphical user interface is provided for demo purposes. You can test Tonnetto live at chess.marcobuontempo.com.
- Piece Sets: The GUI demo uses the following piece sets:
- Open Chess Font: Created by Colin M.L. Burnett GPLv2+
- Horsey: Designed by cham and michael1241 CC BY-NC-SA 4.0
FUTURE TODO:
- [] Move Search: Add ability to track and avoid 3-move repetition.
- [] Performance Improvements: Add advanced search features like null move pruning, and other pruning techniques.
- [] Prettify/ESLint: Clean up the code style and enforce consistent formatting with ESLint.
- [] Evaluate Engine Strength: Measure the engine's strength with respect to depth, time, and approximate Elo rating. Present in a table format.
- [] Build Process: Create a build process to output Tonnetto as an executable for easier distribution and usage in UCI mode.
- [] GUI Demo Enhancements: Update the demo to reflect the difficulty level as an approximate Elo rating in the slider (instead of showing depth).
Usage
Live Demo
You can play the bot directly in the browser, using the custom-built GUI at chess.marcobuontempo.com
Importing into JavaScript Files
Import using CDN:
import TonnettoEngine from 'https://cdn.jsdelivr.net/npm/tonnetto';OR, if using npm:
npm install tonnettoimport TonnettoEngine from 'tonnetto';Browser
Create an instance of the engine:
// use default start position const engine = new TonnettoEngine(); // OR, use a custom FEN (must be valid, as engine does NOT validate) const engine = new TonnettoEngine({ fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" });Refer to ChessEngineAPI for usage. Note: TonnettoEngine is an alias for ChessEngineAPI, hence it contains the exact same methods
For example, to get the best move in the position and apply it:
engine.getFen(); // returns the current position const move = engine.getBestMove(5); // finds the best move in the position engine.applyMove(move); // applies the move to the engine engine.getFen(); // returns the new position
NodeJs Terminal
Start the file where you have imported the program (for example, if you imported into a file called
chess-engine.js):node chess-engine.jsThe engine will automatically be running in UCI mode and ready for input. For example:
uci # engine responds with: id name Tonnetto id author Marco BuontempoSupported UCI commands:
uci: engine identificationisready: whether the engine is ready for inputposition startpos: sets FEN to default chess positionposition fen <custom_fen_string>: sets FEN to custom chess positiongo depth <number>: returns the bestmove at a given depth (lower depth = much faster results)quit: exits the program
License
Tonnetto is open-source and licensed under the MIT License.
Contributing
Contributions to Tonnetto are welcome! Please fork the repository, make your changes, and submit a pull request. Preferred contributions are unit tests or bug-fixes, but feature additions or optimisations are also welcome
