Browser-based Multiplayer Tic Tac Toe Game in React
Published: 2024-03-10 22:29:53

**This post, from our archives, will walk you through the steps to create a Tic Tac Toe game in React but be aware that the versions of libraries used are no longer the latest.** In particular, this article uses version 1 of our [React SDK](https://www.pubnub.com/docs/sdks/react) but all the steps and principles listed below are still valid today

Tic tac toe is a quintessential childhood [game](https://www.pubnub.com/solutions/gaming/). All it requires is something to write on and something to write with. But what if you want to play with someone that’s in another location? In this case, you would need to use an application that connects you and another player to the game.

The application needs to provide a real-time experience so every move you make is seen instantly by the other player and vice versa. If the application does not provide this experience, then you and many people, won’t probably use it anymore.

So how does a developer provide a [connected experience](https://www.pubnub.com/blog/connected-shared-experiences-a-developers-guide/) where the players can play tic tac toe, or any game, no matter where they are in the world?

Real-time Multiplayer Game Concepts
-----------------------------------

There are several ways to provide the real-time infrastructure for multiplayer games. You can go the route of building your own infrastructure from the ground up by using technologies and open-source protocols like [Socket.IO](https://www.pubnub.com/learn/glossary/what-is-socketio/), [SignalR](https://pubnub.com/guides/what-is-signalr/), or [WebSockets](https://www.pubnub.com/learn/glossary/what-is-websocket/).

While this may seem like an appealing route to take, you will encounter several issues; one such issue being scalability. It’s not hard to handle 100 users, but how do you handle 100,000+ users? Besides infrastructure issues, you still have to worry about maintaining your game.

At the end of the day, the only thing that matters is providing a great experience for the players of your game. But how do you do solve the infrastructure problem? This is where PubNub comes in.

PubNub provides the real-time infrastructure to power any application through its global [Data Stream Network](https://pubnub.com/products/pubnub-platform/).  With over [70+ SDKs,](https://pubnub.com/docs/) including the most popular programming languages, PubNub simplifies sending and receiving messages to any device in **under 100 ms**. It is secure, scalable and reliable so you don’t have to worry about creating and maintaining your own infrastructure.

In order to show how easy it is to develop a multiplayer game using PubNub, we will build a simple React tic tac toe game using the [PubNub React SDK](https://pubnub.com/docs/sdks/javascript/react). In this game, two players will connect to a unique game channel where they will play against each other. Every move a player makes will be published to the channel to update the other player’s board in real time.

App Overview
------------

Here is what our app will look like once we finish.

![Screen shot of the React Tic Tac Toe Game](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpy/5d2874d8b7c577a9ce9a5acc6ade755f/React-tic-tac-toe-overview.png "Screen shot of the React Tic Tac Toe Game")Players first join the lobby where they can create a channel or join a channel. If the player creates a channel, they get a _room id_ to share with another player. The player that created the channel becomes _Player X_ and will make the first move when the game starts.

[![Create a room channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpz/d6a1b08ddc0924624f35e779c0c0059a/create-room.gif "Create a room channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jpz/d6a1b08ddc0924624f35e779c0c0059a/create-room.gif)

The player that joins a channel with the _room id_ they were given becomes _Player O_. Players can only join channels when there is one other person in the channel. If there is more than one person, then a game is in progress for that channel and the player won’t be able to join. The game starts once there are two players in the channel.

[![Join the room channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq0/96a0ccd6b75f19b211747f353b27bd39/join-channel.gif "Join the room channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq0/96a0ccd6b75f19b211747f353b27bd39/join-channel.gif)

At the end of the game, the winner’s score is incremented by one point. If the game ends in a tie, then neither player is awarded a point. A modal is displayed to _Player X_ asking them to start a new round or to end the game. If _Player X_ continues the game, the board resets for the new round. Otherwise, the game ends and both players go back to the lobby.

[![Exit to lobby](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq1/f475cc1aeed8a4184348569634ed968d/exit-to-lobby-1.gif " Exit to lobby")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq1/f475cc1aeed8a4184348569634ed968d/exit-to-lobby-1.gif)

Set Up the Lobby
----------------

Before we set up the lobby, sign up for a free PubNub account to get your free Pub/Sub API keys from the [PubNub admin dashboard](https://admin.pubnub.com).

Once you get your keys, insert them into the constructor of **App.js**.

```js
// App.js
import React, { Component } from 'react';
import Game from './Game';
import Board from './Board';
import PubNubReact from 'pubnub-react';
import Swal from "sweetalert2";
import shortid  from 'shortid';
import './Game.css';

class App extends Component {
  constructor(props) {
    super(props);
    // REPLACE with your keys
    this.pubnub = new PubNubReact({
      publishKey: "YOUR_PUBLISH_KEY_HERE",
      subscribeKey: "YOUR_SUBSCRIBE_KEY_HERE"
    });

    this.state = {
      piece: '', // X or O
      isPlaying: false, // Set to true when 2 players are in a channel
      isRoomCreator: false,
      isDisabled: false,
      myTurn: false,
    };

    this.lobbyChannel = null; // Lobby channel
    this.gameChannel = null; // Game channel
    this.roomId = null; // Unique id when player creates a room
    this.pubnub.init(this); // Initialize PubNub
  }

  render() {
    return ();
    }
  }

  export default App;
```

Also in the constructor, the state objects and variables are initialized. We will go over the objects and the variables when they come up throughout the file. Finally, we initialized PubNub at the end of the constructor.

Inside of the **render** method and inside the **return** statement, we add the markup for the Lobby component.

```js
return (
    <div>
      <div className="title">
        <p> React Tic Tac Toe </p>
      </div>

      {
        !this.state.isPlaying &&
        <div className="game">
          <div className="board">
            <Board
                squares={0}
                onClick={index => null}
              />

            <div className="button-container">
              <button
                className="create-button "
                disabled={this.state.isDisabled}
                onClick={(e) => this.onPressCreate()}
                > Create
              </button>
              <button
                className="join-button"
                onClick={(e) => this.onPressJoin()}
                > Join
              </button>
            </div>

          </div>
        </div>
      }

      {
        this.state.isPlaying &&
        <Game
          pubnub={this.pubnub}
          gameChannel={this.gameChannel}
          piece={this.state.piece}
          isRoomCreator={this.state.isRoomCreator}
          myTurn={this.state.myTurn}
          xUsername={this.state.xUsername}
          oUsername={this.state.oUsername}
          endGame={this.endGame}
        />
      }
    </div>
);
```

The Lobby component consists of a title, an empty tic tac toe board (nothing happens if the player presses the squares) and the ‘Create_‘_ and ‘Join_‘_ buttons. This component is displayed only if the state value _isPlaying_ is false. If it’s set to true, then the game has begun and the component is changed to the Game component, which we will go over in the second part of the tutorial.

The Board component is part of the Lobby component as well. Within the [Board](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Board.js) component is the [Square](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Square.js) component. We won’t go into detail for these two components in order to focus on the Lobby and Game components.

When the player presses the ‘Create’ button, the button is disabled so the player can’t create multiple channels. The ‘Join’ button is not disabled, just in case the player decides to join a channel instead. Once the ‘Create’ button is pressed, the method **onPressCreate()** is called.

### Create a Channel

The first thing we do in **onPressCreate()** is generate a random string id that’s truncated to 5 characters. We do so by using [**shortid()**](https://www.npmjs.com/package/shortid). We append the string to ‘_tictactoelobby–_‘, which will be the unique lobby channel players subscribe to.

```js
// Create a room channel
onPressCreate = (e) => {
  // Create a random name for the channel
  this.roomId = shortid.generate().substring(0,5);
  this.lobbyChannel = 'tictactoelobby--' + this.roomId; // Lobby channel name

  this.pubnub.subscribe({
    channels: [this.lobbyChannel],
    withPresence: true // Checks the number of people in the channel
  });
}
```

In order to prevent more than two players from joining a given channel, we use [Presence](https://pubnub.com/docs/sdks/javascript/react). Later on, we will look at the logic for checking the occupancy of the channel.

Once the player subscribes to the lobby channel, a modal is displayed with the room id so another player can join that channel.

[![Share the room id](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq2/2e171d056766c2d974cb5b9f3001b5a3/share-roomid-1.png "Share the room id")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq2/2e171d056766c2d974cb5b9f3001b5a3/share-roomid-1.png)

This modal, and all the modals used in this app, are created by [**SweetAlert2**](https://sweetalert2.github.io/#download) to replace JavaScript’s default alert() popup boxes.

```js
// Inside of onPressCreate()
// Modal
Swal.fire({
  position: 'top',
  allowOutsideClick: false,
  title: 'Share this room ID with your friend',
  text: this.roomId,
  width: 275,
  padding: '0.7em',
  // Custom CSS to change the size of the modal
  customClass: {
      heightAuto: false,
      title: 'title-class',
      popup: 'popup-class',
      confirmButton: 'button-class'
  }
})
```

At the end of **onPressCreate()**, we change the state values to reflect the new state of the app.

```js
this.setState({
  piece: 'X',
  isRoomCreator: true,
  isDisabled: true, // Disable the 'Create' button
  myTurn: true, // Player X makes the 1st move
});
```

Once the player creates a room, they have to wait for another player to join that room. Let’s look at the logic for joining a room.

### Join a Channel

When a player presses the ‘Join’ button, a call to **onPressJoin()** is called. A modal is displayed to the player asking them to enter the _room id_ in the input field.

[![Enter the room id](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq3/97e46c029a2583ac018ab14f44ce8572/enter-roomid.png "Enter the room id")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq3/97e46c029a2583ac018ab14f44ce8572/enter-roomid.png)

If the player types in the _room id_ and presses the ‘OK’ button, then **joinRoom(value)** is called where **value** is the _room id_. This method is not called if the input field is empty or if the player presses the ‘Cancel’ button.

```js
// The 'Join' button was pressed
onPressJoin = (e) => {
  Swal.fire({
    position: 'top',
    input: 'text',
    allowOutsideClick: false,
    inputPlaceholder: 'Enter the room id',
    showCancelButton: true,
    confirmButtonColor: 'rgb(208,33,41)',
    confirmButtonText: 'OK',
    width: 275,
    padding: '0.7em',
    customClass: {
      heightAuto: false,
      popup: 'popup-class',
      confirmButton: 'join-button-class',
      cancelButton: 'join-button-class'
    }
  }).then((result) => {
    // Check if the user typed a value in the input field
    if(result.value){
      this.joinRoom(result.value);
    }
  })
}
```

The first thing we do in **joinRoom()** is append **value** to  ‘_tictactoelobby–_‘, similar to what we did in **onPressCreate()**.

```js
// Join a room channel
joinRoom = (value) => {
  this.roomId = value;
  this.lobbyChannel = 'tictactoelobby--' + this.roomId;
}
```

Before the player subscribes to the lobby channel, we have to check the total occupancy of the channel by using [hereNow()](https://pubnub.com/docs/sdks/javascript/react).  If the total occupancy is less than 2, the player can successfully subscribe to the lobby channel.

```js
// Check the number of people in the channel
this.pubnub.hereNow({
  channels: [this.lobbyChannel],
}).then((response) => {
    if(response.totalOccupancy < 2){
      this.pubnub.subscribe({
        channels: [this.lobbyChannel],
        withPresence: true
      });

      this.setState({
        piece: 'O', // Player O
      });

      this.pubnub.publish({
        message: {
          notRoomCreator: true,
        },
        channel: this.lobbyChannel
      });
    }
}).catch((error) => {
  console.log(error);
});
```

After the player subscribes to the lobby channel, the state value of _piece_ is changed to ‘O’ and a message is published to that lobby channel. This message notifies the _Player X_ that another player has joined the channel. We set up the message listener in **componentDidUpdate()**, which we will get to shortly.

If the total occupancy is greater than 2, then a game is in progress and the player attempting to join the channel will be denied access. The following code is below the if statement in **hereNow()**.

```js
// Below the if statement in hereNow()
else{
  // Game in progress
  Swal.fire({
    position: 'top',
    allowOutsideClick: false,
    title: 'Error',
    text: 'Game in progress. Try another room.',
    width: 275,
    padding: '0.7em',
    customClass: {
        heightAuto: false,
        title: 'title-class',
        popup: 'popup-class',
        confirmButton: 'button-class'
    }
  })
}
```

Let’s now take a look at **componentDidUpdate()**.

### Start the Game

In **componentDidUpdate()**, we check if the player is connected to a channel, that is, check that _this.lobbyChannel_ is not **null**. If it’s not **null**, we set up a listener that listens to all messages that arrive on the channel.

```js
componentDidUpdate() {
  // Check that the player is connected to a channel
  if(this.lobbyChannel != null){
    this.pubnub.getMessage(this.lobbyChannel, (msg) => {
      // Start the game once an opponent joins the channel
      if(msg.message.notRoomCreator){
        // Create a different channel for the game
        this.gameChannel = 'tictactoegame--' + this.roomId;

        this.pubnub.subscribe({
          channels: [this.gameChannel]
        });
      }
    });
  }
}
```

We check if the message arrived is _msg.message.notRoomCreator_, which is published by the player that joins the channel. If so, we create a new channel, ‘_tictactoegame–_‘, with the _room id_ appended to the string. The game channel is used to publish all the moves made by the players which will update their boards.

Finally, after subscribing to the game channel, the state value of _isPlaying_ is set to true. Doing so will replace the lobby component with the game component.

```js
 this.setState({
   isPlaying: true
 });

 // Close the modals if they are opened
 Swal.close();
}
```

Once the game component is shown, we want to close all the modals, if opened, from the Lobby component by doing _Swal.close()_.

Now that we have two players connected to a unique game channel, they can begin playing tic tac toe! In the next section, we will implement the UI and logic for the game component.

Build Game Features
-------------------

The first thing we do in **Game.js** is set up the **base constructor**:

```js
// Game.js
import React from 'react';
import Board from './Board';
import Swal from "sweetalert2";

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(''), // 3x3 board
      xScore: 0,
      oScore: 0,
      whosTurn: this.props.myTurn // Player X goes first
    };

    this.turn = 'X';
    this.gameOver = false;
    this.counter = 0; // Game ends in a tie when counter is 9
  }

  render() {
    return ();
  }
 }
export default Game;
```

For the state objects, we initialize the array _squares_ property, which is used to store the player’s positions in the board. This will be explained furthermore below. We also set the players score to 0 and set the value of _whosTurn_ to _myTurn_, which is initialized to **true** for _Player X_ and **false** for _Player O_.

The value of the variables _turn_ and _counter_ will change throughout the progression of the game. At the end of the game, _gameOver_ is set to **true**.

### Add the UI

Next, let’s set up the markup for the Game component inside of the **render** method.

```js
render() {
  let status;
  // Change to current player's turn
  status = `${this.state.whosTurn ? "Your turn" : "Opponent's turn"}`;

  return (
    <div className="game">
      <div className="board">
        <Board
            squares={this.state.squares}
            onClick={index => this.onMakeMove(index)}
          />
          <p className="status-info">{status}</p>
      </div>

      <div className="scores-container">
        <div>
          <p>Player X: {this.state.xScore} </p>
        </div>

        <div>
          <p>Player O: {this.state.oScore} </p>
        </div>
      </div>
    </div>
  );
}
```

We show the value of _status_ in the UI to let the players know if it’s their turn to make a move or if the other player’s turn. The boolean value of the state _whosTurn_ is updated every time a move is made. The rest of the UI consists of the Board component and the player’s score.

### Add the Logic

When the player makes a move on the board, a call to **onMakeMove(****index)** is made where **index** is the position the piece is placed on the board. The board has 3 rows and 3 columns, so 9 squares total. Each square has its own unique **index** value, starting with the value 0 and ending with the value 8.

```js
onMakeMove = (index) =>{
  const squares = this.state.squares;

  // Check if the square is empty and if it's the player's turn to make a move
  if(!squares[index] && (this.turn === this.props.piece)){
    squares[index] = this.props.piece;

    this.setState({
      squares: squares,
      whosTurn: !this.state.whosTurn
    });

    // Other player's turn to make a move
    this.turn = (this.turn === 'X') ? 'O' : 'X';

    // Publish move to the channel
    this.props.pubnub.publish({
      message: {
        index: index,
        piece: this.props.piece,
        turn: this.turn
      },
      channel: this.props.gameChannel
    });

    // Check if there is a winner
    this.checkForWinner(squares)
  }
}
```

After getting the state of the array _squares_, a conditional statement is used to check if the square the player touched is empty and if it’s their turn to make a move. If one or both conditions are not met, then the player’s piece is not placed on the square. Otherwise, the player’s piece is added to the array _squares_ in the index the piece was placed on.

For example, if _Player X_ makes a move in row 0, column 2 and the conditional statement is true, then _squares\[2\]_ will have the value of “X”.[![Example with the squares array](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq4/a8487d17f078fae4ccfa874baac16013/squares-example.png "Example with the squares array")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq4/a8487d17f078fae4ccfa874baac16013/squares-example.png)Next, the state is changed to reflect the new state of the game and _turn_ is updated so the other player can make their move. In order for the other player’s board to update with the current data, we publish the data to the game channel. All of this is happening in real time, so both players will immediately see their boards update as soon as a valid move is made. The last thing to do in this method is to call **checkForWinner(squares)** to check if there is a winner.

Before we do that, let’s take a look at **componentDidMount****()** where we set up the listener for new messages that arrive in the game channel.

```js
componentDidMount(){
  this.props.pubnub.getMessage(this.props.gameChannel, (msg) => {
    // Update other player's board
    if(msg.message.turn === this.props.piece){
      this.publishMove(msg.message.index, msg.message.piece);
    }
  });
}
```

Since both players are connected to the same game channel, they will both receive this message. The method **publishMove(index, piece)** is called, where **index** is the position that piece was placed and **piece** is the piece of the player that made the move. This method updates the board with the current move and checks if there is a winner. To prevent the player that made the current move to have to redo this process again, the _if_ statement checks if the player’s piece matches the value of _turn_. If so, their board is updated.

```js
// Opponent's move is published to the board
publishMove = (index, piece) => {
  const squares = this.state.squares;

  squares[index] = piece;
  this.turn = (squares[index] === 'X')? 'O' : 'X';

  this.setState({
    squares: squares,
    whosTurn: !this.state.whosTurn
  });

  this.checkForWinner(squares)
}
```

The logic of updating the board is the same as **onMakeMove()**. Let’s now go over **checkForWinner()**.

```js
checkForWinner = (squares) => {
  // Possible winning combinations
  const possibleCombinations = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

  // Iterate every combination to see if there is a match
  for (let i = 0; i < possibleCombinations.length; i += 1) {
    const [a, b, c] = possibleCombinations[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      this.announceWinner(squares[a]);
      return;
    }
  }
}
```

All the winning combinations are in the double array _possibleCombinations_, where every array is a possible combination to winning the game. Every array in _possibleCombinations_ is checked against the array _squares_. If there is a match, then there is a winner. Let’s follow an example to make this more clear.

Say that Player X makes a winning move in row 2 column 0. The _index_ of that position is 6. The board now looks like this:

[![Example of a winning move](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq5/77dcc8ab49b7e7b4e41ac6f1365a2df9/winning-move-example-1.png "Example of a winning move")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq5/77dcc8ab49b7e7b4e41ac6f1365a2df9/winning-move-example-1.png)The winning combination for _Player X_ is \[2,4,6\]. The array _squares_ is updated to: \[“O”, “”, “X”, “O”, “X”, “”, “X”, “”, “”\].

In the _for_ loop, when _\[a,b,c\]_ has the values of \[2,4,6\], the _if_ statement in the _for_ loop with be true since \[2,4,6\] all have the same value of _X_. The score of the winner needs to be updated, so **a****nnounceWinner()** is called to award the winning player.

If the game ends in a tie, there is no winner for that round. To check for tie games, we use a counter that increments by one every time a move is made on the board.

```js
// Below the for loop in checkForWinner()
// Check if the game ends in a draw
this.counter++;
// The board is filled up and there is no winner
if(this.counter === 9){
  this.gameOver = true;
  this.newRound(null);
}
```

If the counter reaches 9, then the game ends in a draw because the player did not make a winning move in the last square of the board. When this happens, the method **newRound()** is called with a **null** argument since there is no winner.

Before we go to this method, let’s go back to **a****nnounceWinner()**.

```js
// Update score for the winner
announceWinner = (winner) => {
  let pieces = {
    'X': this.state.xScore,
    'O': this.state.oScore
  }

  if(winner === 'X'){
    pieces['X'] += 1;
    this.setState({
      xScore: pieces['X']
    });
  }
  else{
    pieces['O'] += 1;
    this.setState({
      oScore: pieces['O']
    });
  }
  // End the game once there is a winner
  this.gameOver = true;
  this.newRound(winner);
}
```

The parameter of this method is _winner_, which is the player that won the game. We check if the winner is ‘X’ or ‘O’ and increment the winner’s score by one point. Since the game is over, the variable _gameOver_ is set to true and the method **newRound()** is called.

### Start a New Round

_Player X_ has the option to play another round or to end the game and go back to the lobby.

[![Endgame modal for Player X](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq6/f7b9623c4b0b628d51d8271f249747ba/playerx-endgame-modal.png "Endgame modal for Player X")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq6/f7b9623c4b0b628d51d8271f249747ba/playerx-endgame-modal.png)

The other player has told to wait until _Player X_ decides what to do.

[![Endgame modal for Player O](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq7/d96413b49ec1b85838aca013cfba174b/playero-endgame-modal.png "Endgame modal for Player O")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq7/d96413b49ec1b85838aca013cfba174b/playero-endgame-modal.png)

Once the _Player X_ decides what to do, a message is published to the game channel to let the other player know. The UI is then updated.

```js
newRound = (winner) => {
  // Announce the winner or announce a tie game
  let title = (winner === null) ? 'Tie game!' : `Player ${winner} won!`;
  // Show this to Player O
  if((this.props.isRoomCreator === false) && this.gameOver){
    Swal.fire({
      position: 'top',
      allowOutsideClick: false,
      title: title,
      text: 'Waiting for a new round...',
      confirmButtonColor: 'rgb(208,33,41)',
      width: 275,
      customClass: {
          heightAuto: false,
          title: 'title-class',
          popup: 'popup-class',
          confirmButton: 'button-class',
      } ,
    });
    this.turn = 'X'; // Set turn to X so Player O can't make a move
  }

  // Show this to Player X
  else if(this.props.isRoomCreator && this.gameOver){
    Swal.fire({
      position: 'top',
      allowOutsideClick: false,
      title: title,
      text: 'Continue Playing?',
      showCancelButton: true,
      confirmButtonColor: 'rgb(208,33,41)',
      cancelButtonColor: '#aaa',
      cancelButtonText: 'Nope',
      confirmButtonText: 'Yea!',
      width: 275,
      customClass: {
          heightAuto: false,
          title: 'title-class',
          popup: 'popup-class',
          confirmButton: 'button-class',
          cancelButton: 'button-class'
      } ,
    }).then((result) => {
      // Start a new round
      if (result.value) {
        this.props.pubnub.publish({
          message: {
            reset: true
          },
          channel: this.props.gameChannel
        });
      }

      else{
        // End the game
        this.props.pubnub.publish({
          message: {
            endGame: true
          },
          channel: this.props.gameChannel
        });
      }
    })
  }
 }
```

If the message is _reset_, then all the state values and variables, except the score for the players, are reset to their initial values. Any modals that are still open are closed and a new round starts for both players.

For the message _endGame_, all the modals are closed and the method **endGame()** is called. This method is in **App.js**.

```js
// Reset everything
endGame = () => {
  this.setState({
    piece: '',
    isPlaying: false,
    isRoomCreator: false,
    isDisabled: false,
    myTurn: false,
  });

  this.lobbyChannel = null;
  this.gameChannel = null;
  this.roomId = null;

  this.pubnub.unsubscribe({
    channels : [this.lobbyChannel, this.gameChannel]
  });
}
```

All the state values and variables are reset to their initial values. The channel names are reset to null because a new name is generated every time a player creates a room. Since the channel names won’t be useful anymore, the players unsubscribe from both the lobby and the game channel. The value of _isPlaying_ is reset to false, so the game component will be replaced with the lobby component.

The last method to include in **App.js** is **componentWillUnmount()**, which unsubscribes the players from both channels.

```js
componentWillUnmount() {
  this.pubnub.unsubscribe({
    channels : [this.lobbyChannel, this.gameChannel]
  });
}
```

This is all we need to do for the game to work! You can get the CSS file for the game in the [repo](https://github.com/PubNubDevelopers/react-tictactoe/blob/master/src/Game.css). Now, let’s get the game up and running.

Run the Game
------------

There are a couple of small steps we need to do before running the game. First, we need to enable the [Presence feature](https://pubnub.com/docs/sdks/javascript/react) because we use it to get the number of people in the channel (we used _withPresence_ when subscribing to the lobby channel). Go to the [PubNub Admin Dashboard](https://admin.pubnub.com/#/login) and click on your application. Click on **Keyset** and scroll down to **Application add-ons**. Toggle the **Presence** switch to **on**. Keep the default values the same.

[![Enable presence in PubNub Admin Dashboard](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq8/fb24ea6026b76234f79ee65d2426ac71/enable-presence-1.png "Enable presence in PubNub Admin Dashboard")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq8/fb24ea6026b76234f79ee65d2426ac71/enable-presence-1.png)

To install the three dependencies used in the app and to run the app, you can run the script **dependencies.sh** that’s in the root directory of the app.

```js
# dependencies.sh
npm install --save pubnub pubnub-react
npm install --save shortid
npm install --save sweetalert2

npm start
```

In the terminal, go to the app’s root directory and type the following command to make the script executable:

```js
chmod +x dependencies.sh
```

Run the script with this command:

```js
./dependencies.sh
```

The app will open in http://localhost:3000 with the lobby component displaying.![Run the React app locally](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jq9/fb4f1a520b9d68e1c283a61d5fa04653/run-the-app.png "Run the React app locally")Open another tab, or preferably window, and copy and paste http://localhost:3000. In one window, create a channel by clicking the ‘Create’ button. A modal will pop up displaying the _room id_. Copy and paste that id. Go to the other window and click the ‘Join’ button. When the modal pops up, type the _room id_ in the input field and press the ‘Okay’ button.

[![Create and join the channel](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqa/01343a31a36c16277b86d8a1ed92570b/create-join-lobby.png "Create and join the channel")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqa/01343a31a36c16277b86d8a1ed92570b/create-join-lobby.png)

Once the players are connected, the game will start. The window you used to create the channel makes the first move. Press any square on the board and see as the piece **X** is displayed on the board in real time for both windows. If you try to press another square in the same board, nothing will happen because it’s no longer your turn to make a move. In the other window, press any square on the board and the piece **O** is placed in the square.

[![Place the piece on the board](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqb/454174588fc9acf9c2f086c3e9c53a19/place-piece-on-board.png "Place the piece on the board")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqb/454174588fc9acf9c2f086c3e9c53a19/place-piece-on-board.png)

Keep on playing until there is a winner or a tie. A modal is then displayed announcing the winner of the round, or announcing that the game ended in a tie. In the same modal, _Player X_ will have to decide whether to continue playing or to exit the game. The modal for _Player O_ will tell them to wait for a new round.

[![End of game modals](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqc/71b832403fecb058be460282895e3f10/end-of-game.png "End of game modals")](https://images.ctfassets.net/3prze68gbwl1/asset-17suaysk1qa1jqc/71b832403fecb058be460282895e3f10/end-of-game.png)

Everything, except the score, is reset if _Player X_ continues the game. Otherwise, both players are taken back to the lobby where they can create or join new channels. Check out the game demo below:

**Have suggestions or questions about the content of this post? Reach out at [devrel@pubnub.com](mailto:devrel@pubnub.com).**

#### Contents

[Real-time Multiplayer Game Concepts](#h-0)[App Overview](#h-1)[Set Up the Lobby](#h-2)[Create a Channel](#h-3)[Join a Channel](#h-4)[Start the Game](#h-5)[Build Game Features](#h-6)[Add the UI](#h-7)[Add the Logic](#h-8)[Start a New Round](#h-9)[Run the Game](#h-10)

How can PubNub help you?
========================

This article was originally published on [PubNub.com](https://www.pubnub.com/blog/browser-based-multiplayer-tic-tac-toe-game-in-react/)

Our platform helps developers build, deliver, and manage real-time interactivity for web apps, mobile apps, and IoT devices.

The foundation of our platform is the industry's largest and most scalable real-time edge messaging network. With over 15 points-of-presence worldwide supporting 800 million monthly active users, and 99.999% reliability, you'll never have to worry about outages, concurrency limits, or any latency issues caused by traffic spikes.

Experience PubNub
-----------------

Check out [Live Tour](https://www.pubnub.com/tour/introduction/) to understand the essential concepts behind every PubNub-powered app in less than 5 minutes

Get Setup
---------

Sign up for a [PubNub account](https://admin.pubnub.com/signup/) for immediate access to PubNub keys for free

Get Started
-----------

The [PubNub docs](https://www.pubnub.com/docs) will get you up and running, regardless of your use case or [SDK](https://www.pubnub.com/docs)