How to make a Noughts and Crosses game in React
Tags: tictactoe, beginners, react
Published: 2022-02-23 17:40:21

Photo by Elīna Arāja from Pexels

## Introduction
**Fun fact**: The well known epic mini game "Tic-Tac-Toe" in Britain is called "Noughts and Crosses". While the former one is playing with consonant (T), the later one is playing with the vowel (O).

I am so excited to have it as my first React.js project. The simple game rule is just good for a junior developer to get familiar with handling logic. Let's take a look at UI design first and then the logic.

---

## UI Design
There are 3 main parts:
1. Information: Showing who wins. And better show also whose turn.
2. Body: The 9 boxes for users to input O or X.
3. Button: A "Play Again" button at the end of the game

For the body, I declare a default grid for the 9 boxes:
```javascript
const defaultGrid = [1, 2, 3, 4, 5, 6, 7, 8, 9];
```
Then a grid-container is made to contain the 3x3 grid. The gap together with background color do the trick of showing the lines like 井.

```css
.grid-container {
  display: grid;
  grid-template-columns: auto auto auto;
  grid-gap: 15px;
  background-color: #444;
}
```
Then loop the grid array in JSX.
```javascript
<div className="grid-container">
  {defaultGrid.map((boxNumber) => (
    <button
      type="button"
      key={boxNumber}
      value={boxNumber}
      onClick={handleClick}
    >
      {boxNumber}
    </button>
  ))}
</div>
```

---

## Logic
There should be 3 status for each box:
- Empty
- O
- X

Winning criteria is defined:
```javascript
const winArrays = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
    [1, 4, 7],
    [2, 5, 8],
    [3, 6, 9],
    [1, 5, 9],
    [3, 5, 7],
  ];
```

Two array is created to contain a list of box number that has been clicked by each side during the game.
```typescript
const [noughtsArr, setNoughtsArr] = useState<number[]>([]);
const [crossesArr, setCrossesArr] = useState<number[]>([]);
```

Flow of the program:
1. Clicking one of the 9 buttons
2. Insert clicked box number to corresponding array
3. Toggle turn

Winner calculation takes place in `useEffect()`, which keep watching at the states of the Noughts Array and Crosses Array.

I found a function `array.every()` in ES6 very helpful for the calculation. On [MDN website](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) it has provided an example to check if an array is a subset of another array. So my thought is to check each of the possible win array whether it is a subset of Noughts or Crosses clicked numbers or not. E.g. if the X side has clicked box 1,2,6,9, then `crossesArr` would be `[1,2,6,9]`. Neither `[1, 2, 3]` nor `[3, 6, 9]` in `winArrays` is a subset of `crossesArr` so Crosses has not been qualified to win yet.

```typescript
const isSubset = (xoArr: number[], winArr: number[]) =>
    winArr.every((number) => xoArr.includes(number));
```
```typescript
const noughtResult: number[][] = winArrays.filter(
  (winArray) => isSubset(noughtsArr, winArray)
);
const crossResult: number[][] = winArrays.filter(
  (winArray) => isSubset(crossesArr, winArray)
);
```
`filter()` will return value that passed `isSubset()` checking. So the last thing to do is to check the length of `noughtResult` and `crossResult` and see which is larger than 0 then that is the winner.

---

## Lesson Learned
**Array handling.** There is quite a number of arrays to handle and calculate. It is also a good exercise for spread operation.

**Functional Programming.** Tried applying the concepts of functional programming like immutability and separation of data and functions. And I found Single-responsibility principle(SRP) make the testing much easier.

The code below is showing...

- two higher order functions are created to get correct box status and render a corresponding icon (X/O) by a given box number.
- one higher order function to paint the win icon dynamically.
```javascript
<button
  ...
  style={{ color: getWinBoxStyle(boxNumber) }}
  ...
>
  {withIcon(getStatus(boxNumber))}
</button>
```


**Grid and Flex in CSS.** To build a table like layout in a modern way.

**Typescript.** This is my first typescript project with ESLint and I am getting mad with so many errors in my code to solve! Time spending on solving typescript errors is probably more than coding the program logic itself. After all, it would still only be a small taste of typescript to me as I didn't do all the variable type and check type.

**GitHub Pages.** Setting up GitHub Pages workflow for CI/CD. It does a list of actions like build, test and deploy every time I push the code.

---

## Thing to think about
**Extreme Case handling.** Think about 2 extreme cases: 
1. All 9 boxes clicked and X win
2. All 9 boxes clicked but draw game.

I would not be happy if X win but a "Draw Game!" message is shown. In `useEffect()` I thought the logic was in sequential order so I tried to put "Handle Draw" after checking winner but it did not work as expected. Below is the code that works fine. I lift "Handle Draw" up to the top so the program can check win before handle draw game as expected. But the order of code goes a bit strange. I'm not sure if anything I missed.
For a quick check, You can try below order of box clicking:
1 2 3 4 5 6 8 9 7 for X win at 9th box.
1 2 3 7 8 9 4 5 6 for draw game.

```typescript
const [winner, setWinner] = useState('');
...
useEffect(() => {
  // Handle Draw
  const combinedArr = [...crossesArr, ...noughtsArr];
  if (!winner && combinedArr.length === 9) {
    setWinner('Draw');
  }
  // Check who is eligible to win
  const noughtResult: number[][] = winArrays.filter(
    (winArray) => isSubset(noughtsArr, winArray)
  );
  const crossResult: number[][] = winArrays.filter(
    (winArray) => isSubset(crossesArr, winArray)
  );

  // Setting Winner
  if (noughtResult.length > 0) {
    setWinner('Noughts');
    const result = [...noughtResult];
    setWinResult(result);
  } else if (crossResult.length > 0) {
    setWinner('Crosses');
    const result = [...crossResult];
    setWinResult(result);
  }
}, [noughtsArr, crossesArr]);
```

**Nought and Crosses:**

[Github](https://github.com/samsonsham/noughts-and-crosses)
[Live site](https://samsonsham.github.io/noughts-and-crosses/)

---
**2022-02-27 Update:**
I added a variable `thisWinner` for "Handle Draw" to refer to. So that the flow would look better and make more sense.

```typescript
useEffect(() => {
    // Check who is eligible to win
    const noughtResult: number[][] = winArrays.filter((winArray) => isSubset(noughtsArr, winArray));
    const crossResult: number[][] = winArrays.filter((winArray) => isSubset(crossesArr, winArray));

    // Setting Winner
    let thisWinner = '';
    if (noughtResult.length > 0) {
      thisWinner = 'Noughts';
      const result = [...noughtResult];
      setWinResult(result);
    } else if (crossResult.length > 0) {
      thisWinner = 'Crosses';
      const result = [...crossResult];
      setWinResult(result);
    }
    setWinner(thisWinner);

    // Handle Draw
    const combinedArr = [...crossesArr, ...noughtsArr];
    if (!thisWinner && combinedArr.length === 9) {
      setWinner(`Draw`);
    }
  }, [noughtsArr, crossesArr]);
```