Build a Multiplayer Game with Twilio Sync: Part 2
Time to read: 11 minutes
This blog post is the second part of a two-part tutorial where you will learn how to create a multiplayer Tic Tac Toe game with the Twilio Sync Javascript SDK.
In the first part, you learned how to create a custom Tic Tac Toe game with a user interface, controls, game logic, and an AI player.
In this part, you will learn how to use Twilio Sync to incorporate real-time multiplayer functionality into the game. Instead of playing against an AI player, after you finish this part you will be able to play against a real human player.
Twilio Sync Javascript SDK is a software development kit that allows you to add real-time data synchronization functionality to your web and mobile applications.
By the end of this part, you will have a game that looks like the following:
Tutorial Requirements
To follow this tutorial you will need the following:
- A free Twilio account
- A basic understanding of how to use Twilio and Javascript to build a applications
- Node.js v12+, NPM, and Git installed
Project setup
In this section, you will clone a repository containing the code generated in the first part of this tutorial, initialize a new node project and install the packages required to create a web server for the multiplayer game.
Open a terminal window and navigate to a suitable location for your project, use the following commands to clone the repository containing the boilerplate code and navigate to the boilerplate directory.
This code includes the tic tac toe game that was built in the first part of this tutorial. All the application files were stored inside the public directory.
Use the following command to create a new node project in this project’s root directory:
Now, use the following command to install the packages needed to build this application:
With the command above you installed the following packages:
dotenv
: is a package that allows you to load environment variables from a .env file without additional dependencies.express
: is a lightweight Node.js web application framework that offers a wide range of features for creating web and mobile apps. You will use the package to create the application server.twilio
: is a package that allows you to interact with the Twilio API.
Use the following command to install nodemon
as a dev dependencies
:
nodemon
is a Node.js development utility that automatically restarts your application when a file is changed.
Change the contents of the scripts
located inside the package.json to the following:
The code above ensures that you start the server using nodemon when you run the npm run dev
command.
Collecting and storing your Twilio credentials
Create a file named .env in this project's root directory. This file is where you will store your Twilio account credentials that will be needed to create access tokens for the Twilio Sync API. In total, you will need to collect and store four twilio credentials.
The first credential required is the Account SID, which is located in the Twilio Console. This credential should be stored in the .env file once obtained:
The second and third required credentials are an API Key SID and API Key Secret, which can be obtained by following this guide. After obtaining both credentials, copy and store them in the .env file:
The fourth credential required is a Sync Service SID. A Sync Service is the top-level scope of all other Sync resources (Maps, Lists, Documents).
Navigate to the Twilio Sync Service page, create a new Sync Service, and copy the Sync Service SID. This credential should also be stored in the .env file once obtained:
Creating the server
In this section, you will create the server application responsible for managing game rooms and generating Twilio Sync access tokens.
Game rooms are virtual spaces with unique identifiers within multiplayer games where players can interact and participate in specific game sessions. The game room that you will implement in this tutorial will be very simple, it will be responsible for storing the players’ signs (O or X) and limiting the number of players in a given room to 2. The players’ interaction will be handled by Twilio Sync.
Navigate to your project’s root directory, create a file named server.js, and add the following code to it:
The code above sets up a server using the Express framework. It imports the required modules, including Express and Twilio, and configures the server to listen on port 3000
. It also initializes an empty object to store information about game rooms. The server is configured to parse JSON data in requests and serve static files from the public directory.
Add the following code to the bottom of your server.js file:
Here a function named createOrJoinRoom()
was initialized, this function allows players to create or join a room in the game server. It checks if the specified room already exists and if it has space for another player.
If the room exists and has space (has less than two players), a player sign (X
or O
) is assigned to the new player and added to the room. The assigned player sign is returned. If the room is full, an empty string is returned.
If the room doesn't exist, a new room is created with the specified name, the first player is assigned the player sign O
, and O
is returned as the player sign.
Add the following code below the createOrJoinRoom()
function:
The leaveRoom()
function initialized above handles a player leaving a room in the Tic Tac Toe game server. It checks if the specified room exists and if the player's sign is present in the room's array of players.
If both conditions are met, the player is removed from the room by splicing the array. The function returns true if the player was successfully removed and false otherwise.
If the specified room doesn’t exist it returns false.
Add the following code below the leaveRoom()
function:
The code above initializes a route handler for the /joinRoom
endpoint in the server application.
When a GET request is made to this endpoint, it extracts the roomName
parameter from the request query. It then calls the createOrJoinRoom()
function to determine the player's sign and join the room if it exists and if it isn’t full.
If the room is full, it sends a response with an empty player sign.
Add the following code to the else
statement inside the /joinRoom
route:
In the else statement above—which runs when the room isn’t full—the code generates a Twilio access token for the player, including their identity and necessary permissions. Lastly, it sends a response containing the player's sign, token, and the number of participants in the room to the client.
Add the following code to the below the /joinRoom
route:
Here the code initializes a route handler for the /leaveRoom
endpoint in the server.
When a GET
request is made to this route, the server retrieves the roomName
and sign
parameters from the request query. The leaveRoom()
function is then called with these parameters to remove the specified sign from the room. The response sent back to the client includes the result of the operation.
Finally, the server starts listening on the specified port and logs a message indicating it's running and listening for requests at the specified port.
Run the following command below to start serving the application:
Open your preferred browser, navigate to http://localhost:3000/ and you should see the game UI:
Joining and leaving rooms
In this section, you will add the Twilio Sync Javascript SDK to the game client and incorporate the functionality that will allow multiple players to join and leave rooms.
Navigate to the public directory and add the following code to the head
tag of the index.html file:
With the line of code above you added the Twilio Sync Javascript SDK to the application.
Add the following code below the modalGameOver
in the body
section of the index.html file.
Here a modal and a toast, modalJoinRoom
and toast
respectively, were added to the application.
The join room modal will be displayed when the page loads and it will prompt the player to write the room name that he would like to join. The toast will be displayed when another player joins or leaves the room that the current player is in.
Open the index.js file located in the public directory and replace the value stored in the player
variable (located around line 6) with an empty string:
Here you replaced the previous value stored in the player
value because the player sign will be dynamically set.
Add the following code below the line where you declared the isGameOver
variable (located around line 10):
The code above sets up the necessary elements for joining a room in the application. It initializes variables that will be used to store the Twilio client, room name, and room participants. It also displays the join room modal that the players will use to join a room.
Add the following code to the bottom of your index.js file:
Here the code defines a function named joinRoom()
, this function sends a request to the server to join a room and asynchronously waits for the response. It retrieves the data in the response and returns it for further processing.
Add the following code below the joinRoom()
function:
The code above defines a function named leaveRoom()
, this function sends a request to the server to leave the current room and logs the response received from the server.
Add the following code below the leaveRoom()
function:
Here the function named initialize()
that you added earlier to your join room modal was defined, this function disables the Start button, retrieves the room name, and joins a room by calling the joinRoom()
function. Next, It assigns the player sign and the current player based on the received data.
If the player sign is not an empty string (meaning that the player successfully joined the room), it updates the room participants count, and checks if there are two participants in the room. If that is the case it enables the Start button, updates the toast element text to indicate that an opponent player has already joined the room, and shows the toast. Next, it calls the indicatePlayerSign()
function to indicate the player sign on the game toolbar.
However, if the player sign is an empty string, it alerts the player that the room is full.
Add the following code below the indicatePlayerSign()
function call:
The code added retrieves the access token stored on the data returned by the server and uses it to create a Twilio Sync client for real-time synchronization. The client is used to access a document associated with a specific room. A function named updateDocument()
function is then called to send events to update the document and notify the other player that a new player has joined the room.
The document object has an event listener for updates, which triggers a callback function when changes occur. The function checks if the update is made by another player (not local) and then calls a function named handleDocumentUpdate()
to handle the updated data.
If any errors occur during the initialization process, such as client creation or document retrieval failures, an error message is logged to the console.
Add the following code below the initialize()
function;
The code above defines the function named updateDocument()
that takes two parameters: event
and value
. This function is responsible for sending events to update the content of a document associated with a specific room.
Add the following code below the updateDocument()
function:
Here the code defines the handleDocumentUpdate()
function, which receives a data
parameter and is responsible for processing updates to a document. This function will perform various actions based on the type of event that the data
parameter has, such as notifying that another player has joined or left the room, updating the game board, and indicating players' turn.
Currently, it only handles the case when the event is playerJoined
. If the event is playerJoined
, it increments the roomParticipants
variable if it is less than 2
, enables the Start button, updates the text of the toast element to indicate the player who joined, and displays a Bootstrap toast message.
Add the following case below the playerJoined
case:
The code added handles the playerLeft
event. The code first checks if the variable roomParticipants
is greater than 0. If it is, it decrements the value of roomParticipants
by 1.
Next, it checks if the player’s sign matches the value received. if it doesn’t match the code updates the toast element text to indicate that the opponent player left the room and then displays the toast.
Add the following code below the handleDocumentUpdate()
function:
The code above adds event listeners to the window object for the pagehide
and beforeunload
events. These events are triggered when the player navigates away from the current page or closes the browser window.
When either one of these events is triggered, the code first, calls the updateDocument()
function to update the document associated with the room, indicating that the player has left the room then it calls the leaveRoom()
function to send a request to the server to allow the player to leave the room.
Go back to your browser, open another browser window, and place it side by side with the first one. Refresh the page on the first window and navigate to http://localhost:3000/ on the second window. Once both windows have loaded the pages, enter the same room name in the modals to join a room. In both windows, the game toolbar will be updated to indicate each player's sign and a toast message stating that the opponent player has joined the room will appear. After the toast disappears, select one of the windows, refresh the page, and a toast message stating that the opponent player has left the room will appear on the other window.
Sharing the players' inputs
In this section, you will write the code that will be responsible for sharing the players’ input events, such as clicking the Start button, hovering, and clicking a cell on the board.
Replace the code inside the Start button click event listener with the following (located around line 62):
Here before calling the startGame()
function, the code now includes a call to the switchPlayer()
function located in the game.js file. This function is responsible for switching the current player.
After calling startGame()
, the code calls the updateDocument()
function to send an event indicating the start of the game.
Now open the game.js file and add the following highlighted line to the switchPlayer()
function (located around line 24):
The line added calls the updateDocument()
function to send an event to switch the current player.
Navigate to the checkGameStatus()
function (located around line 68) and comment out the code below the switchPlayer()
function call:
Here you commented out the code responsible for managing the AI player’s moves because in this part of the tutorial you are adding the multiplayer functionality.
Navigate to the reset()
function (located around line 117) and comment out the switchPlayer()
function call because this function is being called from the Start button click event listener:
Now, go back to your index.js file and add the following code to the event listeners inside the addListenersToCells()
function (located around line 28):
In the code above the updateDocument()
function is called within the mouseover
, mouseout
, and click
event listeners. It is passed two arguments: the first argument is the event name (mouseover
, mouseout
, or click
), and the second argument is the index i
of the cell on which the event occurred. This way, the updateDocument()
function is invoked with the appropriate event and the corresponding cell index to update the document accordingly.
Add the following cases to switch statement inside the handleDocumentUpdate()
function (located around line 149) :
The code above adds the startGame
and the switchPlayer
event handlers.
When the event is startGame
, the function calls the startGame()
function, which initializes the game.
When the event is switchPlayer
, the function updates the value of currentPlayer
to the value provided in data.value
. This change indicates that the turn has shifted to the opponent player. The function then calls the indicatePlayerTurn()
function, which updates the user interface to indicate the current player's turn.
Add the following cases below the switchPlayer
case:
When the event is mouseover
, the function adds the CSS class shrink-grow
to the cell element selected using the index stored in the data.value
property. This class creates a shrink-grow visual effect when the mouse is hovering over the cell
When the event is mouseout
, the function removes the CSS class shrink-grow
from the cell element, thus removing the visual effect when the mouse moves out of the cell.
When the event is click
, the function first updates the currentPlayer
value to match the opponent player's sign since this code will only run if the opponent player makes a move. Next, it disables the cell element and removes the CSS class shrink-grow
from the cell. Finally, it calls the markCell()
, passing the cell element as an argument to mark the cell with the opponent player’s sign and check the game status.
Go back to the browser windows, refresh both of them, enter the same room name and play the game.
Conclusion
In this second part of a two-part tutorial, you learned how to use the Twilio Sync Javascript SDK to incorporate real-time multiplayer functionality in the Tic Tac Toe game. First, you created a server capable of managing game rooms and generating Twilio Sync access tokens. Lastly, you added the Twilio Javascript SDK to the game and implemented the multiplayer functionality.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.