Redis Announces CEO Transition

How to Create a Real-Time Online Multi-Player Strategy Game Using Redis

Multiplayer gaming remains colossal in the gaming industry. And why wouldn’t it be? To settle old scores, solve disputes, or even satisfy that competitive itch , battling it out online against other users is just as cathartic as it is entertaining. 

This is why this Launchpad app has created its own real time strategy game, Pizza Tribes, that involves…wait for it… mice! The gameplay involves training a population of mice to bake and sell pizzas for coins, with the overarching objective being to generate more coins than any other player. 

For all its creativity, this application wouldn’t be able to provide users with real time gameplay without Redis’ ability to transmit data between components efficiently. Any delays would have made real time gameplay impossible. 

Let’s take a look at how this application was created. But before we go any further, we’d like to point out that we have an excellent range of applications that are having an impact on everyday life for you to check out on the Redis Launchpad

  1. What will you build?
  2. What will you need?
  3. Architecture 
  4. Getting started
  5. The game state update

1. What will you build?

You’ll build a multiplayer browser-based real time strategy game using Redis. Below we’ll go through each step in chronological order and outline all of the components that you’ll need to create this application. 

Ready to get started? Ok, let’s dive straight in. 

2. What will you need?

  • Typescript: used as a superset of the JavaScript language
  • Golang: the preferred programming language used to build efficient software
  • RedisTimeSeries: provides time series data
  • RedisJSON: stores, updates, and fetches JSON values from Redis keys

3. Architecture 

The application has an unconventional approach when it comes to client-server communication. This is because it relies heavily on web sockets to carry out responsibilities that would normally be fulfilled by an HTTP request/response. 

Below is an example of a typical web-socket flow:

  1. The Web App sends commands over the Web socket
  2. The Web API enqueues the command on a Redis queue wsin (RPUSH)
  3. A Worker
    1. Pulls the command from the Redis queue wsin (BLPop)
    2. Executes the command
    3. May push a response to another Redis queue wsout (RPUSH)
  4. The Web API
    1. Pulls a response from the Redis queue wsout (BLPOP)
    2. Sends the response back to the corresponding Web socket

Web sockets

Since this application is reliant on Web-Sockets, we’re going to spend a bit more time going over the role that they play. Firstly, web socket communication relies heavily on Redis to send and retrieve messages. 

The API does not do any game logic but simply validates client messages before pushing them to Redis. Meanwhile, the workers can be horizontally scaled whilst relying on Redis to push messages efficiently through the system. 

Since Web Socket bidirectional lightweight communication protocol over a single TCP connection, it is not easy to scale the Web API (holding the sockets). 

This solution attempts to minimize load on the Web API so that it can focus on shoveling data to the clients.
Note: messages are not sent between the API and workers using Redis Pub/Sub. Instead, Redis lists (RPush and BLPop) are used. One of the advantages of this is that the workers or the API can be restarted without losing messages, whereas with Redis Pub/Sub everything will be forgotten.

Updater (Delayed Tasks)

The worker will need to delay some tasks (e.g., finish construction of the building after 5 minutes). This is accomplished by carrying out updates to the Sorted set user_updates. The updater then pulls the top record of the sorted set which is determined by time. If the time has passed, it will remove the record from the set and will then update the game state of that user.

Below is a simplified typical flow:

  1. The Web App sends the command to start the construction of a building
  2. A worker processes the command by carrying out a number of commands

i. Validates the command

ii. Updates the user game state:

iii. Discovers the next time the user game state needs to be updated (e.g. when the construction is completed)

iv. Sets the next updated time of the user game state:

3. An updater updates the user game state at the next update time by carrying out a number of commands:

i) Runs the following command to fetch the next user that needs to be updated (and at what time)

ii) If the score (timestamp) has been passed

a) Remove the next update time:

b) Perform game state update

c) Find next time the user game state needs to be updated again

d) Set next update time:

Client-server protocol

Protocol Buffers are used to define the messages that are sent between the client/server and the server/client. 

Client Messages

Below is the full definition.

Server messages

Below is the full definition.

File Tree

4. Getting started

Prerequisite:

  • Docker 
  • Docker Compose

Step 1: Running the application locally

There are two ways you can run the application locally. You can either run everything (Redis, services, web app) in a Docker container Or, you can pick and choose for faster development.

Clone the repository
git clone https://github.com/redis-developer/pizza-tribes

Start the services

This is arguably the easiest way to run the project locally. To get started, you need to execute the docker-compose command as shown below:

This command will achieve the following:

  • Build all of the services: the web app and the caddy front
  • Run everything: this includes Redis and RedisInsight 

When this command is carried out, the following is achieved: 

  • redis server running at port 6379
  • Redisinsight  running at port 8001
  • The webapp running  at port 8080

Picking and choosing for faster development

If you want to make changes then you’ll benefit from Hot Module Replacement (HMR) in the web app. This will allow you to build the Go apps more efficiently. To achieve this, run Redis using docker-compose, and then run the services and web app on your host OS:

Once carried out, this command should give you:

  • Redis server running via Docker at 6379
  • The Redis GUI ‘Reedisinsight”  via docker at 8001
  • The webapp running on the host system at 3000
  • api via host OS at 8080

Note: The web app will proxy calls to /api to http://localhost:8080 (see webapp/vite.config.ts).

Step 2: Registering new players

The users are stored as a hash set in key user:{user_id} containing fields. These include:

  • id
  • username
  • hashed_password

The user_id can be looked up using the username via username:{username}. 

Registration is achieved through the following commands:

  • Generate unique id (rs/xid)
  • redis cmd:
  • redis cmd:

Authentication is done like so:

  • redis cmd (get user id):
  • redis cmd:
  • Verify hashed_password

Note: If the hashes match, create JWT

Step 3: Storing the user Game State

Using RedisJSON, the user game state is stored as a JSON value in the key user.

It does this with the following structure: 

The game state is accessed in different ways depending on the use case. But for a complete retrieval, the following is used:

  • redis cmd:

In other cases, a path is used to retrieve only a subset of the data:

  • redis cmd (retrieve building info at lot 5):
  • redis cmd (retrieve population data):

5. The Game State Update

The game state update is what makes the game tick and it’s one of the most important processes in the game. Its purpose is to:

  • Extrapolate resources (i.e. increase resources with produced amounts since the last update)
  • Complete buildings
  • Complete training sessions for the characters
  • Complete travels (i.e. thieves moving between towns)

In addition, the game state update will also: 

  • Insert resource data points for RedisTimeSeries
  • Update the leaderboard (because the resources have changed)

Discovering which user needs the update

The updater runs in a loop that queries a sorted set that’s called user_updates. It retrieves the top record in the sorted set by running the following command:

By utilizing WITHSCORES we also retrieve the timestamp of when that user needs a game state. As such, the updater can check if timestamp < now. If so, then the user can carry out the following commands:

2. Proceed to update the game state

Note: there is some level of risk involved because if the game state update fails, the user will no longer have a record in the user_updates sorted set. When this happens, no game state update will be scheduled. 

To avoid this, the game will ensure that the user is scheduled for game state updates when logging in. 

Updating the Game State

The update is executed with a check-and-set approach (WATCH, MULTI, EXEC). This is achieved through the following steps:

  • Run game state process to figure out how to transform the game state
  • MULTI
  • Run all modifying commands to convert to the game state calculated in the previous step
  • EXEC

For more details of a simple game state update, see the trace below:

Note: Extrapolating resources, completing buildings, trainings and complete travels are all implemented using the flow described above. 

Scheduling the next game update

When the game state has been updated, you’ll have to schedule the next one. Here’s how to do it:

  • Determine when the game state needs to be updated
    • Is a building being completed?
    • Is a training session being completed?
    • Is travel being completed?

Inserting data points with RedisTimeSeries

The RedisTimeseries module is used to track the changes in user resources. The resources are tracked using the following keys:

When every game state update is carried out, a new data point is inserted into each key. Below is an example:

When the user wants to look at their resource history, the following command is used to retrieve the aggregated data points from the last 24 hours. 

Updating the Leaderboard

The game state update will change the number of coins a user has which is why we need to update the leaderboard. The leaderboard is a sorted set with the key leaderboard. It’s updated by running the following command:

When any user wants to have access to the leaderboard, the data is retrieved with the following command:

Conclusion: Keeping Everything in Real Time Using Redis

From a performance perspective, achieving real-time gameplay is one of the most important objectives behind creating a successful online multiplayer game. Failing to achieve this will drastically hamper the user’s experience, irrespective of how advanced other qualities of the game are. 

Despite having a complicated architecture, Redis removed this obstacle through its ability to zip data between different components with ease. Having no lags, no delays, and no data setbacks whatsoever allowed this Launchpad App to create a complex yet engaging online multiplayer strategy game where users from around the world can battle it out against each other for the top spot. 

If you want to get more of an insight into how this application was made then you may want to check out this YouTube video

We should also let you know that we have an exciting range of game-changing applications (excuse the pun) on the Redis Launchpad. Here you’ll discover a collection of apps that are having an impact on everyday life by everyday programmers. 

So make sure to check it out!

Who built this application?

Matteus Hemström

From Matteus is a highly innovative software engineer who currently plies his trade at Nuway.

If you want to keep up to date with all of the projects he’s been involved with, then head over to his GitHub page here