Let’s Play Chess: DomDeepBlue vs YOU

View app
View GitHub repository
With this app, I have paid tribute to the Garry Kasparov brand of electronic chess boards (image below), which I was besotted with as a kid. The design of my app isn’t quite the same, but it’s close enough.

As is pointed out in this graphic guide introduction to artificial intelligence, designing a computer program for the game of tic-tac-toe “is easy because tic-tac-toe has a relatively small game tree – it comprises only 362,880 possible board positions. By generating the entire game tree, the computer can always make the right decision by looking ahead. It can guarantee a win or a draw. The element of surprise is removed, once you can see how all the possible games unfold”. In AI, a game tree is the map of all possible moves that can unfold from a given position. Every choice creates further branches, each leading to a new board state and another set of possible replies. For tic-tac-toe, this tree is small enough to be fully explored.
A chess app, by contrast, is a very different proposition. One of the greatest masters in the history of the game, Garry Kasparov, summed this up when he said: “The number of potential chess moves exceeds the number of atoms in the universe. It’s a number beyond any possible calculation”. Or, as the graphic guide puts it, “with chess, looking ahead even a moderate number of moves becomes intractable – the number of combinations becomes too large to contemplate. The game tree for chess cannot fit into the universe, let alone a computer’s memory”. Chess programs cannot simply calculate everything to the end. Instead, chess-playing computers use heuristics that don’t guarantee success but provide a good approximation by looking ahead only a certain distance, using measures that reflect how advantageous a given board position is. In this respect, “our world is more like chess than tic-tac-toe. We can never plan too far ahead; the number of possibilities available to us in our everyday life is too great to contemplate”. Life really is like a game of chess after all. And chess has been part of our lives for a long time (estimated at around 1,500 years, likely originating in India), surviving countless rises and falls of empires and wars, mirrored in the game’s own use of strategy and attacking techniques, in order to be the ultimate victor.
My obsession with chess after acquiring the Kasparov-brand electronic chess board led me to getting to the final of the school chess tournament (I lost, though ): ). The ‘retro-futurist’ aesthetic design of my app is very much a homage to these vintage electronic chessboards, a precursor in that pre-Internet era to the immersive online chess games and apps that exist now, as well as a chance to consolidate my TypeScript knowledge (which the chess app logic is built in).
Moreover, the name of the app references Deep Blue, the IBM supercomputer that beat Kasparov, then the world champion in chess, thirty years ago (Kasparov had previously beaten Deep Blue’s predecessor, Deep Thought, in 1989, whose name in turn should be familiar to any geeky programmer). Likewise with this app, the human user plays against the computer (rather than another human player), which I’ve named DomDeepBlue in homage.
That result can now be seen as a symbolic key milestone in the development of AI. Its influence can be seen referenced subsequently in the name DeepMind (aka AlphaGo), another AI that would likewise defeat Go world champion Lee Sedol in 2016.
However, Deep Thought was not the first board game programme to play a whole game against a human. Alan Turing devised the first chess program in 1948, followed by Arthur Samuel, who designed a checkers-playing program. AI thrives on strict rules and predictable consequences in uncomplicated environments, which are ideal qualities for games like Go, checkers, and draughts – and, ultimately, chess.
With Deep Blue, the AI community had designed a machine that could beat one of the masters of chess. Yet, as the graphic guide to AI points out, following the quote from Kasparov earlier, “chess-playing computers shed little light on the question of mechanised cognition. They unashamedly rely on the ability of machines to consider hundreds of millions of moves per second. Deep Blue won using brute force, not brains…Deep Blue patently relies on mechanical trickery rather than mental dexterity.” This claim is backed up by IBM themselves on the section of their website covering Deep Blue, where it concedes that “Deep Blue derived its chess prowess through brute-force computing power”. Given that, was Deep Blue actually ‘progress’ in the evolution of AI?
Leaving aside philosophical questions such as this and focusing on the practical, designing a chess app such as this was a huge project to do individually, and was a confounding but rewarding experience. The codebase takes in something like 78 files (not including audio files, which add accompanying sound to each move and options such as quitting a game), and involved the processing of a large amount of complex game logic, with the user interface (UI) and visual design only coming near the end of the project once I was satisfied that the hard chess logic engine coding had been completed (and that’s before even mentioning the substantial testing of all functions). The status of each piece, and their respective ‘legal’ set of allowed moves (e.g. rooks can move in horizontal and vertical directions, but cannot go in diagonal directions, while bishops are the opposite), was only one aspect, along with check and checkmate logic; I also had to consider ‘special’ issues such as castling validation (i.e. if the king or rook moves beforehand, castling is then ‘illegal’), promotion, en passant, etc.
To familiarise myself with the board representation used internally by the engine, I created a reference chessboard that illustrates the relationship between standard algebraic chess notation and the engine’s internal coordinate system:

The engine represents the board as a zero-indexed 8×8 array indexed grid, where each square is mapped to a (rank, file) pair. For example, a1 maps to (0,0) and h8 maps to (7,7). The board is internally represented as a zero-indexed two-dimensional array. Each square is mapped from algebraic notation (a1–h8) to (rank, file) coordinates, where ranks increase from white’s side (1 to 8) and files increase from left to right (a to h). As you can see, this created a potentially confusing clash between standard chess notation and the engine’s internal coordinate system. Chess notation is designed for human readability, whereas the engine relies on zero-indexed ranks and files. Small translation issues like these could quickly make the game logic difficult to follow, so the codebase needed a clean separation of responsibilities.
To organise this complexity, the code needed clean separation:
- Geometry -> coords.ts
- Identity -> colour.ts, Piece.ts
- Action -> Move.ts
- State -> Square.ts, ChessBoard.ts
- Rules and move legality -> LegalMoveFilter.ts, ChessBoard.ts
- Evaluation and search -> evaluatePosition.ts, minimax.ts
Organising the project in this way made it much easier to manage the complexity of the engine as new rules and features were added.
Furthermore, this complexity was taken up a notch by the need to record and reconstruct positions using FEN notation, which became an important part of the engine. FEN was possible because the code cleanly separated board representation, square identity, and game state. It encoded six pieces of information: the board position, the side to move, castling rights, the en passant target square, the halfmove clock, and the fullmove number.
Standing for Forsyth-Edwards Notation, FEN is a single-line snapshot of a chess position. It begins at rank 8 and reads from left to right across each rank before moving down to rank 1. In the engine, this corresponds to reading from the internal top row downwards, thus going in the opposite direction to rank numbers, which increase from bottom to top (as outlined above). Just to make things even more confusing, knights are represented by the phonetic n, not k, since k is reserved for the king; and by convention, black pieces are written in lowercase and white pieces in uppercase.
In this project, FEN served several practical purposes. It allowed the board to be serialised into a standard notation, made it possible to reload exact positions, preserved key state such as castling rights and en passant availability, and supported features such as position setup, repetition tracking, and communication with the computer-move worker. The engine’s undo system itself is handled through internal move-history records, but FEN provides a reliable snapshot of the board at any given moment.
The initial position in FEN, before any pieces have moved, is:
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq – 0 1
The first field describes the board from rank 8 down to rank 1. The remaining fields indicate that it is White to move; that both sides still retain full castling rights; that there is currently no en passant target square; and that the halfmove and fullmove counters are 0 and 1 respectively.
- Rank 8: rnbqkbnr: rook/knight/bishop/queen/king/bishop/knight/rook
- Rank 7: pppppppp: pawn/pawn/pawn/pawn/pawn/pawn/pawn/pawn
- Rank 6: 8: eight empty squares
- Rank 5: 8: eight empty squares
- Rank 4: 8: eight empty squares
- Rank 3: 8: eight empty squares
- Rank 2: PPPPPPPP: PAWN/PAWN/PAWN/PAWN/PAWN/PAWN/PAWN/PAWN
- Rank 1: RNBQKBNR: ROOK/KNIGHT/BISHOP/QUEEN/KING/BISHOP/KNIGHT/ROOK
- w KQkq – 0 1 indicates that a full move (white then black) is to take place.
This can be contrasted with algebraic chess notation, which is much easier on the eye for human players. For example, white’s opening pawn move is written simply as e4 in SAN (Standard Algebraic Notation). Although the project worked extensively with internal coordinates and FEN, full Portable Game Notation (PGN – e.g., 1. e2 e4 2. Nf3 Nc6) or SAN move-history output was not the main focus of the final implementation. In many ways, ensuring that the chess logic itself was reliable, while also supporting accurate position notation and reconstruction, was one of the priorities and took up a large part of the project.
Meanwhile, one of the other complex aspects of the app was constructing the three computer difficulty levels:
- Easy
- Medium
- Hard
Rather than simply making the harder modes “more random” or “faster”, I designed each level around a different move-selection strategy. At the lowest level, the computer plays much more like a beginner, often choosing from legal moves without deep calculation. At the higher levels, it switches to a minimax-based search, which simulates future move sequences and evaluates the resulting positions before deciding what to play.
The core of this system is the minimax algorithm with alpha-beta pruning. In simple terms, the engine explores the game tree to a limited depth, assuming that the computer will try to maximise its advantage while the human opponent will try to minimise it (given that the human is the opponent). Alpha-beta pruning improves efficiency by cutting off ‘branches’ that are already known to be worse than another available option, meaning the engine does not have to search every possibility fully. This made it feasible to create stronger difficulty levels without making the program unreasonably slow.
The three levels differ mainly in how far ahead they search and how consistently they choose the strongest move. Medium mode searches two plies ahead, while hard mode searches four plies ahead, and also orders candidate moves heuristically so that promising options, such as captures, promotions, castling and centralising moves, are examined first. This improves both strength and efficiency, since stronger candidate moves are likely to produce better pruning.
To evaluate positions at the end of the search, I implemented a scoring function based primarily on material balance. Each piece is assigned a conventional chess value: pawns are worth the least, then knights and bishops, then rooks, with the queen naturally as the most valuable piece apart from the king. Checkmate is treated as overwhelmingly decisive, while drawn positions are scored neutrally. Although this is only a first layer of chess understanding, it provides a practical heuristic that allows the engine to distinguish between stronger and weaker positions.
Another challenge was avoiding overly mechanical play. If the engine always selected the first equally good move returned by the search, it became predictable, especially in the opening, where fixed move ordering caused it to repeat the same choices, as I experienced when testing a few games. To solve this, I introduced controlled randomness. When several moves share the same best score, the engine randomly selects between them, which makes games feel less scripted. I also allowed the easier settings to make occasional deliberate mistakes, so that the player experiences a more believable progression in difficulty rather than facing a perfectly clinical opponent from the outset.
I also added a further refinement around queen safety. On easy and medium settings, if the chosen move would leave the computer’s own queen immediately vulnerable, the engine tries to substitute a safer alternative. Hard mode does not need this safeguard in the same way, because its deeper search already handles tactical danger more reliably. Altogether, this meant the difficulty system was not just a matter of assigning labels, but of carefully balancing search depth, heuristics, randomness and tactical safeguards to produce a computer opponent that felt distinct to play against rather than robotic (even though it IS a robot).
Other challenges included ensuring that the app was responsive when viewed on different devices, which required significant final design work; and sound latency: the computer would make a move, only for the accompanying sound to be delayed (particularly if the player’s device has a low CPU). To improve this, I preloaded the audio files in advance and reused cached audio elements instead of creating them at the moment a move happened. That reduced the gap between the visual move and the sound effect, making gameplay feel more responsive and polished.
Finally, I added a SQLite backend so that a Leaderboard could be added, ensuring a small external HTTP API:
- GET /api/leaderboard returns { entries: [{ name, points }] }
- POST /api/leaderboard/wins records a win with JSON like { playerName, difficulty, resultStatus: “checkmate” }
- POST /api/leaderboard/revoke removes previously awarded points with JSON like { playerName, difficulty }
This, along with the rest of the app guide, is deliberately kept in the ‘Game Info’ modal window, which only appears when the button is clicked, ensuring a minimal user experience in the spirit of the original Kasparov-brand electronic chess boards. Give the game a go and add your name to the Leaderboard to see if it can go to the top!
External attribution:
Sound on/off icons created by Andrean Prabowo – Flaticon
Chess sound effects from Pixabay
Tech stack
- Front-end: HTML, CSS, TypeScript
- Build tooling: Vite
- Backend / data: SQLite
- Testing: Vitest, Playwright
- Deployment: Render