> For the complete documentation index, see [llms.txt](https://wiki.playgama.com/playgama/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.playgama.com/playgama/guides/game-development/multiplayer.md).

# Multiplayer

How to build a multiplayer game that starts fast: matchmaking, bots in empty seats, the host and its world, and testing.

A multiplayer game on the web loses most players while they wait for an opponent: there are not always other players online in the player's region and mode right now. This guide is about building a match that starts in seconds whatever the number of players online. The API is the [Multiplayer module](/playgama/bridge-sdk/api/multiplayer.md).

## Never leave the player without a game

* Check [`isSupported`](/playgama/bridge-sdk/api/multiplayer.md#is-supported) before showing online modes. When it is `false`, the game still has to be playable: offer a match against bots or a single-player mode instead of an error.
* If entering a room fails (for example `MULTIPLAYER_CONNECTION_FAILED`) or the player loses the room with the `connection_lost` reason, start a match against bots instead of sending the player back to an empty menu.
* Keep the core loop the same online and offline: the same match code runs with players, bots or both.

## Fill empty seats with bots

The player should wait a few seconds at most. Then the match starts, and the seats nobody took are played by bots.

* **Short wait.** Wait for players for a few seconds — longer for a large room, shorter for a duel. Show that the search is going on: the players who have joined, a countdown or a progress bar.
* **Start when the room is full or the time is up,** whichever comes first. The host decides: it starts the match and adds the bots, the other players wait for the start.
* **Start right away if the player wants.** A "Play now" button during the search lets an impatient player start against bots at once.
* **Bots live in the host's world.** The host adds them to the game state, runs their logic in its game loop and sends the result like any other change. The other players see bots as ordinary participants and need no bot logic of their own.
* **Keep bots across a host change.** When the host leaves, the new host has to continue the bots. With [Sync State](/playgama/bridge-sdk/api/multiplayer.md#sync-state) the new host gets the last world, bots included, and goes on from there; without it, keep the bots' data in the state you send with messages.
* **Replace players who leave.** When a player leaves in the middle of a match, a bot takes their seat, so the match does not break and the others do not lose their game.
* **Let new players replace bots.** If the game allows joining a match that has started, keep the room open and visible, and give a bot's seat to a player who joins — right away or at the next round. Otherwise close the room when the match starts with [`setRoomOpen(false)`](/playgama/bridge-sdk/api/multiplayer.md#room).

### Make bots feel like players

* Give bots names and avatars that fit the game, and reaction times and mistakes like a real player's. A bot that never misses is as bad as one that stands still.
* Match the bots' strength to the player: weaker for a new player, stronger for one who keeps winning. Losing every match against bots drives players away faster than waiting.
* Do not submit bots' results to [leaderboards](/playgama/bridge-sdk/api/leaderboards.md), and decide whether a win against bots gives the same reward as a win against players.

### Example

The host starts the match when the room is full or the time is up and fills the free seats with bots. The other players get the started match with Sync State.

```javascript
const MATCH_SIZE = 4
const WAIT_FOR_PLAYERS_MS = 8000

let world = { started: false, players: [] }
let waitTimer = null

function findMatch() {
    if (!bridge.multiplayer.isSupported) {
        startOfflineMatch()
        return
    }

    bridge.multiplayer.setSyncState(world)
    bridge.multiplayer.joinOrCreateRoom({
        maxPlayers: MATCH_SIZE,
        filter: { mode: 'arena', version: 3 },
        syncState: true,
    })
        .then(() => {
            showSearch()
            waitForPlayers()
        })
        .catch(() => startOfflineMatch())
}

// The host waits; a new host restarts the wait if the match has not started
function waitForPlayers() {
    clearTimeout(waitTimer)
    if (bridge.multiplayer.isHost && !world.started) {
        waitTimer = setTimeout(startMatch, WAIT_FOR_PLAYERS_MS)
    }
}

function startMatch() {
    if (!bridge.multiplayer.isHost || world.started) {
        return
    }

    clearTimeout(waitTimer)
    const humans = bridge.multiplayer.players.map(player => ({ id: player.id, name: player.name, isBot: false }))
    const bots = createBots(MATCH_SIZE - humans.length)

    world = { started: true, players: [...humans, ...bots] }
    bridge.multiplayer.setSyncState(world)
}

bridge.multiplayer.on(bridge.EVENT_NAME.MULTIPLAYER_PLAYER_JOINED, () => {
    if (bridge.multiplayer.players.length === MATCH_SIZE) {
        startMatch()
    }
})

bridge.multiplayer.on(bridge.EVENT_NAME.MULTIPLAYER_PLAYER_LEFT, player => {
    if (bridge.multiplayer.isHost && world.started) {
        replaceWithBot(player.id)
        bridge.multiplayer.setSyncState(world)
    }
})

bridge.multiplayer.on(bridge.EVENT_NAME.MULTIPLAYER_HOST_CHANGED, waitForPlayers)

bridge.multiplayer.on(bridge.EVENT_NAME.MULTIPLAYER_SYNC_STATE_RECEIVED, state => {
    world = state
})

bridge.multiplayer.on(bridge.EVENT_NAME.MULTIPLAYER_ROOM_LEFT, reason => {
    if (reason === 'connection_lost') {
        startOfflineMatch()
    }
})
```

`createBots()`, `replaceWithBot()`, `showSearch()` and `startOfflineMatch()` are the game's own code. The host runs the bots in its game loop and calls `setSyncState(world)` after each change.

## Matchmaking

* Use quick match — [`joinOrCreateRoom()`](/playgama/bridge-sdk/api/multiplayer.md#join-or-create-room) — as the main way to play. Rooms by code are for playing with friends.
* Put the game version and the mode into `filter`, so that players of incompatible builds never meet. Keep the number of modes and room sizes small: every extra mode splits the players online into smaller groups and makes bots more likely.
* Let players invite friends with [`inviteToRoom()`](/playgama/bridge-sdk/api/multiplayer.md#invite-to-room) and open the room from the invite with [`launchRoomId`](/playgama/bridge-sdk/api/multiplayer.md#launch-room-id). Bots fill the seats friends have not taken.

## Host and game state

* Keep the authoritative game state on the host. Other players send their actions to the host and show what it sends back; do not let each device decide the result on its own.
* Check `isHost` after every [host changed](/playgama/bridge-sdk/api/multiplayer.md#players) event: any player can become the host in the middle of a match.
* Do not send messages every frame. Send player actions when they happen and the world a few times a second; on the other devices, smooth movement between updates.
* Keep the world small: positions, scores, timers — not textures, meshes or anything the game can compute.

## Ads and pauses

* Show interstitial ads only between matches, never during one: the match goes on on the other devices while one player watches an ad.
* Offer rewarded ads before or after a match, for example to double the reward.
* A player who switches tabs or minimizes the browser can stay in the room without playing. Their character should not break the game for the others: let a bot play for them until they are back, or keep them idle.

## Check before uploading

* Play alone: the search ends after the wait and the match starts with bots.
* Open the game in two or more tabs: the players meet in one room and the free seats go to bots.
* Close the host's tab in the middle of a match: another player becomes the host, the match and the bots go on.
* Close another player's tab: a bot takes their seat.
* Turn off the network or remove the public token from the config: the game offers a match against bots instead of an error.
