go-fish-game-server
Async WebSocket game server for the Go Fish card game, built with Tokio and tokio-tungstenite.
Features
- WebSocket-based JSON protocol (shared with
go-fish-web) - Lobby system: create, join, and start games; auto-starts when a lobby is full
- Bot players: lobby leader can add/remove bots before starting; bots use
go_fish::bots::SimpleBot - Supports up to N players per lobby (configurable)
- Ping/pong keepalive with automatic disconnect of unresponsive clients
- OpenTelemetry tracing and logging via OTLP (HTTP)
- TOML configuration with sensible defaults
Quick start
# Run with defaults (127.0.0.1:9001)
# Run with a config file
Configuration
Create a TOML file with any combination of the following fields:
= "127.0.0.1:9001" # interface and port to bind
= 4 # players needed to auto-start a lobby
= 10 # hard cap on concurrent connections
All fields are optional; the values above are the defaults.
Bot behaviour can also be tuned:
[]
= 2000 # minimum simulated thinking delay per bot turn
= 4500 # maximum simulated thinking delay per bot turn
[]
= 3 # observations retained (0 = memoryless / random)
= 0.2 # probability noise std-dev (0.0 = deterministic)
Environment variables
| Variable | Effect |
|---|---|
RUST_LOG |
Log level / filter (default: info) |
OTEL_EXPORTER_OTLP_ENDPOINT |
Override OTLP collector endpoint |
Docker
The image bundles build/config.toml (binds 0.0.0.0:80, 4 players, 10 connections).
Protocol
All messages are JSON-encoded WebSocket text frames. Types are defined in the go-fish-web crate.
Client → Server
| Message | When | Description |
|---|---|---|
"Identity" |
After connecting | Request a player identity (name) |
"CreateLobby" |
After identity | Create a new lobby and become its leader |
{"JoinLobby": "<id>"} |
After identity | Join an existing lobby by ID |
"StartGame" |
In lobby, leader only | Start the game (requires ≥ 2 participants) |
{"AddBot": {"bot_type": "SimpleBot"}} |
In lobby, leader only | Add a bot slot |
"RemoveBot" |
In lobby, leader only | Remove the last bot slot |
{"Hook": {"target": "<name>", "rank": "<rank>"}} |
In game, your turn | Ask a player for cards of a given rank |
"LeaveLobby" |
In lobby or game | Leave the current lobby |
Server → Client
| Message | Description |
|---|---|
{"PlayerIdentity": "<name>"} |
Assigned player name |
{"LobbyJoined": {...}} |
Joined a lobby; includes leader, players, and max_players |
{"LobbyUpdated": {...}} |
Lobby membership changed |
{"LobbyLeft": "<reason>"} |
Removed from lobby |
"GameStarted" |
Game has begun |
{"GameSnapshot": {...}} |
Full game state after each turn |
{"HookAndResult": {...}} |
Details of the last hook (broadcast to all) |
{"HookError": "<reason>"} |
Invalid hook attempt |
{"GameResult": {...}} |
Game over; includes winners and losers |
{"Error": "<message>"} |
Protocol or parse error |
Game flow
connect → Identity → CreateLobby / JoinLobby → [StartGame] → Hook (repeat) → GameResult
- Send
"Identity"→ receivePlayerIdentitywith your assigned name. - Create or join a lobby.
- The lobby leader sends
"StartGame"(or the game auto-starts when the lobby is full). - On your turn, send a
Hook. All clients receiveHookAndResultthen an updatedGameSnapshot. - When the game finishes, all clients receive
GameResultand return toPreLobby.
Architecture
TCP listener ──(ClientEvent)──▶ ConnectionManager ──(LobbyEvent)──▶ LobbyManager
▲ │
└──────(LobbyOutboundMessage)──────┘
Three independent Tokio tasks communicate through typed mpsc channels. One lightweight ConnectionHandler task is spawned per client; no mutexes are needed in the hot path.
Testing
The test suite includes unit tests and proptest-based property tests for both the connection layer (connection.rs) and the lobby/game layer (lobby.rs). Tests use in-memory duplex streams — no real network I/O — so they run in milliseconds.