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
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
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
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"
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
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"
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).
We also have 8 Forward slash diagonal line ("/")
And 8 backward slash diagonal line ("\")
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.
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