Course: Section

Get Started with Modern React: Learn by Doing

Episode: Title

S03・V15: Calculating the Winner

Date Created: July 17th, 2019
Last Updated: 27 days ago

Objectives
  1. We will create a function to calculate if a combination of moves has resulted in a win or not.
  2. We will show the result of a winning combination in the status text.
Watch Video
Duration: 4m 49s

Calculating the Winner

Next, we will show when the game has been won. We will create a helper function to calculate which player is the winner. At the end of the file, let’s create a function using the function expression syntax. This will ensure the function is hoisted to the top, and accessible to our components.

We will call our function calculateWinner. It will take our squares array as its input parameter.

function calculateWinner(squares) {

}

It will return either X, or O, or null. To help us write which combinations are winning ones, let’s add the indexes of the squares in comments.

function calculateWinner(squares) {
  /*  0 1 2  3 4 5  6 7 8  */}

A player wins if they have a line of Xs or Os. So, we will put our winning combinations in an array that we will call lines. The first winning combination is the first row of our board. That is, the row of indices: [0, 1, 2]. The second and third rows are also winners: [3, 4, 5] and [6, 7, 8]. The columns are winning combinations: [0, 3, 6], 1, 4, 7], and [2, 5, 8]. The diagonal from top left to bottom right will also win the game: [0, 4, 8]. And the final winning combination is the diagonal from top right to bottom left: [2, 4, 6].

function calculateWinner(squares) {
  /*
  0 1 2
  3 4 5
  6 7 8
  */
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

}

Now, let’s iterate over each combination in this winning lines array. If the values at each index within a combination are either all Xs or all Os, then the player has won.

We will loop over the lines array: for (let line of lines) {...}. We will use “destructuring assignment syntax” on each line to unpack each individual index within it: const [a, b, c] = line. If the value at index a is non-null: squares[a], and if it is equal to the value at index b: squares[a] === squares[b], and if the value at index a is also the same as the value at index c: squares[a] === squares[c] then we know that all values in a winning line are all the same and not null. In this case, we can return the winning value, which is either an X or an O: return squares[a]. If this is not the case, let’s return null from the function to indicate that there is no winner: return null.

function calculateWinner(squares) {
  /*
  0 1 2
  3 4 5
  6 7 8
  */
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

  for (let line of lines) {    const [a, b, c] = line;    if (      squares[a] &&      squares[a] === squares[b] &&      squares[a] === squares[c]    ) {      return squares[a];    }  }  return null;}

Update Status with Winner

We will call the calculateWinner function in the Board component to check if a player has won. If a player has won, we can display it in the status text. We will update the status depending on whether there is a winner or not.

  const winner = calculateWinner(squares);
  const status = winner ?
    `Winner: ${winner}` :
    `Next player: ${xIsNext ? 'X' : 'O'}`;

Now, we can test it in the browser. Click on index 0 first. The game now correctly shows that the next player is O. Then click for player O on index 1. Click for player X on index 4, in the center. Click for player O on index 2. Finally, click for player X on index 8. The game correctly shows the winner is player X.

Code Snapshot

src/index.js
import React, { useState } from 'react';
import ReactDOM from 'react-dom';

import './index.css';

const Square = props => {
  return (
    <button
      className="square"
      onClick={props.onClickEvent}
    >
      {props.value}
    </button>
  );
};

const Board = () => {
  const initialSquares = Array(9).fill(null);
  const [squares, setSquares] = useState(initialSquares);
  const [xIsNext, setXIsNext] = useState(true);

  const handleClickEvent = i => {
    const newSquares = [...squares];
    newSquares[i] = xIsNext ? 'X' : 'O';
    setSquares(newSquares);
    setXIsNext(!xIsNext);
  };

  const winner = calculateWinner(squares);
  const status = winner ?
    `Winner: ${winner}` :
    `Next player: ${xIsNext ? 'X' : 'O'}`;

  const renderSquare = i => {
    return (
      <Square
        value={squares[i]}
        onClickEvent={() => handleClickEvent(i)}
      />
    );
  };

  return (
    <div>
      <div className="status">{status}</div>
      <div className="board-row">
        {renderSquare(0)}
        {renderSquare(1)}
        {renderSquare(2)}
      </div>
      <div className="board-row">
        {renderSquare(3)}
        {renderSquare(4)}
        {renderSquare(5)}
      </div>
      <div className="board-row">
        {renderSquare(6)}
        {renderSquare(7)}
        {renderSquare(8)}
      </div>
    </div>
  );
};

const Game = () => {
  return (
    <div className="game">
      <Board />
    </div>
  );
};

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

function calculateWinner(squares) {
  /*
  0 1 2
  3 4 5
  6 7 8
  */
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

  for (let line of lines) {
    const [a, b, c] = line;
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c]
    ) {
      return squares[a];
    }
  }

  return null;
}

Summary

We created a helper function to calculate if there was a winner. The calculation ran on each re-render of the status text, and showed the result.

Next Up…

In the next video, we will prevent setting the value of a square if someone has already won or if a square already has a value.