# API Reference: adenosine-multiplayer

Game-agnostic multiplayer WebSocket client with lobby and board game template.

## Table of Contents

- [MP](#mp): Main client
- [MSG](#msg): Message type constants
- [MP_PALETTE](#mp_palette): Player colour palette
- [BoardGameTemplate](#boardgametemplate): HTML template for board games
- [Types](#types)

---

## MP

The `MP` object is the main multiplayer client. Assign callbacks before calling `connect()`.

### `MP.configure(config)`

Set deployment-specific wiring. Must be called before `connect()`.

| Param | Type | Description |
|-------|------|-------------|
| `config.defaultServer` | `string` | Where `connect()` goes when called with no argument. Bare `"host[:port][/path]"`, scheme is chosen from page protocol. |
| `config.allowlist` | `readonly string[]` | Extra hosts a `?server=` override may name, on top of the page's own origin and private ranges. |

```js
AdMP.MP.configure({
  defaultServer: 'games.example.com/chess',
  allowlist: ['games.example.com'],
});
```

### `MP.connect(url?)`

Connect to the game server. With no argument, uses `defaultServer` from `configure()`.

```js
MP.connect();                    // uses configured defaultServer
MP.connect('wss://other:8769');  // explicit URL
```

### `MP.quit()`

Leave the room and clear the local name and room code. Sends a `quit` frame; the
server is expected to broadcast `player_quit` to the others.

There is no separate disconnect method, closing without telling the server leaves a ghost in
the lobby until the socket times out.

### `MP.sendAction(data)`

Send a game action to the server.

| Param | Type | Description |
|-------|------|-------------|
| `data` | `object` | Action payload (must include `type`) |

```js
MP.sendAction({ type: 'play_card', card: { suit: 'hearts', rank: 'A' } });
```

### `MP.sendChat(text)`

Send a chat message in the current room.

### `MP.join(name, color, room?)`

Enter a room by name, letting the server place you if `room` is omitted. Sets the
local name immediately; the server confirms via `onWelcome`.

| Param | Type | Description |
|-------|------|-------------|
| `name` | `string` | Display name |
| `color` | `string` | Player colour, see [MP_PALETTE](#mp_palette) |
| `room` | `string` | Optional room code |

### `MP.createRoom(name, color, roomCode)`

Request a new room from the server. **All three arguments are required**: the
join details travel with the create. Triggers the `onRoomCreated` callback.

### `MP.joinRoom(name, color, roomCode)`

Join an existing room. Like `createRoom`, this carries the player's identity, so
it is not a bare `joinRoom(code)`. Triggers `onRoomJoined`.

### `MP.spectate(name, room?)`

Watch without playing. Marks the client a spectator locally and the server
answers with `onSpectatorWelcome` rather than `onWelcome`.

### `MP.startGame()`

Tell the server to start the game (host only).

### Getters

| Method | Returns | Description |
|--------|---------|-------------|
| `MP.getMyName()` | `string \| null` | Player name |
| `MP.getMyColor()` | `string \| null` | Player colour |
| `MP.getRoomCode()` | `string \| null` | Current room code |
| `MP.amIHost()` | `boolean` | Whether this player is the host |
| `MP.isSpectator()` | `boolean` | Whether this is a spectator connection |

### Callbacks

Assign these before calling `connect()`:

| Callback | Signature | Description |
|----------|-----------|-------------|
| `MP.onConnected` | `() => void` | Connection established |
| `MP.onDisconnected` | `() => void` | Connection lost |
| `MP.onRejected` | `(reason: string) => void` | Server rejected the connection |
| `MP.onWelcome` | `(data: MPMessage) => void` | Initial server welcome |
| `MP.onSpectatorWelcome` | `(data: MPMessage) => void` | Spectator welcome |
| `MP.onLobbyUpdate` | `(data: MPMessage) => void` | Lobby state update |
| `MP.onLobbySnapshot` | `(data: MPMessage) => void` | Full lobby snapshot |
| `MP.onGameStarted` | `(data: MPMessage) => void` | Game has started |
| `MP.onGameState` | `(state: unknown) => void` | Full game state from server |
| `MP.onGameAction` | `(action: unknown) => void` | Broadcast game action |
| `MP.onChatMessage` | `(from, text, color) => void` | Chat message received |
| `MP.onSystemMessage` | `(text: string) => void` | System message |
| `MP.onPlayerJoined` | `(data: MPMessage) => void` | Player joined the room |
| `MP.onPlayerQuit` | `(data: MPMessage) => void` | Player left the room |
| `MP.onRoomCreated` | `(code: string) => void` | New room created |
| `MP.onRoomJoined` | `(code: string) => void` | Joined a room |
| `MP.onError` | `(text: string) => void` | Error message |

---

## MSG

Message type constants to avoid magic strings.

```js
const { MSG } = AdMP;

// Client → Server
MSG.JOIN            // 'join'
MSG.CREATE_ROOM     // 'create_room'
MSG.JOIN_ROOM       // 'join_room'
MSG.SPECTATE        // 'spectate'
MSG.START_GAME      // 'start_game'
MSG.GAME_ACTION     // 'game_action'
MSG.CHAT            // 'chat'
MSG.QUIT            // 'quit'

// Server → Client
MSG.LOBBY_SNAPSHOT  // 'lobby_snapshot'
MSG.WELCOME         // 'welcome'
MSG.SPECTATOR_WELCOME // 'spectator_welcome'
MSG.REJECTED        // 'rejected'
MSG.LOBBY_UPDATE    // 'lobby_update'
MSG.GAME_STARTED    // 'game_started'
MSG.GAME_STATE      // 'game_state'
MSG.GAME_ACTION_BC  // 'game_action' (broadcast)
MSG.CHAT_MSG        // 'chat'
MSG.SYSTEM_MSG      // 'system'
MSG.PLAYER_QUIT     // 'player_quit'
```

---

## MP_PALETTE

Array of 12 player colours. Assigns colours to players in join order.

```js
const { MP_PALETTE } = AdMP;
// ['#ff2d55', '#ff7c1e', '#ffe135', '#39d353', '#6cd4f5', '#4059c8',
//  '#9b30ff', '#ff69b4', '#fff5e1', '#00fa9a', '#ff4f6d', '#7b68ee']
```

---

## BoardGameTemplate

Generates the common HTML structure for board games (lobby, game board area, game-over overlay).

### `BoardGameTemplate.render(config)`

Build the page HTML, append it to `.container`, and return it.

It does both: the markup is injected for you, and the same string comes back
for a caller that wants to inspect or place it elsewhere. If no `.container`
exists the markup is still returned, just not inserted.

Note that `insertAdjacentHTML` never executes inserted `<script>` elements, so
a caller must place its own script tags in the document after calling this.

| Param | Type | Description |
|-------|------|-------------|
| `config` | `BoardGameConfig` | Layout configuration |

```js
const { BoardGameTemplate } = AdMP;

BoardGameTemplate.render({
  title: 'CHESS',
  subtitle: '// NEON EDITION //',
  buttons: [
    { id: 'createBtn', label: 'CREATE GAME', cls: 'primary' },
    { id: 'joinBtn', label: 'JOIN GAME' },
  ],
  gameControls: [
    { id: 'restartBtn', label: 'RESTART', icon: '⟲' },
    { id: 'quitBtn', label: 'QUIT', icon: '✕' },
  ],
  credits: 'MagmaCrunch Media',
});
```

### BoardGameConfig

```ts
interface BoardGameConfig {
  title?: string;           // game title
  subtitle?: string;        // subtitle below title
  footer?: string[];        // footer lines
  buttons?: BoardGameButton[];  // start menu buttons
  extraStart?: string;      // HTML injected after start buttons
  gameHeader?: string;      // HTML for game header area
  gameBody?: string;        // HTML for main game area
  gameControls?: BoardGameButton[];  // in-game control buttons
  instructions?: string;    // how-to-play text
  credits?: string;         // credits text
  gameOverTitle?: string;   // game-over overlay title
  gameOverMsg?: string;     // game-over overlay message
  extraHead?: string;       // HTML injected into <head>
}
```

### BoardGameButton

```ts
interface BoardGameButton {
  id: string;     // DOM element ID
  label: string;  // button text
  cls?: string;   // CSS class (e.g. 'primary')
  icon?: string;  // icon character
}
```

---

## Types

### `MPMessage`

```ts
interface MPMessage {
  type: string;
  [key: string]: unknown;
}
```

### `MPConfig`

```ts
interface MPConfig {
  defaultServer?: string;      // where connect() goes with no argument
  allowlist?: readonly string[];  // extra hosts a ?server= override may name
}
```

### `MsgType`

```ts
type MsgType = (typeof MSG)[keyof typeof MSG];
```

The union of every value in [MSG](#msg), useful for exhaustive switches over
incoming `message.type`.

### `BoardGameConfig` / `BoardGameButton`

Documented with [BoardGameTemplate](#boardgametemplate) above.

---

## Connection lifecycle

**`MP` does not reconnect, deliberately.** On `close` it clears the session
identity the server assigned: `_myName`, `_myColor`, `_roomCode`, `_isHost`,
`_isSpectator`, and calls `onDisconnected()`.

That is not an oversight, and it is the opposite of what
`adenosine-score-client` does with its own socket. The difference is in what
each client is holding when the socket drops:

- `score-client` holds a **queue of writes**. They are replayable, so it
  reconnects every 3s and flushes them. Nothing is lost by trying again.
- `MP` holds **server-side session identity**. A room membership exists on the
  server, and reopening a socket does not restore it. An automatic reconnect
  would hand you a live connection that belongs to no room, in which every
  `send` is silently dropped, worse than a clean disconnect, because it looks
  like it is working.

So rejoining is the caller's decision, made with the caller's knowledge of
whether the game is still worth rejoining. Handle `onDisconnected()`, and call
`connect()` and `joinRoom()` again if you want to come back.
