Foodwars is a multiplayer top-down shooter where you battle other chefs in a shared kitchen. It runs in your browser and allows you to join friends using a kitchen code or link. You can also sign in with Google to save your results and see how you compare on the leaderboards!
Enter a username, join a kitchen, and click START COOKING! to start. Share the kitchen link with a friend if you want to join the same game.
| Control | Action |
|---|---|
| W / A / S / D | Move |
| Mouse | Aim |
| Left mouse button | Fire |
| R | Reload |
You need Node.js 22 or newer, npm, Redis, and Google OAuth credentials. To save accounts and game stats, you will also need a Supabase project. The database setup is covered below. You can use Docker Compose to run the app and Redis together.
git clone https://github.com/vihdutta/foodwars.git
cd foodwars
nvm install
nvm use
npm ciIf you do not use nvm, install a compatible Node.js version before running npm ci.
Create a .env file in the project folder using the settings below. For local Google sign-in, register http://localhost:8080/auth/google/callback as an authorized redirect URI and set GOOGLE_CALLBACK_URL to that exact value.
Start Redis, then start the application:
docker compose up -d redis
npm run devOpen http://localhost:8080 to play. npm run dev builds the TypeScript files and starts the server. You will need to restart it after making changes.
To run both services in containers instead, with the same .env file:
docker compose up --buildTo build and run the app without tsx, use npm run build followed by npm start.
| Variable | Purpose |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth client ID; required by the authentication setup. |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret. |
GOOGLE_CALLBACK_URL |
Set to http://localhost:8080/auth/google/callback for local sign-in. |
SESSION_SECRET |
Private random session signing secret; required in production. Generate one with node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))". |
SUPABASE_URL |
Supabase project URL for accounts and persistent statistics. |
SUPABASE_SECRET_KEY |
Server-side Supabase secret key. SUPABASE_SERVICE_ROLE_KEY is also supported. |
REDIS_URL |
Defaults to redis://localhost:6379. Compose supplies redis://redis:6379. |
PORT |
HTTP port; defaults to 8080. The supplied Compose mapping uses this default. |
PUBLIC_URL |
Base URL for the OAuth callback when GOOGLE_CALLBACK_URL is unset; defaults to https://foodwars.vihdutta.com. |
NODE_ENV |
Set to production for deployment; enables secure session cookies, requiring HTTPS. |
Keep .env and Supabase server credentials private. The browser accesses account and statistics data through Express endpoints.
The SQL files set up the database functions. You will need to create the users and game_results tables separately using the fields in src/services/supabase.ts. The schema needs unique users.google_user_id and users.username values, generated numeric primary keys, and a foreign key from game_results.google_user_id to users.google_user_id.
After creating the tables, apply the function definitions in sql/2_create_leaderboard_function.sql, sql/3_create_user_stats_function.sql, and sql/4_create_other_functions.sql. Review sql/5_grant_permissions.sql for execution grants.
Results for signed-in players are saved when a round ends or when they disconnect. Each disconnected session is saved as a separate result. If a save fails, the server logs the error but does not retry it later. This means results can be lost if the database goes down or the server crashes.
flowchart LR
Browser[Browser · PixiJS + TypeScript] <-->|HTTP / Socket.IO| Server[Express + game server]
Server <-->|Sessions / round statistics| Redis[(Redis)]
Server <-->|Accounts / results / leaderboards| Supabase[(Supabase · PostgreSQL)]
Browser <-->|Sign-in redirects| Google[Google OAuth]
Server <-->|Passport authentication| Google
The Node.js server keeps track of the game. Redis stores sessions and stats for the current round, while Supabase stores accounts and past results. The browser uses PixiJS to draw the game and Tailwind CSS for the menus.
| Command | What it does |
|---|---|
npm run dev |
Build TypeScript and launch the server with tsx. |
npm run build |
Compile server and browser TypeScript. |
npm start |
Run the compiled server. |
npm test |
Run regression tests. |
npm run test:integration |
Build and run HTTP, socket, session, and round checks; requires Redis. |
npm run check:supabase |
Check live tables and RPCs without writing records. |
npm run check:supabase:writes |
Create disposable records, verify writes and aggregates, then remove them. Requires a configured database and write permissions. |
The read check only checks that data can be read. To check inserts and updates too, use the write check. It creates temporary records and removes them afterward. The OAuth tests simulate responses from Google, so you will still need to test Google sign-in manually.
foodwars/
├── .github/
│ └── workflows/ # Build, audit, test, and deploy
├── public/
│ ├── images/ # Game artwork
│ ├── js/ # Browser TypeScript modules
│ ├── index.html # Game entry page
│ └── style.css
├── scripts/ # Integration and live database checks
├── sql/ # PostgreSQL functions and grants
├── src/
│ ├── backend/ # Authentication, physics, maps, and rounds
│ ├── services/ # Redis, Supabase, and statistics persistence
│ ├── tilesets/ # Tiled maps and tileset definitions
│ ├── types/ # Shared game types
│ ├── constants.ts # Game and server configuration
│ └── server.ts # Express and Socket.IO server
├── tests/ # Regression tests
├── Dockerfile
├── docker-compose.yml
├── package.json
└── tsconfig.json
The GitHub Actions workflow runs when you push to main. You can also start it manually from GitHub Actions. It installs and audits dependencies, builds the app, and runs the tests before pushing a Docker image and deploying it to AWS Lightsail over SSH.
Add these values to the GitHub production environment:
| Kind | Names |
|---|---|
| Variables | DOCKERHUB_USERNAME, LIGHTSAIL_PUBLIC_IP, LIGHTSAIL_USERNAME |
| Secrets | DOCKERHUB_ACCESS_TOKEN, LIGHTSAIL_SSH_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SUPABASE_URL, SESSION_SECRET |
| Supabase secret | SUPABASE_SECRET_KEY or SUPABASE_SERVICE_ROLE_KEY |
Local .env values are not copied to GitHub. The workflow targets foodwars.vihdutta.com, writes the host configuration, and restarts the containers. It assumes Docker and NGINX are available on the host. Review the domain, DNS, and HTTPS configuration before using it for another deployment.