# go-fish-web
Shared protocol types for the Go Fish game — serialised as JSON over WebSocket.
This crate defines `ClientMessage` and `ServerMessage` plus all supporting types. It contains no application logic; the server and client own the transport and deserialization call sites.
## Types
### `ClientMessage` (client → server)
| `Identity` | — | First message sent on connect; requests a server-assigned player name |
| `CreateLobby` | — | Create a new game lobby |
| `JoinLobby(String)` | lobby ID | Join an existing lobby |
| `LeaveLobby` | — | Exit the current lobby |
| `StartGame` | — | Lobby leader starts the game |
| `Hook(ClientHookRequest)` | `{ target_name, rank }` | Execute a turn: ask `target_name` for all cards of `rank` |
| `AddBot { bot_type }` | `BotType` | Leader adds a bot slot (currently only `SimpleBot`) |
| `RemoveBot` | — | Leader removes the last bot slot |
### `ServerMessage` (server → client)
| `PlayerIdentity(String)` | assigned name | Response to `Identity` |
| `LobbyJoined { lobby_id, leader, players, max_players }` | `players: Vec<LobbyPlayer>` | Lobby entry confirmed |
| `LobbyUpdated { leader, players }` | `players: Vec<LobbyPlayer>` | Broadcast when lobby roster changes |
| `LobbyLeft(LobbyLeftReason)` | — | Lobby exit confirmed |
| `GameStarted` | — | Broadcast when the game begins |
| `GameSnapshot(GameSnapshot)` | full state | Sent to all players after each turn |
| `HookError(HookError)` | error kind | Turn was rejected |
| `PlayerTurn(PlayerTurnValue)` | `YourTurn` or `OtherTurn(name)` | Indicates whose turn it is |
| `GameResult(GameResult)` | winners + losers | Sent when the game ends |
| `Error(String)` | message | Generic server error |
### Supporting types
**`BotType`** — identifies which bot implementation to add to a lobby. Currently the only variant is `SimpleBot`.
**`LobbyPlayer`** — a slot in the lobby player list, used in `LobbyJoined` and `LobbyUpdated`:
```rust
pub enum LobbyPlayer {
Human { name: String },
Bot { name: String, bot_type: BotType },
}
```
**`GameSnapshot`** — the primary message during play, sent after every turn:
```rust
pub struct GameSnapshot {
pub hand_state: HandState, // your hand + completed books
pub opponents: Vec<OpponentState>, // opponents' visible state
pub active_player: String, // whose turn it is
pub last_hook_outcome: Option<HookOutcome>, // result of the last turn
pub deck_size: usize, // cards remaining in the draw pile
}
```
**`OpponentState`** — what you can see of another player:
```rust
pub struct OpponentState {
pub name: String,
pub card_count: usize, // hand size (individual cards hidden)
pub completed_books: Vec<CompleteBook>,
}
```
**`HookError`** variants: `NotYourTurn`, `UnknownPlayer(String)`, `CannotTargetYourself`, `YouDoNotHaveRank(Rank)`.
**`PlayerTurnValue`** variants: `YourTurn`, `OtherTurn(String)`.
## Wire format
All types derive `serde::Serialize` / `Deserialize`. The wire format is JSON using Serde's default tagged-union encoding:
```json
"Identity"
{"Hook": {"target_name": "Alice", "rank": "Ace"}}
{"LobbyJoined": {
"lobby_id": "abc123",
"leader": "Bob",
"players": ["Alice", "Bob"],
"max_players": 4
}}
{"GameSnapshot": {
"hand_state": { ... },
"opponents": [{ "name": "Alice", "card_count": 3, "completed_book_count": 1 }],
"active_player": "Bob",
"last_hook_outcome": null
}}
```
## Protocol flow
```
Client Server
│─── Identity ───────────────▶│
│◀── PlayerIdentity("Bob") ───│
│
│─── CreateLobby ────────────▶│
│◀── LobbyJoined ─────────────│ (broadcast LobbyUpdated to others)
│
│─── StartGame ──────────────▶│
│◀── GameStarted ─────────────│ (broadcast to all)
│
│─── Hook({target, rank}) ───▶│
│◀── GameSnapshot ────────────│ (broadcast to all; contains HookOutcome)
│ or HookError ──────────────│ (only to sender, if invalid)
│
│◀── GameResult ──────────────│ (broadcast when game ends)
```
## Dependencies
| [`go-fish`](../go-fish) | `Rank`, `Hand`, `CompleteBook`, `HookResult`, `IncompleteBook` |
| `serde` | Serialisation derive macros |
| `serde_json` | JSON codec |
## License
MIT