How to implement Reversi game in React. Part 1: Game engine

How to implement Reversi game in React. Part 1: Game engine

Reversi or Othello is a strategic board game with n column and n rows. Most commonly used are 8x8 square board. This game is very common in Japan and Japanese children learn this game at a very young age. In this blog post, let's address some challenge when implement the game logic in Javascript. Source code and Demo

2021-10-19_22-54-26.png

The rules of Reversi

The rules are very simple:

  • Two players, each have either "dark" or "light" disks. The game start with four disk at the center of the board. The dark player move first

image.png

  • Player must place a piece on the board so that at least one occupied line between the new piece and the old piece between them

  • Then the pieces that are capture in between will revert in to black and vice versa. This rule applies for vertical, horizontal and diagonal lines

image.png

  • When one player cannot make any valid move, the turn goes back to the other player

  • When both player cannot make any valid moves, the game ends. The one who have more pieces are the winner

Class and object design

The atomic element of the Game is the 8x8 board. Each board has 64 Cells. The columns are numbered from "a" to "g", and the rows are numbered from "1" to "8"

image.png

Board object will have all the methods needed to play the game such as darkMove, lightMove We also have a calculate method, to re-calculate the board after each move and then flip the cells if applicable

ROwxIWH144NZwQyeAl91Dmo8Y2p3-W0LDL4pCIXxYjd1sksb7sEm-kzMevZCXhVEAUvTfamnbxKOBvGINB48x4nZVH1OB5AEusReEc0td0EISoi4fJ5ypcdht1agd4WAp_7rnTFzryFTpUrBbNpezpZKOcE-pT-Ooq2nXrrJTHP9wjTzhszTfoUxZUD4cQtLq5FyU0mJ7nnga75OvV_aINmH5JNLh_8kewzFnsjLdZDl5MZd.png

General code flow

The core rule of Reversi game requires to identify lines of Cells, and whether or not the cells trapped between 2 cells are "reversible"

SoWkIImgAStDuG8pk7Jj2d8IArEB57Bpor8Lb99DbAovh98p4lFIKnMIKtDom48APOavfQaWYSISvDASn99KXQJynA8KQw1sXTIy591ce3IGnUK08W00.png

To be able to calculate this, we need a Line object, which contains some Cells

The common instance of Line can be Row (horizontal line), or Column (vertical line).

image.png

We also have 8 Forward slash diagonal line ("/")

image.png

And 8 backward slash diagonal line ("\")

image.png

Constructing lines

Constructing vertical lines or horizontal lines are pretty easy based on row and column. We can easily have the lines using 2 nested for loop

Constructing backlash and forward slash is harders and less intuitive. One trick is to slide the grid To construct forward slash "/", we shift the bottom row to the right, by prepending the rows with null. The bottom line with 7, the one above that with 6, etc.

image.png

After that, we get all the columns and using lodash/compact to remove null values Below is the full source code for that

// src/game/Board.js
import * as R from "ramda";
class Board {
  getDiagonalFwdSlash() {
    const shiftedRows = [];
    for (let index = 0; index < 8; index++) {
      const row = [...nullx(index), ...this.rows[index]];
      shiftedRows.push(row);
    }
    const diagonalLines = [];
    for (let col = 0; col < 15; col++) {
      const retCol = [];
      "12345678".split("").forEach((rowValue, rowIndex) => {
        retCol.push(shiftedRows[rowIndex][col]);
      });
      diagonalLines.push(_compact(retCol));
    }
    return diagonalLines;
  }
}

The same method can apply for backward slash ("\"), but this time, stead of shifting the bottom rows, we need to shift the top rows. The last row (index=7), should not be shifted, while the top row with index=0, will be shifted 7 cells

Calculate after each moves

One thing to remember is that: given a line , we will have to caculate from left to right LTR or right-to-left RTL to detect the reversi pattern

And since all the vertical, horizontal, backslash and forward slash are normalized into one line, we don't need to distinguish between top-to-bottom or top-right to bottom-left direction

The algorithm is very simple. From the current position, find the next cell with the same color. And then flip every cells in between

// src/game/Line.js
import * as R from "ramda";

class Line {
  calculateLeftToRight(code, cells) {
    const startIndex = _findIndex(cells, { code });
    const startCell = cells[startIndex];
    const endIndexWithinSlice = R.pipe(
      R.slice(startIndex + 1, Infinity),
      R.takeWhile((c) => !c.isEmpty()),
      R.findIndex((c) => c.value === startCell.value)
    )(cells);

    return R.pipe(
      R.slice(startIndex + 1, startIndex + endIndexWithinSlice + 1),
      R.map((c) => {
        c.reverse();
        return c;
      })
    )(cells);
  }

  calculateRightToLeft(code, cells) {
    return R.pipe(R.reverse(), R.curry(this.calculateLeftToRight)(code))(cells);
  }
}

Conclusion

In the section above, I included some essential element to implement the game logic in Javascript. The fun part of developing the game is to strictly follow TDD practice.

The full source code is pushed to Github reversi-react. The final version with React interface is hosted in Vercel: Reversi React

In the next part of the post, we'll try to highlight some challenges when implement React interface