Skip to content

Seating: core events to add and remove players during a match #1327

Description

@devill

Follow-on from #1326, which gives ctx an explicit players list — who is in the match.
Once that list exists it can change. Would close #884.

Proposal

events.seat(id) and events.unseat(id), which add to and remove from ctx.players.
A game with no seating key behaves exactly as today.

Seating stays with the game, because the details vary: some games let a player pick from
the empty seats, others assign them. So the framework says who is waiting, and the game
decides what to do about it.

const blackjack = {
  minPlayers: 1,
  maxPlayers: 7,

  seating: {                                    // names provisional
    onWaiting: ({ events }, waiting) => {        // blackjack seats anyone, next round
      for (const id of waiting) events.seat(id);
    },
    onSit:    ({ G }, playerID) => { G.chips[playerID] = 100; },
    onUnseat: ({ G }, playerID) => { delete G.chips[playerID]; },
  },
};

The waiting list is metadata.players minus ctx.players. Passing it as an argument
keeps it out of ctx.

Handing a seat over is unseat plus seat plus a game-supplied migration — G is
keyed by player ID, so only the game knows what moves across.

Open

  1. When is onWaiting called? Every admission, every phase boundary, or both. A game
    that declines someone mid-hand has to be asked again, or every game ends up keeping
    its own waiting list.
  2. Letting the player pick a seat needs a route that is not a move. A waiting player
    is not in ctx.players, so isPlayerActive rejects anything they send
    (master.ts:267). Either seat choice arrives with the join and stays a lobby concern,
    or waiting players get some narrow way to act.

What it costs

  • Admitting a player becomes a state change, so join needs a reducer round-trip
    through the master, as leaveGame already does. Today it writes metadata only — no
    reducer, no lock, no _stateID bump. This is the one worth agreeing before any code.
  • maxPlayers is only enforced when a match is created (api.ts:256). The seat path
    needs it too.
  • getFirstAvailablePlayerID iterates indices (util.ts:80), and IDs stop being dense.
  • Join stops returning 409 once the seats are full (api.ts:393).
  • Seat changes must reuse rebaseUndoRedoState (reducer.ts:104), or undo brings a
    player back.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions