I’ve recently open-sourced my chess engine, Gyatso, which is written entirely in Nim. The project is still in an early stage, but it is fully functional and actively being improved.
The goal is both to build a stronger engine over time and to explore Nim’s suitability for performance-critical applications such as chess engines (move generation, search, pruning, and evaluation). Feedback from Nim users—whether about performance, code structure, or idiomatic Nim usage—would be especially valuable.
The engine is open source, and contributions, testing, and general comments are very welcome.
Repository: https://github.com/GyatsoYT/GyatsoChess
Releases: https://github.com/GyatsoYT/GyatsoChess/releases
Thank you to anyone who takes the time to look at the code or test the engine.
Had a poke, just to look at the code. Seems pretty idiomatic, good job. A few nitpicks would be to combine let/var statements into blocks, to use result instead of return when building up a return value in a variable, and to maybe use template instead of inline if you want to make absolutely sure things are inlined.
Apart from that writing a Nimble file to track dependencies and have build tasks (removing the need for your shell scripts) might be a good thing to consider.
Thanks for taking a look at the code and for the Nim-specific feedback — I really appreciate it.
The suggestions about grouping let/var declarations, using result when constructing return values, and considering template for guaranteed inlining are very helpful, and I’ll keep them in mind as I refactor and clean up the code.
Regarding Nimble: at the moment the project doesn’t have any external dependencies, which is why I’ve kept the build setup simple. That said, using a Nimble file for build tasks alone could still make things cleaner, so it’s something I’ll consider.
Thanks again for the review.
You can place build commands in config.nims without the need for a .nimble file, e.g. here is my typical setup:
config.nims
--outdir:"build"
--nimcache:"build/nimcache"
task release, "release build":
--define:danger
setCommand "c", "file.nim"
# or if you prefer exec
task debug, "debug build":
exec "nim c -d:debug file.nim"
but with simple tasks like this, I prefer to run nim c / nim c -r directly
The files to compile the engine is not for me but for users who don't know programming but still want to compile the engine on their PC , they can download nim and just double tap the batch file and it will build it according to their PC without them knowing anything about nim.
I myself always run the command whenever I need to build I don't use the scripts anyways
+ I already have a pre-made build script it's just not available on GitHub.
I'm not a specialist of chess bots so I can't comment on the specific algorithm, if you have specific bottlenecks you want to solve feel free give me more details.
From a high-level skimming, here are the things I see:
proc eventLoopWorker*(service: Rewinder, workerID: WorkerID) {.gcsafe.} =
let worker = service.workerpool[workerID]
worker.init(service, workerID)
while not worker.shutdown:
# Block until we receive a task
let task = worker.inTasks.recv()
worker.ready.store(moRelease, false)
# Process it
task.fn(task.env)
worker.ready.store(moRelease, true)
worker.teardown()
or general purpose (in your case, you replace "pick a task" and "steal task" with "search move" and "ponder": https://github.com/status-im/nim-taskpools/blob/v0.1.0/taskpools/taskpools.nim#L216-L236
proc eventLoop(ctx: var WorkerContext) =
## Each worker thread executes this loop over and over.
while not ctx.signal.terminate.load(moRelaxed):
# 1. Pick from local deque
debug: log("Worker %2d: eventLoop 1 - searching task from local deque\n", ctx.id)
while (var taskNode = ctx.taskDeque[].pop(); not taskNode.isNil):
debug: log("Worker %2d: eventLoop 1 - running task 0x%.08x (parent 0x%.08x, current 0x%.08x)\n", ctx.id, taskNode, taskNode.parent, ctx.currentTask)
taskNode.runTask()
# 2. Run out of tasks, become a thief
debug: log("Worker %2d: eventLoop 2 - becoming a thief\n", ctx.id)
var stolenTask = ctx.trySteal()
if not stolenTask.isNil:
# 2.a Run task
debug: log("Worker %2d: eventLoop 2.a - stole task 0x%.08x (parent 0x%.08x, current 0x%.08x)\n", ctx.id, stolenTask, stolenTask.parent, ctx.currentTask)
stolenTask.runTask()
else:
# 2.b Park the thread until a new task enters the taskpool
debug: log("Worker %2d: eventLoop 2.b - sleeping\n", ctx.id)
ctx.eventNotifier[].park()
debug: log("Worker %2d: eventLoop 2.b - waking\n", ctx.id)
To put a thread to sleep while waiting for a task, you can have them wait on a barrier (if threads always stop and start at the same time), you will need a custom implementation for MacOS, see barriers{_posix,_windows,_macos}.nim https://github.com/status-im/nim-taskpools/tree/v0.1.0/taskpools/primitives
Or an eventcount (for async stop/start) https://github.com/mratsim/constantine/blob/master/constantine/threadpool/crossthread/backoff.nim / https://github.com/mratsim/weave-io/blob/master/weave_io/crossthread/backoff.nim (weave-io is just a copy of Constantine's threadpool until I have time to revamp it to optimize it for IO, at the moment it's optimized for compute).
One thing I'm unclear about is how your spread your search between threads so that they don't do redundant search, I only see the stopFlag using atomics and a gated sync section for threadId == 0. That's typically the type of things that really need a detailed writeup / doc for maintenance because it's easy to introduce races.
If you can write such a document, I can help on the threading refactoring.
I’m pleased to share Gyatso v1.1.0, the latest release of my open-source chess engine written in Nim.
Current estimated strength is around ~2500 Elo based on ongoing testing.
Repository & downloads: https://github.com/GyatsoYT/GyatsoChess/releases
Contributions and feedback are welcome!
— Gyatso
Is there some generic gui / interface you can plug the engine into?(To play with it?)
Or is that a whole new project?
You can use any chess GUI that supports UCI to run Gyatso — load the engine executable into the GUI and either play against it or run analysis/matches. Examples of UCI-compatible GUIs:
CuteChess GUI — a cross-platform chess GUI and engine manager available on GitHub: https://github.com/cutechess/cutechess
Arena Chess GUI — a free GUI for chess engines and analysis: https://www.playwitharena.de/
Personally I’ve tested Gyatso with CuteChess and it works well with UCI. You can add the engine via the GUI’s engine settings and then play or automate matches.
I could have built a dedicated GUI directly into this project, but right now I’m focusing on improving the engine strength and speed first. Once the core engine is stronger and more refined, I may consider shipping a simple integrated GUI in a future release.
Gyatso v1.2.0 is now released. Current estimated strength is around ~2700 Elo based on ongoing testing.
Repository & downloads: https://github.com/GyatsoYT/GyatsoChess/releases/tag/v1.2.0
Contributions and feedback are welcome.
— Gyatso
Please let me know what issues you are facing and I'll try to fix that in the next update.
Also you do have an option to download the prebuilt binaries, and also you do have 2 scripts compile.bat and compile.sh depending on the os you are on you can use either of those to get a binary specific to your computer and that works perfectly aswell.
Note :- The compile.sh script had some problems in this update which was fixed by me a few hours ago so if you got problem in that then it should be fixed by now.
Well 90% of chess engines do not come with a pre built UI. Most of them support UCI commands so download any GUI that supports UCI will let you play with the bot. What i personally suggest is CuteChess. I do understand that having a GUI prebuilt would be good but that's not the point of this project at this moment. The point of this project is to make a strong engine not a GUI.
Thanks for understanding.