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 beforesetupruns.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_idis an opaquei64. Compare ids; never compute with them.state.seat_of(id)gives the seat's position, from 0.public_stateis seen by everyone.private_stateis seen only by this seat.botnames the bot that plays this seat, or isNonefor 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_idnames the space. It must be unique.kindgroups spaces of one type.state.spaces_of_kind("hand")iterates them in order.arrangementisOrdered(the default) orCollection. See Hidden information.owner,public_state,private_stateandpieceswork 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_kindnames what every viewer sees, such as"back"or"x-token".private_kindnames what only the piece's viewers see, such as the card's face.owner,public_stateandprivate_statefollow 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
serdethroughboardweaver_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. readpanics 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.