This content originally appeared on DEV Community and was authored by FredLitt
My Experience Building A Chess App In React
Hi, my name is Fred and I’m a chess player who has been learning to code using The Odin Project for the past 10 months. After gaining some familiarity with React, I thought it would be a fun challenge to try and build a chess application using React. I'm also interested in finding my first job as an entry level developer and would love to chat with anyone who is hiring or has suggestions on getting into the field.
- Replit link: https://replit.com/@FredLitt/Chess-Engine#src/chessBoard.js
- Github link: https://github.com/FredLitt/Chess-Engine
- e-mail: fredolitt@gmail.com
What the App Does
1. Supports All Basic Rules of Chess
- Pieces are capable of performing all legal moves and possible moves are indicated with a circular highlight on the possible move square. The last played move’s squares are highlighted as well.
b. Castling is supported in either direction, and cannot be done if either the king or corresponding rook has moved, or if the king is in check or would move through check.
c. En passant, which proved to be one of the most challenging aspects of the game to program due to the amount of conditionals that must be met.
Per the Wiki link:
- the capturing pawn must be on its fifth rank;
- the captured pawn must be on an adjacent file and must have just moved two squares in a single move (i.e. a double-step move);
- the capture can only be made on the move immediately after the enemy pawn makes the double-step move; otherwise, the right to capture it en passant is lost.
d. Checkmate: When the attacked king’s army has no means of salvaging their leader.
2. App Features
a. Move notation and captured piece tracker
b. Pawn Promotion
c. End of Game Detection. The current game recognizes checkmate and stalemate and creates a new game popup accordingly.
d. Changing board themes: LOOK at those pretty colors
e. Takeback button
How The App Is Built
1. The Game Logic
a. The Board Class
The board is represented in a 2d array of “square” objects, each with a unique coordinate and the presence or non-presence of a piece (which are themselves objects).
export class Board {
constructor() {
this.squares = []
for (let row = 0; row < 8; row++) {
const boardRow = []
for (let col = 0; col < 8; col ++){
const square = {
piece: null,
coordinate: [row, col]
}
boardRow.push(square)
}
this.squares.push(boardRow)
}
The board has a large variety of methods to manipulate itself and to gather information about the current board position...
getPossibleMoves(pieceToMove, fromSquare){
const searchOptions = {
board: this,
fromSquare: fromSquare,
squaresToFind: "possible moves"
}
this.selectedPiece.possibleMoves = pieceToMove.findSquares
(searchOptions)
this.markPossibleMoveSquares()
}
updateBoard(startSquare, endSquare){
startSquare.piece = null
endSquare.piece = this.selectedPiece.piece
}
b. The Piece Classes
Each type of piece has its own class that is capable of
- Finding the squares that it currently controls
- Finding all the squares that it could possibly move to
It wasn’t until I started writing the logic for determining king moves that I realized just how distinct these two things were. For example:
Black could not move the knight to the X square as it would expose the black king, but the square is still a controlled square as the white king could not move there either
Therefore, each piece has a unique method for each case. In either case an array of coordinates is returned.
findSquares({board, fromSquare, squaresToFind}) {
const [fromRow, fromCol] = fromSquare
const knightMoves = {
"NorthOneEastTwo": [fromRow - 1, fromCol + 2],
"NorthTwoEastOne": [fromRow - 2, fromCol + 1],
"SouthOneEastTwo": [fromRow + 1, fromCol + 2],
"SouthTwoEastOne": [fromRow + 2, fromCol + 1],
"NorthOneWestTwo": [fromRow - 1, fromCol - 2],
"NorthTwoWestOne": [fromRow - 2, fromCol - 1],
"SouthOneWestTwo": [fromRow + 1, fromCol - 2],
"SouthTwoWestOne": [fromRow + 2, fromCol - 1]
}
if (squaresToFind === "controlled squares") {
return this.findControlledSquares(board, fromSquare, knightMoves)
}
if (squaresToFind === "possible moves") {
return this.findPossibleMoves(board, fromSquare, knightMoves)
}
}...
A Shared Search Method for Long Range Pieces:
I discovered that the Queen, Rook and Bishop had similar patterns for finding possible and controlled squares. All of them are capable of moving as many squares as possible in a given direction until:
- An enemy piece is reached (at which point a capture is possible)
- The square before a friendly piece is reached
- The edge of the board is reached
Each of these pieces iterate from their given starting coordinate in each of their possible directions, and continue iterating until one of these conditions is met. This enabled me write a generalized method that could be used by each of these pieces.
const findSquaresForLongRange =
({piece, board, fromSquare, squaresToFind, pieceDirections}) => {
const possibleSquares = []
const [fromRow, fromCol] = fromSquare
const completedDirections = []
for (let i = 1; i < 8; i++) {
const allDirections = {
"North": [fromRow - i, fromCol],
"South": [fromRow + i, fromCol],
"East": [fromRow, fromCol + i],
"West": [fromRow, fromCol - i],
"NorthWest": [fromRow - i, fromCol - i],
"NorthEast": [fromRow - i, fromCol + i],
"SouthWest": [fromRow + i, fromCol - i],
"SouthEast": [fromRow + i, fromCol + i]
}
Each piece simply needs to pass in the directions that they are capable of...
class Bishop {
constructor(color) {
this.type = "bishop"
this.color = color
if (color === "white") {
this.symbol = pieceSymbols.whiteBishop
} else if (color === "black") {
this.symbol = pieceSymbols.blackBishop
}
}
findSquares({board, fromSquare, squaresToFind}) {
return findSquaresForLongRange({
piece: this,
pieceDirections: ["NorthWest", "NorthEast", "SouthWest", "SouthEast"],
board,
fromSquare,
squaresToFind
})
}
}
directions that are not included will be skipped over immediately
for (const direction in allDirections) {
if (!pieceDirections.includes(direction) || completedDirections.includes(direction)){
continue;
}
c. End of Game Detection
Currently, the game is capable of detecting checkmate and stalemate.
The game detects an end of game by running a function that determines all of a player’s possible moves. The check detection method returns a boolean of whether a king’s square is contained in the opposing player’s attacked squares.
- If player has possible moves → gameOver ≠ true
- If player has no possible moves & is in check → “other player wins”
- If player has no possible moves but is not in check → “stalemate”
2. The UI
The App function contains the following components, all of which rely on the data from the Board Object to determine what to render.
- A conditionally appearing modal to start a new game (appears when game is over)
- A BoardUI component which displays the chessboard, contains a pop up for pawn promotions and contains the game’s option buttons
- A CapturedPieceContainer component for white pieces and for black pieces
- A MoveList component that renders chess notation of the current game
The chessboard is contained by a BoardUI component, which uses the data from the Board classes 2d array of squares to render the current position.
<table
id="board"
cellSpacing="0">
<tbody>
{gameDisplay.boardPosition.map((row, index) =>
<tr
className="board-row"
key={index}>
{row.map((square) =>
<td
className={getSquaresClass(square)}
coordinate={square.coordinate}
piece={square.piece}
key={square.coordinate}
style={{
backgroundColor: isLightSquare(square.coordinate) ? lightSquareColor : darkSquareColor,
opacity: square.isLastPlayedMove ? 0.6 : 1.0
}}
onClick={(e) => move(e)}>
{square.piece !== null && square.piece.symbol}
{square.isPossibleMove &&
<span className="possible-move"></span>} </td>)}
</tr>)}
</tbody>
</table>
The board is displayed using an HTML table. Squares containing a piece display the piece’s symbol and when a piece to move is selected, its possible move squares are given a colored element to highlight them.
A Possible Improvement...
An issue I ran into in my code dealt with the nature of how React knows when to update the interface. Although the Board object is very good at mutating itself, React won’t know to update because the object that is being referenced is the same. This forced me to create a method on Board that returns a copy of itself...
clone(){
let newBoard = new Board()
for (const property in this){
newBoard[property] = this[property]
}
return newBoard
}
which could then be passed in for state changes...
setBoard(board.clone())
However, this extra step does not really take full advantage of React. Taking a more functional approach to writing the methods in the Board class could remove the need for this. If I end up doing a large scale refactor of this project, I believe this would be a great opportunity for improvement and chance to make the best use of React’s capabilities.
A nested conditional component within BoardUI...
The BoardUI component also contains a conditionally rendered PromotionModal component, which relies on the BoardUI’s state to render the appropriately colored pieces as a pop-up
const [pawnPromotion, setPawnPromotion] =
useState({
pawnIsPromoting: false,
color: null,
promotionSquare: null})
Positioning this just the way I wanted took some effort, and I finally landed on making use of CSS calc() function and CSS variables to achieve my desired effect.
.promotion-pieces {
...
position: fixed;
top: 50%;
left: calc(0.5 * (100vw - var(--board-length) - var(--move-list-width)) + 0.5 * var(--board-length));
transform: translate(-50%, -50%);
...
}
3. Game Options
a. New Game: Sets game to initial game settings, then sets the App’s state to a copy of that board
const createNewGame = () => {
board.startNewGame()
setBoard(board.clone())
}
b. Flip Board: Checks player currently at bottom of screen and rearranges the game’s squares in reverse order:
const flipBoard = () => {
const updatedPosition = {}
const boardToFlip = board.squares
const flippedBoard = []
if (gameDisplay.playerPerspective === "black"){
for (let row = 7; row >= 0; row--){
const boardRow = []
for (let col = 7; col >= 0; col --){
boardRow.push(boardToFlip[row][col])
}
flippedBoard.push(boardRow)
}
updatedPosition.playerPerspective = "white"
updatedPosition.boardPosition = flippedBoard
setGameDisplay(updatedPosition)
return
}
if(gameDisplay.playerPerspective === "white"){
for (let row = 0; row <= 7; row++){
const boardRow = []
for (let col = 0; col <= 7; col++){
boardRow.push(boardToFlip[row][col])
}
flippedBoard.push(boardRow)
}
updatedPosition.playerPerspective = "black"
updatedPosition.boardPosition = flippedBoard
setGameDisplay(updatedPosition)
return
}
}
c. Takeback:
const takeback = () => {
// Create list of moves equal to the current game minus the last
const movesToPlayBack = board.playedMoveList.slice(0, -1)
// Reset game
createNewGame()
// Plays through the list of moves
for (let i = 0; i < movesToPlayBack.length; i++){
board.selectPieceToMove(movesToPlayBack[i].fromSquare)
const targetSquare = movesToPlayBack[i].toSquare
if (movesToPlayBack[i].moveData.promotionChoice){
const pieceType = movesToPlayBack[i].moveData.promotionChoice
const pieceColor = movesToPlayBack[i].piece.color
const promotionChoice = findPiece(pieceColor, pieceType)
return board.movePiece(targetSquare, promotionChoice)
}
board.movePiece(targetSquare)
}
}
d. Board Theme: Sets CSS variables for colors to various color schemes
const changeTheme = (lightSquareChoice, darkSquareChoice, highlightChoice) => {
document.documentElement.style.setProperty("--light-square", lightSquareChoice)
document.documentElement.style.setProperty("--dark-square", darkSquareChoice)
document.documentElement.style.setProperty("--highlight", highlightChoice)
}
Final Thoughts
This was by far my favorite coding project that I’ve worked on so far. The combination of my own personal love of chess and the challenge of accounting for all the complexity and nuances of the game was difficult but equally rewarding. Some things I’d consider adding at this point are:
- 2-player network chess
- End of game detection for fifty-move rule and threefold repetition
- Different chess set options
- Forward and back button on move list to look through a game
- Draggable rather than clickable moves
- Update codebase to TypeScript
- Refactor in more of a functional rather than object oriented style
If I were to go back in time in my coding journey I think I would have attempted to start this project sooner than I did. Learning from the mistakes that I made during this project has helped me grow tremendously and I’m excited to continue building and see what I pick up along the way. Feel free to e-mail me if you're someone looking to hire a new dev!
This content originally appeared on DEV Community and was authored by FredLitt

FredLitt | Sciencx (2022-02-21T13:37:38+00:00) My Experience Building A Chess App In React. Retrieved from https://www.scien.cx/2022/02/21/my-experience-building-a-chess-app-in-react/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.