Multiplayer Battleship over SSH. Connect with a single command — no install, no account, no friction.
ssh play.trevato.dev
Built with Go, Bubbletea, Wish, and Lip Gloss. Styled with Catppuccin Mocha.
# With Nix (no clone needed)
nix run github:Trevato/battleship-cli
# Or clone and run
go run .This starts an SSH server on port 23234. Connect from any terminal:
ssh localhost -p 23234Start the server, then have them connect using your local IP:
ssh <your-ip> -p 23234The -l flag sets your display name: ssh <ip> -p 23234 -l captain
- Create Game — get a 4-character code to share
- Join Game — enter a friend's code
- Quick Match — get paired with the next player who queues
- Place your fleet (hjkl to move, r to rotate, enter to place)
- Take turns firing shots (hjkl to aim, enter to fire)
- Sink all 5 ships to win
| Key | Action |
|---|---|
h/j/k/l or arrows |
Move cursor |
enter or space |
Select / Fire / Place |
r |
Rotate ship (placement) |
tab |
Toggle board view (battle, narrow terminals) |
esc |
Back |
q |
Quit |
| Ship | Size |
|---|---|
| Carrier | 5 |
| Battleship | 4 |
| Cruiser | 3 |
| Submarine | 3 |
| Destroyer | 2 |
# Nix (adds `battleship` to your path)
nix shell github:Trevato/battleship-cli
# Or build from source
go install github.com/trevato/inter-face@latest- Nix with flakes enabled, or Go 1.25+
# With Nix (recommended)
nix develop # or use direnv with `use flake` in .envrc
# Run the server
go run .
# Connect (use two terminals for two players)
ssh localhost -p 23234 -l player1
ssh localhost -p 23234 -l player2go test ./... # unit + integration tests
go test -race ./... # with race detectormain.go SSH server entry point
internal/
game/ Core game logic (board, ships, state, AI)
protocol/ Message types between client and server
server/ Lobby manager, matchmaking, player bridge
ui/ Bubbletea models for each game phase
Configured for Fly.io with TCP passthrough on port 22.
fly volumes create ssh_data --size 1 --region iad
fly ips allocate-v4
fly deployPoint a DNS A record at the Fly IPv4 and you're live.
Manager (singleton)
└─ Lobby (per game, owns Game state + mutexes)
├─ Player 1 (tea.Program via SSH session)
└─ Player 2 (tea.Program via SSH session)
Each SSH connection gets its own Bubbletea program. Lobbies broadcast game events to both players. Game state is server-side, protected by mutexes. Dispatch from UI to server runs asynchronously to avoid deadlocking Bubbletea's event loop.
MIT