This is the backend for Shootball - a game born from my 9-year-old son Araz's idea to turn our penny football matches into something friends could play online. His game design instincts shaped every mode; this server makes sure nobody can cheat.
Powered by Nakama open-source game server.
The server handles matchmaking, runs all physics simulations, determines game outcomes, and streams frame-by-frame trajectories back to clients - ensuring both players always see identical results.
- Server-authoritative physics - Custom 2D circle physics engine (circle-circle collisions, circle-wall collisions, damping, bounce, friction)
- 5 game modes - CoinBall, Football, Battle Arena, Volleyball, Curling
- Per-mode world builders - Each mode constructs its own arena geometry, walls, goals, pits, and nets
- Trajectory recording - Physics simulation records object positions at regular intervals for client-side replay
- Matchmaking - Automatic player pairing with per-mode queues
- Disconnect handling - Remaining player wins if opponent leaves
# Clone the repository
git clone https://github.com/mamipour/shootball-server.git
cd shootball-server
# Install dependencies
npm install
# Copy environment variables
cp .env.example .env
# Build TypeScript
npm run build
# Start Nakama + PostgreSQL
docker compose up -dThe server exposes three ports:
| Port | Service |
|---|---|
| 7349 | Nakama Console (admin dashboard) |
| 7350 | Game API (client connections) |
| 7351 | gRPC API |
Admin Console: http://localhost:7349 - default credentials are in .env
Recompile TypeScript after changes:
npx tscThen restart the Nakama container to pick up the new build:
docker compose restart nakamashootball-server/
├── src/
│ └── main.ts # All server logic (physics, world builders, match handler)
├── dist/
│ └── main.js # Compiled JS (loaded by Nakama runtime)
├── build/
│ └── index.d.ts # Nakama runtime type definitions
├── docker-compose.yml # Nakama 3.37.0 + PostgreSQL 16
├── tsconfig.json
├── .env.example # Environment variable template
└── package.json
All server logic lives in a single src/main.ts file (Nakama requires a single JS entry point). It contains:
- Physics engine - Circle-circle and circle-wall collision resolution, velocity damping, impulse application, settling detection
- World builders - Per-mode arena construction (walls, goals, pits, nets, house rings)
- Simulation runners -
runSingleShotSimulation(CoinBall),runMultiShotSimulation(Football, Battle Arena, Volleyball),runCurlingShotSimulation(Curling) - Match handler - Nakama match lifecycle (
matchInit,matchJoinAttempt,matchJoin,matchLeave,matchLoop,matchSignal,matchTerminate)
Communication between client and server uses Nakama match state messages with the following op codes:
| Code | Name | Direction | Description |
|---|---|---|---|
| 1 | SHOT_SUBMIT |
Client -> Server | Single shot input (pill index, direction, power) |
| 2 | ROUND_START |
Server -> Client | New round begins, includes current positions |
| 3 | SHOTS_EXECUTE |
Server -> Client | Both shots revealed (legacy, pre-authoritative) |
| 4 | GOAL_SCORED |
Server -> Client | Goal event with updated scores |
| 5 | GAME_OVER |
Server -> Client | Match ended, winner declared |
| 6 | PLAYER_READY |
Client -> Server | Player ready for next round |
| 7 | OPPONENT_INFO |
Server -> Client | Opponent display name and avatar |
| 8 | PLAYER_LEFT |
Server -> Client | Opponent disconnected |
| 9 | TIMER_SYNC |
Server -> Client | Remaining aim time |
| 10 | ROUND_RESULT |
Server -> Client | Round outcome (legacy) |
| 11 | MULTI_SHOT_SUBMIT |
Client -> Server | Multiple shots for multi-pill modes |
| 12 | MULTI_SHOTS_EXECUTE |
Server -> Client | All multi-shots revealed (legacy) |
| 13 | ELIMINATION |
Server -> Client | Pill eliminated (legacy) |
| 14 | CURLING_TURN |
Server -> Client | Curling turn begins, includes positions |
| 15 | CURLING_END_SCORED |
Server -> Client | Curling end scored (legacy) |
| 16 | SIM_RESULT |
Server -> Client | Primary message - full trajectory + outcome data |
The SIM_RESULT (op code 16) is the core of the server-authoritative model. It contains:
trajectory- Array of frames, each frame containing[x, y]positions for every objectoutcome- Scorer, updated scores, eliminations, game over status, winnerpositions- Final resting positions of all objects
| Variable | Description | Default |
|---|---|---|
POSTGRES_PASSWORD |
PostgreSQL password | localdb |
NAKAMA_CONSOLE_USERNAME |
Admin console username | admin |
NAKAMA_CONSOLE_PASSWORD |
Admin console password | password |
For production, change all default credentials and consider adding TLS termination via a reverse proxy.
A $5/month VPS (2 GB RAM) comfortably handles hundreds of concurrent matches. For production:
- Update credentials in
.env - Set up a reverse proxy (nginx/Caddy) with TLS for ports 7350 and 7349
- Run
docker compose up -d
See the Nakama deployment docs for advanced configuration.
- Shootball - The game client (Godot 4.x)
This project is licensed under the CC BY-NC 4.0 (Creative Commons Attribution-NonCommercial 4.0 International) license. You are free to use, share, and adapt this work for non-commercial and educational purposes with appropriate credit. See the LICENSE file for details.