- Go 97.3%
- Just 2.2%
- Shell 0.3%
- Dockerfile 0.2%
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| benchmarks | ||
| internal/game | ||
| .dockerignore | ||
| .gitignore | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| main.go | ||
| README.md | ||
ACES
A multiplayer WWI-themed dogfighting game played entirely in the terminal over SSH.
_ ____ _____ ____
/ \ / ___| ____/ ___|
/ _ \| | | _| \___ \
/ ___ \ |___| |___ ___) |
/_/ \_\____|_____|____/
Connect to a server, pick a callsign, and engage in side-scrolling aerial combat with other players. No client installation required — just SSH.
ssh your-server.com
Gameplay
You fly a biplane in a horizontally-wrapping world filled with floating airships. Shoot down other players, collect power-ups, and climb the scoreboard.
Controls
| Key | Action |
|---|---|
| Left / Right | Change direction |
| Up / Down | Move vertically |
| Space | Shoot |
| Double-tap Left/Right | Activate boost |
| W | Drop mine |
| Q | Quit |
Combat
- Bullets travel horizontally in your facing direction
- Hitting another player scores a point; they respawn after 3 seconds
- Crashing into an airship destroys your plane (shields absorb one hit)
- Head-on collisions kill both players
Power-ups
Power-ups spawn randomly throughout the world:
| Icon | Type | Effect |
|---|---|---|
| D | Double Shot | Fires two parallel bullets (10s) |
| S | Scatter Shot | Fires a 3-bullet spread (10s) |
| M | Mine Pack | Grants 3 mine charges |
| O | Shield | Absorbs one hit (10s) |
Mines
- Drop mines behind your plane with W
- Mines are triggered by proximity (any non-owner player within 1 cell)
- The blast radius is 4 cells and damages everyone caught in it, including the owner (friendly fire)
- You cannot trigger your own mines by flying over them
Boost
- Double-tap your current direction to activate a speed boost (3x speed)
- Boost consumes fuel that recharges over time
- The HUD shows your current fuel level
Spawn Protection
You get 2 seconds of invulnerability after spawning, shown by a blinking effect. During this time you cannot be damaged by bullets, mines, collisions, or airships.
World
- The world is 1200 cells wide and wraps horizontally
- 30-45 floating airships drift across the sky as obstacles and cover
- A wrap seam marker shows where the world loops
- Ground and ceiling boundaries constrain vertical movement
HUD
The top bar shows:
- Score — your current kill count
- Ammo — regenerates over time (max 10)
- Boost fuel — shows remaining boost capacity
- Active weapon — if you have a power-up weapon, with time remaining
- Shield timer — if shielded
- Mine count — available mine charges
- Player count — number of connected players
The sidebar shows a live scoreboard of all players.
Running Locally
Requires Go 1.26+.
go run .
The server starts on port 2222 by default. Connect with:
ssh localhost -p 2222
Environment Variables
| Variable | Default | Description |
|---|---|---|
ACES_HOST |
0.0.0.0 |
Listen address |
ACES_PORT |
2222 |
Listen port |
ACES_HOST_KEY |
.ssh/id_ed25519 |
Path to SSH host key |
Deployment
Docker
docker build -t aces .
docker run -d --name aces --restart unless-stopped \
-p 22:22 \
-v aces-data:/data \
aces
The container runs on port 22 by default so players can connect with just ssh your-server.com.
The /data volume persists:
- SSH host key (
.ssh/id_ed25519) — generated on first run; keeps the key stable across redeployments so players don't get host key warnings - Highscore database (
aces.db) — SQLite file tracking top scores
VM Considerations
- If your VM's own sshd runs on port 22, move it to another port in
/etc/ssh/sshd_configbefore starting the container - Ensure port 22 is open in your firewall / security group
- The game accepts all SSH connections without authentication — this is by design for a public game
Architecture
Built with:
- Wish — SSH server framework
- Bubble Tea — terminal UI framework
- Lip Gloss — terminal styling
- modernc.org/sqlite — pure-Go SQLite for highscores
Each SSH connection gets its own Bubble Tea program. A shared world singleton runs the game loop at 20 FPS (50ms ticks), broadcasting state to all connected players via channels. Input from each player is forwarded to the world under a write lock, and rendering reads state under a read lock.
AI bots (Maverick, Goose) join automatically to keep the game interesting when few players are online.
Benchmarks
Performance is tracked across commits for the hot-path functions (Render and Tick). See benchmarks/BENCHMARKS.md for trend charts.
just bench # run benchmarks, print results
just bench-save # run (count=6), save median to CSV, update charts
just bench-compare REF # compare current against a saved commit via benchstat
just bench-history # list saved benchmark commits
License
MIT