Turns and Deployment: Multiplayer React Tic Tac Toe Game
Note: This is part two of building a multiplayer tic-tac-toe game in React Native using PubNub. If you have not completed part one of the tutorial, which focuses on environment setup, lobby creation, and inviting other players to join, please go back to part one of the tutorial. Remember, you can view the completed application on GitHub.
Welcome to Part Two of the tutorial series on building a mobile multiplayer tic-tac-toe game with React Native and PubNub. In this section, you will implement the core functionality of the game and play the game.
If you haven’t already, please check out and go through Part One before working on this section, as the project was initialized and the lobby was set up in that section.
Implementing the Game Component
From the project root directory, go to src/components and create a new file named Game.js. All the game logic will be inside this file. Add the following code to the file.
There are several things occurring in the base constructor.
An array of possible combinations to win the game is set up. If a player matches with any of the combinations, that player is the winner of the game.
Another array named
ids
is initialized and labels every square of the table with a unique id to check if a square has been occupied or not.The variable
rows
contains three empty arrays with each size of three.For the state objects, the array
moves
is initialized with empty string values and the score for both players to 0. The arraymoves
will be used to check if there is a winner, discussed later on.Finally, three variables are added,
turn
,game_over
, andcount
that will be useful throughout the game.
Setting up the UI
The UI for the component includes the table and its styles, current score, and username for each player.
For the username, this.props
is used since the value is obtained from App.js. As a reminder from part one of the tutorial, here are the values that you’ll be using from App.js.
To build the tic-tac-toe game board, generateRows
is called, which calls generateBlocks
. These methods are responsible for creating the table.
TouchableHightlight
is called so players can touch any square on the table. In order for their corresponding piece to be placed on that square, set the block text to be the value of this.state.moves[id]
, which will contain the correct piece for the player that made the move. This will be discussed in detail later on in the tutorial.
Adding the Logic
Create the function onMakeMove
with row_index
, the row that the piece was placed on, and col_index
, the column the piece was placed on, as the method arguments.
The integer ID of the square pressed is obtained by getting the value of this.ids[row_index][col_index]
. The if
statement checks if the square the player touched is empty and if it is the current player's turn to play. The touch is ignored if these two conditions are not met. If the conditions are met, the piece is added to the array moves
with id
as the index.
For example, if the room creator makes a move on row 0 column 2 on the table, then this.ids[0][2]
returns the integer 2.
The piece X
is added to the array moves
on the second index: [“”, “”, “X”, “”, “”, “”, “”, “”, “”]
. Later in updateScores
, you will see how moves
is used to check for a winner.
Back in onMakeMove
, after the state for moves
is changed, turns
is updated so the next player can make their move. Data is published to the game channel, such as the piece that moved and its position. The other player’s table updates with the current data. Finally, make a call to updateScores
to check if there is a winner or if the game ended in a draw.
Implement the channel listener in componentDidMount
to listen to incoming messages from the channel.
Both players will receive this message since both are subscribed to the same channel. When the message is received, several of the same steps performed in onMakeMove
occur: update the table and check if there is a winner. However, the player that made the move should not repeat the steps again. Perform a if
statement to ensure that only the opposite player performs the steps. Do this with the conditional if turn
(which is either X or O) matches the player’s piece.
Next, implement the method updateScores
.
To determine if there is a winner, you'll need to iterate possible_combinations
to check if any of the combinations are present in the table. This is where the array moves
comes in handy. You'll use [a,b,c]
to get each array of possible_combinations
. Then check if that matches any pattern in moves
.
For example, the room creator makes a winning move on row 2 column 2, with an id
of 8, on the table. The table will look as follows:
The winning moves are in positions 2, 5, and 8, according to the IDs. The array moves
is now: [“O”, “”, “X”, “”, “O”, “X”, “”, “”, “X”]
. In updateScores
, iterate through every possible winning combination.
For this example, the winning combination is [2,5,8]
. So when [a,b,c]
has the values of [2,5,8]
, the if
statement in the for
loop will be true
since [2,5,8]
all have the same value of X
. A call is made to determineWinner
to update the score of the winning player, which in this case, is the room creator. Before implementing that method, finish the rest of updateScores
.
If no winner is found and the game ends in a draw, then neither player gets a point. To check if there is a draw, add the following code below the above for
loop.
Every time a square is pressed on the table updateScores
is called. If no winner is found, then count
increments by one. If the count
is equal to 9, the game ends in a draw. This occurs when a player makes a final move on the last square of the table that is not a winning move, so count
is incremented from 8 to 9. The method newGame
is then called.
Next, implement determineWinner
.
You check who has won the game and increment the winner’s current score by one point. Once the score has been updated, game_over
is set to true
and calls the method newGame
.
Game Over
Two different alerts are shown to the room creator and the opponent. The alert for the room creator has a message asking them if they want to play another round or exit the game.
For the opponent, an alert is shown with a message telling them to wait for a new round, which will be decided by the room creator.
If the room creator decides to end the game, then the message gameOver
is published to the channel. Otherwise, the message restart
is published to the channel. These messages are handled inside of getMessage
in componentDidMount
.
If the room creator wants to play another round, then the table is reset. This occurs by setting the array moves
to its original state: turn
is set to X
and game_over
to false
.
However, if the room creator decides to end the game, then both players unsubscribe from the current channel and a call to the method endGame
is made. This method will end the game and take the players back to the lobby since is_playing
is reset to false
. The method endGame
is a prop from App.js, so you need to go back to App.js to implement this functionality.
The state values are reset back to the original state and channel
to null
since there will be a new room_id
when the create button is pressed again. Finally, subscribe again to the gameLobby
channel so the players can continue playing the game if they choose to do so.
The React Native multiplayer tic-tac-toe game is now ready to test and play!
Testing the App
Before running the app, you'll need to enable the Presence feature to detect the number of people in the channel. To turn it on, go to the PubNub Admin Dashboard and click on your application. Click on your Keyset and toggle the Presence function switch to enable it. A dialog will appear, asking you to confirm by typing in "ENABLE" in all caps. Do so and keep the default values the same.
To run the game, make sure you are in the project folder and enter the following command in the terminal: react-native run-ios
.
This command will open the simulator and run the game. You can also run the game on the Android emulator by replacing run-ios
with run-android
(Note: Make sure you open an emulator first before running the command). Once the simulator opens, the lobby UI will be displayed.
You can test the game by running the React app version of the tic-tac-toe game used to test the simulator applications. The test React app is already connected to the React Native app used in this tutorial and the necessary logic is taken care of. All you need to do is insert the same Pub/Sub keys from the tutorial's React Native app.
Playing the Game
You will use the simulator to create a channel and the React app to join that channel (Note: The React app is currently set up to only join channels and not create them). You can clone the test React app from this repo. Once you open the project, go to the file Game.js and in the constructor, add the Pub/Sub keys you used for the React Native app. That’s all you have to edit for the test React project.
Run the npm install
command in the terminal to install the dependencies. To run the app, enter the npm start
command in the terminal.
The app will open at http://localhost:3000 with an empty table and two input fields.
Navigate to the simulator and in the lobby, type Player X for the username and press the create button to create a new room ID. Go back to the browser and for the username field, type Player O. In the room name field, type the room ID that was created in the simulator.
Once both fields are filled in, press the Submit button and the game will start for both players. Since the simulator is the room creator, press any square on the table to place an X. You should see the X in both the simulator and in the browser. Try pressing another square in the simulator’s table and you will notice that nothing happens as it’s the opponent’s turn to make a move. In the browser, press any square to place an O.
Keep playing until someone wins or the game ends in a draw. If there is a winner, the winner will be announced in the browser and the winning square's background color will turn green. In the simulator, the score for the winning player will update and an alert will ask the room creator if they want to play another round or exit the game.
If the room creator decides to play another round, both the simulator and the test React app tables will reset for the new round. If the room creator decides to exit the game, the room creator will be taken to the lobby while the test React app browser resets the table. You can create a new channel in the simulator and in the React test app browser you can join that new channel.
What’s Next
You've successfully created a React Native multiplayer tic-tac-toe game using PubNub for real-time updates and user online detection. In Part one of the tutorial, you set up your application environment, created the lobby for players to join, and displayed dialogs for players to join other lobbies. In Part two of the tutorial, you completed the logic of players actually playing the game, winning conditions, and how players can choose to play once more or leave the lobby.
You can check out the completed version of the application on GitHub, as well as the React version of the application used to test the game created in this tutorial. If you would like to learn more about how to power your React applications with PubNub, be sure to check out PubNub's other React developer resources:
If you have any other questions or concerns, please feel free to reach out to devrel@pubnub.com.