Skip to content

Pizza Tribes is a multiplayer persistent browser-based clicker real-time strategy game. The gameplay is a combination of a clicker game and a real-time strategy game.

License

Notifications You must be signed in to change notification settings

fnatte/pizza-tribes

Repository files navigation

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!

screenshot of Pizza Tribes

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

  • Pizzas
  • Coins

Actions

  • Click (tap)
  • Construct building
  • Upgrade building
  • Train
  • Steal
  • Expand

Other Features

  • Leaderboard
  • Reports

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:

  1. The Web App sends commands over the Web socket
  2. The Web API enqueue 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

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:

  1. The Web App send command to start construction of a building
  2. A worker processes the command
    1. Validates the command
    2. Updates the user game state: JSON.ARRAPPEND user:$user_id:gamestate .constructionQueue $constructionItem
    3. Finds the next time the user game state needs to be updated (e.g. when the construction is completed)
    4. Set next update time: ZADD user_updates $timestamp $user_id
  3. A updater updates the user game state at the next update time
    1. Runs ZRANGE user_updates 0 0 WITHSCORES to fetch the next user that needs update (and at what time)
    2. If the score (timestamp) has been passed
      1. Remove the next update time: ZREM user_updates $user_id
      2. Perform game state update
      3. Find next time the user game state needs to be updated again
      4. 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.

About

Pizza Tribes is a multiplayer persistent browser-based clicker real-time strategy game. The gameplay is a combination of a clicker game and a real-time strategy game.

Topics

Resources

License

Stars

Watchers

Forks