Documentation

Game logic

v1 · Deprecated

Framework v1 is deprecated for new games. Existing games remain playable and editable.

Players and turns

PlayerDef

abstract class PlayerDef<
  PublicState  extends Record<string, unknown> = {},
  PrivateState extends Record<string, unknown> = {},
  Static       extends Record<string, unknown> = {},
> {
  readonly static?: Static;
  publicState:  PublicState;
  privateState: PrivateState;

  protected publicDefaults():  Partial<PublicState>;
  protected privateDefaults(): Partial<PrivateState>;

  constructor(params?: PlayerDefConstructorParams<PublicState, PrivateState>);
}

Player

class Player<MetaData = Record<string, unknown>> {
  get baseData(): BasePlayerWithId;
  get playerId(): number;
  get color(): string;
  get username(): string;
  get avatarUrl(): string | null;

  get notification(): Notification | undefined;
  set notification(value: Notification | undefined);

  get publicState():  Record<string, unknown>;
  set publicState(value: Record<string, unknown>);

  get privateState(): Record<string, unknown> | null;
  set privateState(value: Record<string, unknown> | null);

  get def():    PlayerDef | undefined;
  get static(): Record<string, unknown>;
  get isPrivate(): boolean;

  // Scoped lookups (same signatures as GameState but limited to this player's spaces / pieces)
  piece(...), ensurePiece(...), pieces(...);
  space(...), ensureSpace(...), spaces(...);
  spaceOfKind(...), ensureSpaceOfKind(...), spacesOfKind(...);

  addSpace(spaceId: string, def: SpaceDef): Space<MetaData>;
}

type Notification = {
  title: string;
  intent: "success" | "warning" | "danger" | "info";
  description?: string;
  duration?: number;
};

Top-level state

Property Type Mutable?
currentPlayer Player<MetaData> no (derived from session)
activePlayerIds number[] yes : controls whose turn it is
metaData MetaData (defaults to Record<string, unknown>) yes
scoreLabels string[] | undefined yes
defRegistry DefRegistry no (framework-built)

scoreLabels names each entry of the points arrays your getPlayerScores returns, in order. The labels head the columns of the final scores table that players see at the end of a match and in their Recently played history, and of the score table on the studio Matches page. Studio Stats also uses them for "Points by category", which compares how many points winners and everyone else score in each label. Points are matched to labels by name, so keep a label's name stable across versions to compare it over time. An entry with no label is shown as "Score 1", "Score 2", and so on.

The end-of-match panel shows results and lets players start another match or return to games.
The end-of-match panel shows results and lets players start another match or return to games. Example data from the local development UI. Select the image to enlarge.

Player-scoped lookups

Player has the same piece / pieces / space / spaces / *OfKind methods, scoped to that player's owned spaces and the pieces inside them.

const me = state.currentPlayer;
me.pieces();                                          // pieces in spaces I own, sorted by order
me.pieces("card", (c) => c.isSelected);
me.spaceOfKind("hand");                               // first space I own with kind "hand"
me.ensureSpaceOfKind("hand");                         // throws if missing
me.addSpace("scratch", new GridCell({ x: 0, y: 0 })); // adds + sets playerId

Per-player spaces use synthesized ids like ${playerId}/space/${configKey}, so player.space("hand") (matching spaceId) usually won't hit. Use player.spaceOfKind("hand") instead.

Need a hand with this?

Join our Discord