Pizza Tribes
Warning
The updates to this public repository have been discontinued. The project is now undergoing closed-source development. We may release the most recent code at a later date.
Play at: https://www.pizzatribes.com
Welcome to Pizza Tribes, a captivating multiplayer clicker real-time strategy game!
Manage your town, construct buildings, and assign roles to your mouse inhabitants.
Build your pizza empire, earn coins, and race to be the first to reach 10 million
coins to claim victory! Defend your profits by training security guards, and outwit
rivals by deploying thieves against them.
Dive into the thrilling world of Pizza Tribes now!
This project was initially created for RedisConf 2021 Hackathon but has since then been developed much further.
Table of Contents
Repository Status
Warning
The updates to this public repository have been discontinued. The project is now undergoing closed-source development. We may release the most recent code at a later date.
Tech Stack
See go.mod and package.json for a list used libraries.
Game Elements
Buildings
- Kitchen
- Shop
- House
- School
Education / Roles
- Chef
- Salesmouse
- Guard
- Thief
Resources
Actions
- Click (tap)
- Construct building
- Upgrade building
- Train
- Steal
- Expand
Other Features
Architecture and Use of Redis
This document provide some overview of the project and its use of Redis. For additional documentation, see docs/README.md.
Overview
+---------+
| Web App |
+---------+
|
| (Web Socket / HTTPS)
|
+---------+
| Web Api |
+---------+
|
|
|
+---------+
+-----| Redis |<-----------+
| +---------+ |
| | +---------+
| | | Updater |
| | +---------+
+--------+ +--------+
| Worker | | Worker |
+--------+ +--------+
As the diagram above describe, there are three types of backend services:
- Web Api — serves HTTP requests and holds Web sockets
- Worker — processes client messages pulled from Redis
- Updater — processes delayed game state updates
In addition, the diagram show:
- The Web App — the game client
- Redis — used as swiss army knife for data communication and persistence
Client-server Communication
The project takes an (probably unconventional) approach of
client-server communication, relying heavily on web sockets
even for communication that traditionally is fulfilled by HTTP
request/response. However, there are a few traditional REST-like API
endpoint as well.
A typical Web socket flow goes as follows:
- The Web App sends commands over the Web socket
- The Web API enqueue the command on a Redis queue wsin (
RPUSH
)
- A Worker
- Pulls the command from the Redis queue wsin(
BLPop
)
- Executes the command
- May push a response to another Redis queue wsout (
RPUSH
)
- The Web API
- Pulls a response from the Redis queue wsout (
BLPOP
)
- Sends the response back to the corresponding Web socket
Web Sockets
Web socket communication heavily relies on Redis for pushing and pulling
messages. The API does not do any game logic but simply validates client
messages before pushing them to Redis. This allows for running multiple
game workers to do the lifting (not that heavy really :)). The
workers can be horizontally scaled while relying on Redis performance to
push messages through the system. Since Web Sockets are stateful
bidirectional communication 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 that the messages are not sent between the API and workers using
pub/sub but instead using Redis lists (RPush and BLPop). I am not sure if
this is used as a good idea or not - but I think one upside is that the workers or
API can be restarted without losing messages (pub/sub is fire-and-forget).
Updater (Delayed Tasks)
The worker needs to delay some tasks (e.g., finish construction of
building after 5 minutes). This is accomplished by updating to the sorted
set user_updates. The updater pulls the top record of the sorted set
(sorted by time), and if the time has passed it removes the record from
the set, and then updates the game state of that user (to finish the
construction).
A (simplified) typical flow is as follows:
- The Web App send command to start construction of a building
- A worker processes the command
- Validates the command
- Updates the user game state:
JSON.ARRAPPEND user:$user_id:gamestate .constructionQueue $constructionItem
- Finds the next time the user game state needs to be updated (e.g. when the construction is completed)
- Set next update time:
ZADD user_updates $timestamp $user_id
- A updater updates the user game state at the next update time
- Runs
ZRANGE user_updates 0 0 WITHSCORES
to fetch the next user that needs update (and at what time)
- If the score (timestamp) has been passed
- Remove the next update time:
ZREM user_updates $user_id
- Perform game state update
- Find next time the user game state needs to be updated again
- Set next update time:
ZADD user_updates $timestamp $user_id
More in-depth documentation
For more in-depth documention, see docs/README.md.
Client-Server Protocol
Protocol Buffers are used to define the messages sent between client/server and server/client. They are also as database models, i.e. how objects/documents are stored in Redis.
Client Messages
The following is not the exact definitions, they are here to describe on a higher-level what messages that exist and roughly the data they contain. See /protos/client_message.proto for full definition.
Id |
Type |
Payload |
... |
"TAP" |
amount? |
... |
"CONSTRUCT_BUILDING" |
lotId, building |
... |
"UPGRADE_BUILDING" |
lotId |
... |
"TRAIN" |
education, amount |
... |
"EXPAND" |
|
... |
"STEAL" |
amount, x, y |
Server Messages
The following is not the exact definitions, they are here to describe on a higher-level what messages that exist and roughly the data they contain. See /protos/server_message.proto for full definition.
Type |
Payload |
"STATE_CHANGE" |
...game_state |
"RESPONSE" |
request_id, result |
File Tree
.
├── cmd (golang source files for each command/process)
│ ├── api
│ ├── migrator
│ ├── updater
│ └── worker
├── docs
├── internal (shared golang source files)
├── protos (protobuf files used by both backend and frontend)
└── webapp (frontend application)
├── fonts
├── images
├── plugins
├── src
└── tools
Running it Locally
There are essentially two ways to run the project locally. You either run everything (redis, services, web app) over docker. Or you pick and choose what you want for faster development.
The easy way (all services over docker)
The easiest way to get started is to run the docker-compose.yml:
cp .env.default .env
docker-compose up --build -d
That will:
- build all services, the web app, and caddy front
- run everything, including redis and redisinsight
The following is exposed:
- redis at 6379
- redisinsight at 8001
- webapp at 8080
For development (pick and choose)
If you want to make changes you might want to benefit from HMR (Hot Module Replacement) in the web app and faster build times for the Go apps. If that's the case, you might want to run redis using docker-compose, and then run the services and web app on your host OS.
Install the following dependencies:
And then run:
printf "HOST=:8080\nORIGIN=http://localhost:3000\nJWT_SIGNING_KEY=secret" > .env
docker-compose up -d redis redisinsight
make -j start # Build and run go services (see Makefile for details)
cd webapp # in another terminal
npm install
npm run generate
npm run dev
Given no errors, that should give you:
- redis via docker at 6379
- redisinsight via docker at 8001
- webapp via host OS at 3000
- api via host OS at 8080
Note that the web app will proxy calls to /api
to http://localhost:8080
(see webapp/vite.config.ts
).
Troubleshooting
Start by checking your logs: docker-compose logs -f
Check Origin
If you find that websocket: request origin not allowed by Upgrader.CheckOrigin"
you might need to change your origin check setting. Try to add a .env
file with the origin you are using like so:
ORIGIN=http://localhost:3000
Can't login after flushing db
You are probably still authenticated (and have a valid JWT) to a user that no longer exists. Either clean your cookies, or remove the token
cookie, or navigate to http://localhost:8080/api/auth/logout
.