Have more questions? Join our Discord

The state

A match's state is one State value. The game logic reads it with table.state() and changes it only through Table (Changing the state). The client receives each seat's view of it as JSON (The React client).

pub struct State {
    pub active_player_ids: Vec<PlayerId>,
    pub players: Vec<Player>,
    pub spaces: Vec<Space>,
    pub meta_data: Map<String, Value>,
}
  • active_player_ids: the seats that may move now. Only these seats get offers. Several active seats move at the same time.
  • players: every seat, in seat order. The platform seats them before setup runs.
  • spaces: every area that holds pieces, in the order the game added them.
  • meta_data: data about the whole match, such as the round number. Every viewer sees it.

Players

pub struct Player {
    pub player_id: PlayerId,
    pub public_state: Map<String, Value>,
    pub private_state: Map<String, Value>,
    pub bot: Option<String>,
}
  • player_id is an opaque i64. Compare ids; never compute with them. state.seat_of(id) gives the seat's position, from 0.
  • public_state is seen by everyone. private_state is seen only by this seat.
  • bot names the bot that plays this seat, or is None for a person.
  • Names and avatars are not in the state. The client reads them with usePlayers.

Spaces

use boardweaver_sdk::serde_json::json;

Space::new("hand-12", "hand")
    .collection()
    .owned_by(player_id)
    .with_public("label", json!("Hand"))
    .with_pieces(pieces)

Space::new(space_id, kind) makes an ordered space nobody owns. Each builder method sets one field:

  • space_id names the space. It must be unique.
  • kind groups spaces of one type. state.spaces_of_kind("hand") iterates them in order.
  • arrangement is Ordered (the default) or Collection. See Hidden information.
  • owner, public_state, private_state and pieces work as on a piece, below.

Pieces

Piece::new("back")
    .hiding("queen-of-hearts")
    .owned_by(player_id)
    .with_public("damage", json!(2))
    .with_private("bonus", json!(5))
  • public_kind names what every viewer sees, such as "back" or "x-token".
  • private_kind names what only the piece's viewers see, such as the card's face.
  • owner, public_state and private_state follow the visibility rule in Hidden information.

A piece has no id. A piece is named by its space and its index in that space's pieces. An index is good only until the next change to that space: when a piece leaves, the pieces after it move down. Read the index again after each change, or use the index a Table method returns.

Reading the state

Method Answers When the item is missing
state.space(id) &Space panics
state.find_space(id) Option<&Space> None
state.space_index(id) position in spaces panics
state.piece(space_id, index) &Piece panics
state.player(id) &Player panics
state.seat_of(id) seat position, from 0 panics
state.spaces_of_kind(kind) iterator of &Space empty

A panic in the game logic is a bug and fails the move. Use find_space where a space may be absent.

Your own types in maps

Every map (public_state, private_state, meta_data) holds JSON. Read and write it as your own serde type, named by StateOf:

StateOf Map
PlayerPublic(id), PlayerPrivate(id) a player's maps
SpacePublic(space_id), SpacePrivate(space_id) a space's maps
PiecePublic(space_id, index), PiecePrivate(space_id, index) a piece's maps
Meta meta_data
use boardweaver_sdk::serde::{Deserialize, Serialize};

#[derive(Default, Serialize, Deserialize)]
#[serde(crate = "boardweaver_sdk::serde", default)]
struct Tally {
    played: u32,
    taken: u32,
}

let tally: Tally = table.state().read(StateOf::PlayerPublic(player_id));
table.update(StateOf::PlayerPublic(player_id), |tally: &mut Tally| tally.played += 1);
  • Use serde through boardweaver_sdk::serde, with #[serde(crate = "boardweaver_sdk::serde")].
  • Give every field a default (#[serde(default)] on the struct). An empty map reads as {}, so the type then reads its default from a map nothing wrote yet, and from a private map the viewer cannot see.
  • read panics when the map does not fit the type.
  • Keep data for a kind of piece, such as a card's cost, in Rust constants keyed by its kind. Put in the state only what changes during the match.

In the client

The client receives the state as JSON in camelCase: activePlayerIds, metaData, spaceId, publicKind, privateKind, publicState, privateState. A map that is empty is left out.

Define a type a map holds inside client_types!, and the client parses the map with its schema: see Client types.

Limit

The state may take at most 1 MiB as compact JSON (MAX_STATE_JSON_BYTES). A move that grows it past that fails.