Multiplayer Tic Tac Toe Game in React Native for iOS and Android: Lobby and Joining
Tags: reactnative, tictactoe, pubnub
Published: 2019-07-24 17:13:46

---
canonical_url: https://www.pubnub.com/blog/multiplayer-mobile-tic-tac-toe-react-native-ios-android-part-one/
---

Tic tac toe is a classic paper-and-pencil game that we're all too familiar with. The rudimentary rules of tic tac toe are simple: two players, X and O, take turns placing their pieces in a square on a 3x3 table. A winner is declared when one of the two players places three of their pieces in a horizontal, vertical or diagonal row.

In this tutorial series, we will develop the classic tic tac toe game in React Native, and allow two players to play against one another in realtime. In this part (Part One), we'll set up the lobby where players will be able to enter their username, then create or join a room to play. In [Part Two, we'll implement and test the game](https://dev.to/pubnub/multiplayer-tic-tac-toe-game-in-react-native-for-ios-and-android-player-turns-and-deployment-343n).

## Multiplayer Gaming and PubNub

Our aim is to provide a [connected shared experience](https://www.pubnub.com/blog/connected-shared-experiences-a-developers-guide/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22) for players, where they can play with their friends anytime, anywhere around the world. To do so, we'll use PubNub to power our game's realtime infrastructure, so we'll just focus on developing a great experience for the players. 

PubNub provides a secure, scalable and reliable realtime infrastructure to power any application through its global [Data Stream Network](https://www.pubnub.com/products/global-data-stream-network/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22). With over [70+ SDKs](https://www.pubnub.com/docs/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22) supporting most of the programming languages, PubNub makes it easy to send and receive messages on any device in under 100 milliseconds.

We use the [PubNub React SDK](https://www.pubnub.com/docs/react-native-javascript/pubnub-javascript-sdk/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22) to connect two players to a game channel where they will play against each other. Each move the player makes will be published to the channel, as a JSON payload, so the other player's table updates with the current move. By updating the table in realtime for each move, players will feel as if they were playing next to each other!

You can check out the complete project in the [GitHub repository](https://github.com/ocastroa/react-native-tictactoe).

## Tutorial Overview

Our app will work on both Android and iOS (the beauty of React Native). This is how it will look once we're finished: 

<img class="size-full wp-image-41643 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/android-ios-game.png" alt="Android/iOS screenshot" width="415" height="415" />

We add a lobby where players can join or create a room. If a player creates a room, they become the room creator and waits for another player to join their room.

<img class="size-full wp-image-43378 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/create-room-channel.gif" alt="Create room channel" width="235" height="415" />

If another player wants to join that same room, they enter the room name in the alert prompt. That player becomes the opponent. </span> 

<img class="size-full wp-image-43379 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/join-room-channel.gif" alt="Join room channel" width="235" height="415" />


If the opponent tries to join a room that already has two people, they will not be able to join. But, if the room only has one player, the opponent will be able to join the room and the game will start for both players. Once the game starts, the tic tac toe board is displayed, along with the initialized score of the players.

<img class="size-full wp-image-43385 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/start-of-game.gif" alt="Start of a new game" width="235" height="415" />

If the game ends in a draw, then neither player gets a point. But</span> if there's a winner, the winner's score is updated. <span style="font-weight: 400;">T</span><span style="font-weight: 400;">he room creator gets an alert asking them if they want to play another round or exit the game.</span> <span style="font-weight: 400;">If the room creator continues the game, the board will reset for the new round. If the room creator decides to exit the game, both players will return to the lobby.

<img class="size-full wp-image-43386 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/exit-to-lobby.gif" alt="Exit game and go to the lobby" width="235" height="415" />

Before we can start implementing the game, there are a few requirements you need to take care of.

## Setting up the App

If you <span style="font-weight: 400;">don’t already have React Native set up on your machine,</span> then follow the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) instructions. Make sure you click on the second tab, **React Native CLI Quickstart**, and choose your **Target OS**. Follow the rest of the instructions in the documentation to install the dependencies. 

In your terminal, go to the directory you want to save your project in and type the following to create a new application:

```javascript
react-native init ReactNativeTicTacToe
```

Next, you need to install five dependencies and link them to the app you just created. To make this easy, add the following script file, **dependencies.sh**, to your app's root directory:

```shell
npm install --save pubnub pubnub-react
npm install --save react-native-prompt-android
npm install --save react-native-spinkit
npm install --save shortid
npm install --save lodash
react-native link
```

Make the script executable with the command:

```javascript
chmod +x dependencies.sh
```

Run the script with:
```javascript
./dependencies.sh
```

Now that your machine is set up, sign up for a [free PubNub account](https://dashboard.pubnub.com/signup/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22). You can get your unique pub/sub keys in [PubNub Admin Dashboard](https://admin.pubnub.com/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22). 

Now that we got the requirements out of the way, let's start coding!


## Initializing the Project

Create a file named **index.js** in your app's root directory and copy the following code:

```javascript
import {AppRegistry} from 'react-native';
import App from './App.js';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);
```

Next, create a new file named **App.js**. This is the main file for the game and it contains the components for the lobby and the table. To make **App.js** easy to follow along, we will break it into separate parts and go into detail for each part. Let’s first import the components and dependencies that will be used throughout the project.

```javascript
import React, { Component } from 'react';
import PubNubReact from 'pubnub-react';
import {
  Platform,
  StyleSheet,
  View,
  Alert,
  Text,
} from 'react-native';

import Game from './src/components/Game';
import Lobby from './src/components/Lobby';
import shortid  from 'shortid';
import Spinner from 'react-native-spinkit';
import prompt from 'react-native-prompt-android';
```

Next, add the base constructor where you will insert your Pub/Sub keys to connect to PubNub, initialize the local state objects and initialize the variables.

```javascript
export default class App extends Component {
  constructor(props) {
    super(props);
    this.pubnub = new PubNubReact({
      publishKey: "ENTER_YOUR_PUBLISH_KEY_HERE",
      subscribeKey: "ENTER_YOUR_SUBSCRIBE_KEY_HERE"
    });

    this.state = {
      username: '',
      piece: '', // Creator of the room is 'X' and the opponent is 'O'
      x_username: '', // Username for the room creator
      o_username: '', // Username for the opponent
      is_playing: false, // True when the opponent joins a room channel
      is_waiting: false, // True when the room creator waits for an opponent
      is_room_creator: false, 
      isDisabled: false // True when the 'Create' button is pressed
    };

    this.channel = null;
    this.pubnub.init(this); // Initialize PubNub
  }
 }
```

We will go into more detail about each state object and variable later on. Also, make sure that you initialize PubNub **after** initializing the state. 

Next, subscribe to the channel "_gameLobby"_ when the component mounts.

```javascript
componentDidMount() {
   this.pubnub.subscribe({
     channels: ['gameLobby']
   });
 }
```

The channel "_gameLobby"_  is the main channel that players subscribe and publish to when they are in the lobby. We will add more logic to this method later. For now, let's take a look at the render method.

```javascript
render() {
  return (
    <View style={styles.container}>
      <View style={styles.title_container}>
        <Text style={styles.title}>RN Tic-Tac-Toe</Text>
      </View>

      <Spinner 
        style={styles.spinner} 
        isVisible={this.state.is_waiting} 
        size={75} 
        type={"Circle"} 
        color={'rgb(208,33,41)'}
      />

      {
        !this.state.is_playing &&
        <Lobby 
          username={this.state.name} 
          onChangeUsername={this.onChangeUsername}
          onPressCreateRoom={this.onPressCreateRoom} 
          onPressJoinRoom={this.onPressJoinRoom}
          isDisabled={this.state.isDisabled}
        />
      }

      {
          this.state.is_playing &&
          <Game 
            pubnub={this.pubnub}
            channel={this.channel} 
            username={this.state.username} 
            piece={this.state.piece}
            x_username={this.state.x_username}
            o_username={this.state.o_username}
            is_room_creator={this.state.is_room_creator}
            endGame={this.endGame}
          />
        }
    </View>
  );
}
```

The **Lobby** component is shown first because _this.state.is_playing_ is initialized to false. Once an opponent has joined a room channel that is waiting for another player, then _this.state.is_playing_ is set to true and the **Lobby** component will be replaced by the **Game** component. The **Spinner** component is displayed to the room creator as long as the room creator is waiting for another player to join the game.

<img class="size-full wp-image-43391 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/spinner-compnent.gif" alt="Spinner component" width="235" height="415" />

Make sure to add the styles at the end:

```css
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: 'white',
  },
  spinner: {
    flex: 1,
    alignSelf: 'center',
    marginTop: 20,
    marginBottom: 50
  },
  title_container: {
    flex: 1,
    marginTop: 18
  },
  title: {
    alignSelf: 'center',
    fontWeight: 'bold',
    fontSize: 30,
    color: 'rgb(208,33,41)'
  },
});
```

Before we finish the rest of **App.js**, let's take a look at the _Lobby_ component.

## Implementing the Lobby Component

In the lobby, players can enter their username and create or join a room. We implement the logic, such as saving the username and calling the right method when a button is pressed, in **App.js**. All the methods in the lobby component are used to style the buttons, so we won't go into detail for those methods. The only three methods we will go into detail is **onChangeUsername()**, **onPressCreateRoom()** and **onPressJoinRoom()**, which are passed in as _props_ from **App.js**. 

In the app's root directory, create a new folder named **src** and within that folder, create another folder named **components**. Inside of **components**, create a new file named **Lobby.js**. Add the following to the new file:

```javascript
import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TextInput,
  TouchableHighlight
} from 'react-native';

export default class Lobby extends Component {
  constructor() {
    super();
    this.state = {
      pressCreateConfirm: false, // Set to true when the Create button is pressed
      pressJoinConfirm: false // Set to true when the Join button is pressed
     };
  }

  onHideUnderlayCreateButton = () => {
    this.setState({ pressCreateConfirm: false });
  }

  onShowUnderlayCreateButton = () => {
    this.setState({ pressCreateConfirm: true });
  }

  onHideUnderlayJoinButton = () => {
    this.setState({ pressJoinConfirm: false });
  }

  onShowUnderlayJoinButton = () => {
    this.setState({ pressJoinConfirm: true });
  }

  render() {
    return (        
      <View style={styles.content_container}>
        <View style={styles.input_container}>
          <TextInput
            style={styles.text_input}
            onChangeText={this.props.onChangeUsername}
            placeholder={" Enter your username"}
            maxLength={15}
            value={this.props.username}
          />
        </View>

        <View style={styles.container}>
          <View style={styles.buttonContainer}>
            <TouchableHighlight
              activeOpacity={1}
              underlayColor={'white'}
              style={
                this.state.pressCreateConfirm
                    ? styles.buttonPressed
                    : styles.buttonNotPressed
              }
                onHideUnderlay={this.onHideUnderlayCreateButton}
                onShowUnderlay={this.onShowUnderlayCreateButton}
                disabled={this.props.isDisabled}
                onPress={this.props.onPressCreateRoom}
              >
                <Text
                  style={
                  this.state.pressCreateConfirm
                      ? styles.cancelPressed
                      : styles.cancelNotPressed
                      }
                  >
                  Create
                </Text>
            </TouchableHighlight>
          </View>

          <View style={styles.buttonBorder}/>
            <View style={styles.buttonContainer}>
                <TouchableHighlight
                activeOpacity={1}
                underlayColor={'white'}
                style={
                  this.state.pressJoinConfirm
                      ? styles.buttonPressed
                      : styles.buttonNotPressed
                }
                  onHideUnderlay={this.onHideUnderlayJoinButton}
                  onShowUnderlay={this.onShowUnderlayJoinButton}
                  onPress={this.props.onPressJoinRoom}
                >
                  <Text
                    style={
                    this.state.pressJoinConfirm
                        ? styles.cancelPressed
                        : styles.cancelNotPressed
                        }
                    >
                    Join
                  </Text>
            </TouchableHighlight>
          </View>
        </View>
      </View>
    );
  }
}
```

This may look like a lot, but really, all we are doing here is setting up the username field and the two buttons. Like previously mentioned, the only logic we do is to style the buttons. In this case, the buttons background color, border color, and text color change when the button is pressed and unpressed. 

<img class="size-full wp-image-43392 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/button-styling.gif" alt="Button styling demo" width="235" height="415" />

Make sure to add the styles to the end of the file:

```css
const styles = StyleSheet.create({
  content_container: {
    flex: 1,
  },
  input_container: {
    marginBottom: 20,
  },
  container: {
    flexDirection: 'row',
    paddingLeft: 11,
    paddingRight: 11
  },
  buttonContainer: {
    flex: 1,
    textAlign: 'center',
  },
  buttonBorder: {
    borderLeftWidth: 4,
    borderLeftColor: 'white'
  },
  text_input: {
    backgroundColor: '#FFF',
    height: 40,
    borderColor: '#CCC', 
    borderWidth: 1
  },
  buttonPressed:{
    borderColor: 'rgb(208,33,41)',
    borderWidth: 1,
    padding: 10,
    borderRadius: 5
  },
  buttonNotPressed: {
    backgroundColor: 'rgb(208,33,41)',
    borderColor: 'rgb(208,33,41)',
    borderWidth: 1,
    padding: 10,
    borderRadius: 5
  },
  cancelPressed:{
    color: 'rgb(208,33,41)',
    fontSize: 16,
    textAlign: 'center',
    alignItems: 'center',
  },
  cancelNotPressed: {
    color: 'white',
    fontSize: 16,
    textAlign: 'center',
    alignItems: 'center',
  },
});
```

### Saving the Username

Whenever the player types in the username field, **onChangeUsername()** is called. This method, along with the rest of the methods in this post, is found in **App.js**.

```javascript
onChangeUsername = (username) => {
    this.setState({username});
}
```

We save the username in the _username_ state and limit the number of characters to **15 characters** so the username won't be too long. You can increase or decrease this number if you want to.

### Creating the Room Channel

Next, let's implement the method for **onPressCreateRoom()**, which is called when the user presses the _Create_ button.

```javascript
onPressCreateRoom = () => {
  if(this.state.username === ''){
    Alert.alert('Error','Please enter a username');
  }
```

We first check that the username field is not empty; if so, we alert the player to enter a username. A **random room ID** is generated and truncated to 5 characters. The **ID** is then appended to _'tictactoe--'_, which will be used as the game channel that players will subscribe and publish to. Below the _if_ statement, add the following code:

```javascript
else{
      let roomId = shortid.generate(); // Random channel name generated
      let shorterRoomId = roomId.substring(0,5); // Truncate to a shorter string value
      roomId = shorterRoomId;
      this.channel = 'tictactoe--' + roomId;
      this.pubnub.subscribe({
        channels: [this.channel],
        withPresence: true
      });
   ...
```

<span style="font-weight: 400;">In order to obtain the number of people in the channel, we use [Presence](https://www.pubnub.com/docs/react-native-javascript/presence/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22). We care about the channel occupancy because we only want two people to be connected to one channel at a time, as tic tac toe is a game for two people. That's why we set _withPresence_ to true.</span> 

Once the room creator subscribes to the new channel, we alert them to share the **room ID** with their friends. 

![Share the room id](https://www.pubnub.com/wp-content/uploads/2019/07/share-roomid.png)

Add the following code to the _else_ statement we used above:

```javascript
// alert the room creator to share the room ID with their friend
Alert.alert(
  'Share this room ID with your friend',
  roomId,
  [
    {text: 'Done'},
  ],
  { cancelable: false }
);
```

Since we want to change the state for certain objects, we use **setState()** to do so. Below the alert and still in the _else_ statement, add the following:

```javascript
this.setState({
      piece: 'X',
      is_room_creator: true,
      is_waiting: true,
      isDisabled: true
    });

    this.pubnub.publish({
      message: {
        is_room_creator: true,
        username: this.state.username
      },
      channel: 'gameLobby'
    });  
  } // Close the else statement
```

After changing the state of four objects, the boolean _is_room_creator_ and the room creator's _username_ will be published to "_gameLobby"_. The Spinner component will be displayed to the room creator while they wait for someone to join the game. 

Getting back to _componentDidMount()_, we need to set up a listener to listen for certain messages that arrive in "_gameLobby"_.

```javascript
componentDidMount(){
  ...
  this.pubnub.getMessage('gameLobby', (msg) => {
    // Set username for Player X
    if(msg.message.is_room_creator){
      this.setState({
        x_username: msg.message.username
      })
    }
   ...
  });
}
```

We want to get the room creator's username, so we do an _if_ statement to check if the message arrived is _msg.message.is_room_creator_. If so, we change the state, _x_username_, to the room creators username.

### Joining the Room Channel

We will now implement the last method, _**onPressJoinRoom()**. _

```javascript
onPressJoinRoom = () => {
  if(this.state.username === ''){
    Alert.alert('Error','Please enter a username');
  }
  else{
    // Check for platform
    if (Platform.OS === "android") {
      prompt(
        'Enter the room name',
        '',
        [
         {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
         {text: 'OK', onPress: (value) =>  
         (value === '') ? '' : this.joinRoom(value)},
        ],
        {
            type: 'default',
            cancelable: false,
            defaultValue: '',
            placeholder: ''
          }
      );      
    }
    else{
      Alert.prompt(
        'Enter the room name',
        '',
        [
         {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
         {text: 'OK', onPress: (value) =>  
         (value === '') ? '' : this.joinRoom(value)},
        ],
        'plain-text',
      );
    }  
  }
}
```

Again, we make sure that the username field is not empty. If it's not empty, then a prompt is shown to the opponent to enter the room name. 

<img class="size-full wp-image-43387 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/07/join-room-alert.png" alt="Join room alert prompt" width="364" height="206" />

Since we want to take into consideration both platforms (iOS and Android), we check which platform the app is running on and use the appropriate prompt. For Android, we use the prompt dependency **react-native-prompt-android**. We do so since **Alert.prompt()** is only supported for iOS devices. Essentially, both prompts accomplish the same goal: call **joinRoom(value)**, where **value** is the room name and cannot be an empty value, when **OK** is pressed.

```javascript
joinRoom = (room_id) => {
  this.channel = 'tictactoe--' + room_id;

  // Check that the lobby is not full
  this.pubnub.hereNow({
    channels: [this.channel], 
  }).then((response) => { 
    // If totalOccupancy is less than or equal to 1, then the player can't join a room since it has not been created
    if(response.totalOccupancy <= 1){
      Alert.alert('Lobby is empty','Please create a room or wait for someone to create a room to join.');
    }
    // Room is available to join
    else if(response.totalOccupancy === 2){
      this.pubnub.subscribe({
        channels: [this.channel],
        withPresence: true
      });

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

      this.pubnub.publish({
        message: {
          readyToPlay: true, // Game can now start
          not_room_creator: true,
          username: this.state.username
        },
        channel: 'gameLobby'
      });
    } 
    // Room already has two players
    else{
      Alert.alert('Room full','Please enter another room name');
    }
  }).catch((error) => { 
      console.log(error)
  });
}
```

We don't want more than two people to be in the same game channel, so we use the [hereNow()](https://www.pubnub.com/docs/react-native-javascript/api-reference-presence#here_now/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q3-DevTo-July-22) function to check the total occupancy for the channel. If the total occupancy is less than 1, the player is trying to join a room that has not been created, or there is a typo in the room name. 

If the total occupancy is 2, then there is a player in the channel, the room creator, and is waiting for another player to start the game. 

If the total occupancy is greater than 2, then the player is trying to join a room with a game in progress, so an alert tells the player that the room is full and to join another room. 

Once the opponent successfully subscribes to the game channel, a message is published with the opponent's _username_ and _readyToPlay_ set to true. Since the player is not the room creator, _not_room_creator_ is set to true.

### Finishing the Lobby Component

We will add the last logic for the listener in _componentDidMount()_.

```javascript
componentDidMount() {
  ...
  this.pubnub.getMessage('gameLobby', (msg) => {
    ...

    else if(msg.message.not_room_creator){
      this.pubnub.unsubscribe({
        channels : ['gameLobby']
      }); 
      // Start the game
      this.setState({
        o_username: msg.message.username,
        is_waiting: false,
        is_playing: true,
      });  
    }
  });
}
```

Both players will unsubscribe from "_gameLobby"_  since they are subscribed to the game room channel. In _setState()_, we do three things: set the opponents username to _o_username,_ set _is_waiting_ to false so the Spinner component will disappear from the room creator's view, and set _is_playing_ to true so the game between the two players can start. 

The last method we need to include is **componentWillUnmount()**.

```javascript
componentWillUnmount() {
  this.pubnub.unsubscribe({
    channels : ['gameLobby', this.channel]
  });
}
```

This method is called when the component is unmounted and destroyed. We unsubscribe from "_gameLobby,"_ if the user is still subscribed to that channel, and _this.channel_, the channel the player is subscribed to for the game. 

Now that we have finished the logic for the lobby, we will work on setting up the table, adding the game logic and implementing realtime interactivity between the players. [On to Part Two!](https://dev.to/pubnub/multiplayer-tic-tac-toe-game-in-react-native-for-ios-and-android-player-turns-and-deployment-343n)