This folder is the complete source of the chess engine in Marco’s Chess.
It is Sjeng 11.2 by Gian-Carlo Pascutto, the engine that macOS Chess runs,
compiled to WebAssembly. build.sh turns it into
../js/sjeng/sjeng.js and ../js/sjeng/sjeng.wasm.
Licensing: GPL-3.0-or-later. See ../NOTICE.md and ../LICENSE.
src/ and books/ are copied from Apple’s open-source macOS Chess:
apple-oss-distributions/Chess
at tag Chess-570.1 (commit 6b3de1f4f307f265b09720f0aa53689248c67a29),
folder sjeng/.
src/, 29 are unchanged. All four opening books are unchanged.blob2.c are left out. blob2.c is an
optional single-file build that #includes the other sources.Each change is marked in the source with “WebAssembly build” or “Fixed”, and each changed file says so in its header.
| File | Change | Why |
|---|---|---|
| src/sjeng.c | sd now reads its number: sscanf(input+3, …) instead of sscanf(input, …). |
A bug in Apple’s copy. The scan starts at the letters “sd”, so it never reads the depth, and the maximum stays 40. macOS Chess sets its strength only with sd, so its strength slider never changes how the engine plays. |
| src/sjeng.c | <machine/endian.h> became <endian.h>. |
That header only exists on macOS, and nothing in the file uses it. |
| src/sjeng.h | Includes <sys/time.h> itself. |
macOS’s headers pulled it in indirectly; Emscripten’s do not. |
| src/config.h | HAVE_SELECT is off. |
A browser has no stdin for the engine to poll. The cost: Sjeng can’t read input in the middle of a search. So the page sends easy (no thinking on your time), and “Move now” restarts the engine. |
| src/utils.c | rinput() takes commands from the page. |
In the browser, commands arrive as messages, not on stdin. |
| wasm/browser_io.c | New. | Pauses the engine until the page sends the next command (Emscripten Asyncify). |
| wasm/ndbm.h, wasm/ndbm_stub.c | New. | Apple’s Sjeng can open an optional ndbm “BinBook” opening book. Browsers have no ndbm, so dbm_open() always fails. Sjeng then uses the .opn books. |
Build settings in build.sh:
-fcommon: the 2001-era headers define globals in every file that includes them.-sSTACK_SIZE=8MB: the search keeps large arrays on the stack. Emscripten’s 64 KB default overflows and corrupts memory.-sASYNCIFY: lets the engine wait for the page’s next command.--embed-file: builds sjeng.rc and the opening books into the engine.The page uses the same xboard commands that macOS Chess sends to this engine:
xboard, confirm_moves, new, force, the moves, sd, go. It also
sends level to set the thinking time and easy (see HAVE_SELECT above).
See ../js/sjeng-worker.js and ../js/ui/engine-client.js.
You need Docker. The script uses the pinned image emscripten/emsdk:6.0.12.
cd marco-chess/sjeng && ./build.sh
node --test marco-chess/tests/sjeng.test.mjs
These tests run the compiled engine in Node. On a Mac they also send the same
commands to sjeng.ChessEngine inside Chess.app and check that both engines
give the same replies and moves. The one expected difference is the depth
that sd 4 sets: 4 here, 40 in Chess.app.