Building My Own Chess Engine
healeycodes.com
healeycodes.com
Some notes from past efforts:
- A simpler but much faster engine will beat a more complex engine any time because the advantage of another ply easily outweighs the advantage of some clever algo.
- A 10x10 board with an unreachable edge has some advantages when laid out as a linear array because it allows for much easier memory management (premature optimizations and all that...). On an 8x8 board laid out linearly the edges have no way of limiting piece movement but on a 10x10 you get that for free by putting an 'impossible' value in the unreachable edge fields. Even a knight won't be able to jump that effectively two field wide edge. This turns all legal moves for all pieces into a simple offset rather than a two dimensional affair.
- Optimization of engine code is much harder than optimization of the yield function.
- It is much better to not generate 'bad' moves than it is to prune them away after a lot of extra work has been done. Colin Wrights' law: 'You can't make computers faster, you can only make them do less work' clearly applies here.
- memoization can bring insane speed gains.
Good luck!
Tell that to AlphaZero
It's both actually. The former helps guide the search (MCTS, not an Alpha-Beta minmax), and the latter helps truncate the search before an end state.
But with an 8x8 board you can store all free fields in a single 64-bit integer
That could be fast, would it not?
> - A simpler but much faster engine will beat a more complex engine any time because the advantage of another ply easily outweighs the advantage of some clever algo.
Depends a lot on what you call "simple". Stockfish's search and evaluation subroutines are pretty complex and distinctly clever:
https://github.com/official-stockfish/Stockfish/blob/master/...
https://github.com/official-stockfish/Stockfish/blob/master/...
But I agree that early on, simple is the way to go. It's much easier to optimize a simple engine.
> - A 10x10 board with an unreachable edge has some advantages [...]
Maybe in theory, but FWIW I don't know of any modern engine that does this. A more popular variant of the 10x10 or 10x12 idea was, back in the day, the 0x88 layout:
https://www.chessprogramming.org/0x88
If you're going to be writing an engine today from scratch, use the bitboard representation. It's easy to implement and has the most resources out there if you need help.
(Bitboards is just the idea of representing the game state as a bunch of 64-bit ints, with one u64 per white/black type of piece.)
Your example use-case, of knight posisitions, is usually solved with bitboards using an 64-element array mapping squares to a bitboard of all the places a knight on that square can go. Here's where Stockfish does that:
https://github.com/official-stockfish/Stockfish/blob/b06ef36...
Other commenters have noted that you can do a slightly fancier version of this lookup table technique to solve for the sliding pieces (bishops and rooks (queens are a bitwise OR of bishops and rooks)), because the raw data is low entropy, so finding a perfect hash function ("magic bitboard", in the lingo) is fast enough to do on startup:
https://github.com/official-stockfish/Stockfish/blob/b06ef36...
It's existing techniques like this that make bitboards a good choice for beginners; if you get stuck, you can find resources online.
> - Optimization of engine code is much harder than optimization of the yield function.
Big +1 there! The yield function is also much easier to A/B test.
> - It is much better to not generate 'bad' moves than it is to prune them away after a lot of extra work has been done. Colin Wrights' law: 'You can't make computers faster, you can only make them do less work' clearly applies here.
Yes, but it's pretty hard to reliably get your move generator to generate good moves first. The other side of making mixmax search fast is quickly throwing out branches that aren't relevant.
For instance, delta pruning and null-move pruning do this by trying to detect if one of the sides has thrown the game with their last move; since minmax is about assuming optimal play on both sides, branches where someone makes a blunder aren't worth looking into.
> - memoization can bring insane speed gains.
Yup! Also, a lot of engines have optimized routines that let you undo a move quickly, without having to keep a redundant stack of every position's state, which would involve a lot of memcpy-ing.
Have fun! I had to stop doing chess engines, they were becoming too much of a distraction for me. I'm kind of glad ML-AI is starting to beat human-crafted AI in chess, that way the space can go back to being a fun little hacker cottage industry.
Edit: Oh! When making a chess engine, start by implementing "perft":
https://www.chessprogramming.org/Perft
That way you can compare how many legal moves your engine thinks there is in a position, versus how many Stockfish or some other known-good engine thinks there is. It's the closest thing there is to unit tests in the space.
I've just finished a weekend project to calculate the number of legal chess positions and there are about 8.8E+45: https://github.com/lechmazur/ChessCounter.
For people considering making a chess program, take a look at this message board http://talkchess.com/forum3/index.php.
As the author mentions, python-chess is really nice. The project maintainers are very responsive - a bug I found was fixed within a day.
I'm personally running bits for sunfish https://github.com/thomasahle/sunfish and fastchess https://github.com/thomasahle/fastchess
There are way too many stockfish clones on there. I hope more hobby chess engine builders will join in.
I recommend playing against https://lichess.org/@/TuroBot which is based on Alan Turing's https://en.wikipedia.org/wiki/Turochamp
I debugged my bot by playing it on lichess since I'm so used to the UI/analysis tools.
That said, everyone has their own preferences and as long as you are motivated to spend a long time with it you can learn something from it, even if the improvement isn't noticeable immediately.
details?
Edit: well ok it was easy enough to duckduckgo it myself ;)
When I was 17 or so I wrote a chess program, at the time I was pretty deep into chess and color me surprised when after a week or so I could only beat my own program by really paying attention because it would never mess up and I would mess up all the time. All it would take is a little mistake and then you'd be lost already.
The code was written in 6502 assembler, it was a lot of fun to write. My buddy who wrote his own in Pascal was quite ticked off that my very straightforward 'dumb' program would beat his extremely elegant program hands down simply because it would look on average two ply further ahead. That's a lot of extra moves to analyze but the speed difference between machine language and Pascal (at the time, today this would definitely no longer be the case with our much better optimizing compilers) on an 8 bitter made this an uneven match.
On the plus side: his code was far more readable than mine.
I think programming a chess engine that can beat most club players (let's say sub-2000 rating) is not too hard. At that level, human players will make inaccurate moves (as well as blunders at lower ratings). It is however much harder to develop engines that are competing at GM level (~2500+).
This wiki has some good high level articles (https://www.chessprogramming.org/Main_Page). Even something like a chess bitboard representation is non-trivial to code.
Implementing minimax and comparable algorithms may be straightforward, but the actual evaluation of positions for traditional engines is a distillation of thousands of pieces of expert knowledge. See for example the parameters that can be tweaked in Fritz (http://help.chessbase.com/Fritz/16/Eng/index.html?000038.htm).
If you look at the history of chess engines (https://www.youtube.com/watch?v=wljgxS7tZVE), by the 1990s and later most of the top chess engines have had masters, international masters, and grandmasters intimately involved with development. For example, Deep Blue had several consulting grandmasters. Rybka (the world's best engine 2007-2010) had IM Vasik Rajlich as primary author and GM Larry Kaufman closely involved with tweaking its evaluation functions. Kaufman also went on to write the (still) very strong engine Komodo (https://ccrl.chessdom.com/ccrl/4040/).
Traditional chess engines used to have glaring weaknesses like playing poorly in closed positions, being poor at avoiding disadvantageous endgames, etc. By the mid-2000s many of these weaknesses disappeared, but as recently as Fritz 9 there were well known opening sequences where engines could be tricked into playing losing lines.
Something I have always been interested in trying is writing a chess engine that makes human-like moves. So the evaluation function would try to emulate what a human would do, maybe with a few depths of evaluation, instead of playing accurate moves that never miss simple combinations.
And you can challenge the bot on lichess: https://lichess.org/@/maia1
"Obviousness" would take into account things like the last move played ("ah, your pawn is attacking my bishop; I should move it"), and whether the move is a capture, check, or attacks another piece. A forward move for a knight is more obvious than a backward move, as is moving it towards the center of the board, versus moving it to the edge of board.
As the depth increases, the probability of exploring a branch decreases. I think it would be pretty easy to scale such a system to make it play better or worse by simply adjusting how much the probability of exploring a branch decreases as the depth increases.
Perhaps this could lead to more natural blunders where a line is simply missed?
I've also wanted to come up with an evaluation system that scales better with skill level. A position may be +3, but only if you play the next 5 moves exactly correctly. In another position, one move might be +1, and another might be +0.5, but the +0.5 requires four moves of perfect play from your opponent to keep it even. Can these subtleties be expressed clearly? Maybe something like "turns until positional advantage is converted to a clear material advantage." When you're just starting engine evaluations often don't make any sense.
I've watched a lot of chess on Twitch lately, and it'd also be cool to have scores for all the relatively-subjective terms that the commentators use: "lots of attacking chances" (just pure count of checks possible in next N moves? percentage of lines that lead to checkmate in next N moves?), "very sharp position" (how many moves drastically change the evaluation?), etc.
This was one of the debates in computer chess in the ‘70s. Should a good engine spend a lot of time on evaluating each position like a human using many heuristics, or should it go for speed and depth? In a famous match Dartmouth CP[0] was able to tie Northwestern’s program (the reigning champ) using this approach. But ultimately it turned out search depth was much more important. Modern engines don’t need to compromise compared to the ‘70s. They can do a great deal of depth and also evaluate way more heuristics for each position than in the ‘70s. But there is of course still a trade off between how much you analyze each position and how deep you can get in a reasonable amount of time.
We covered this debate briefly in our podcast’s episode on computer chess: http://kopec.live/episode/03ab19084c9e408e/computer-chess
0: https://www.chessprogramming.org/Dartmouth_CP Which includes details about its evaluation function
The great thing was that it worked, and it could "play chess". The sad thing was, I am such a bad chess player that it could still actually beat me around 50% of the time by choosing random moves.
I do think there’s also a big gap in the market for chess _tooling_ that makes learning and match prep easier. I actually think simpler more interpretable engines can be more useful as tools for humans in many cases. For example instead of increasingly inscrutable neural networks, or explanations of moves that are merely “after 10 moves this happens and it’s bad”, even a simple regression over some human interpretable features can give you an idea why the engine is suggesting a positional move (king safety, piece activity etc). I also think just pure stats and algorithmic stuff would be useful, like finding transpositions between openings, trying to work out what kinds of middle game structures or endgames a player is good or bad at, and navigating a path towards that in your preparation.
Nothing. This thing is getting messier, buggier, slower and less maintainable with each release.
Time to move on."
https://opensource.apple.com/source/Chess/Chess-321/sjeng/TO...
I highly recommend that all programmers with even a passing interest in chess spend some time writing an engine.
[1] https://github.com/ludocode/pottery/tree/master/examples/pot...
B > N, but.
Q + B < Q + N
Queens just work better with knights.One of the exercises at the end of the chapter was, "Improve the checkers program to play chess"
Not sure if the author was kidding or not.
I don't remember any strong BASIC chess engines, but there were quite a few 8-bit engines that were fairly decent in those days.
>> This program throws a RecursionError and prints 59691 — the number of different positions the search tree contained when it crashed. All we need to fix this, is another universe to run the program in.
To be fair, the recursion limit is not the same thing as running out of memory. You can use a loop to have your program run further than this.
https://github.com/FossterCare/flask-pgn-api takes a single PGN file and converts it into MP4 via flask api
another idea which I am working on, storing chess games in neo4j as graph data
The Internet is wild.