The game functions
A game is a type that implements the Game trait. A trait is Rust's name for an interface: a list of functions a type must provide. The game writes all six functions. They take no self, so the game keeps nothing between calls: everything lives in the state.
pub trait Game {
fn score_labels(players: &[PlayerId]) -> Vec<String>;
fn setup(table: &mut Table, players: &[PlayerId], rng: &mut Rng);
fn offers(table: &Table, player_id: PlayerId) -> Vec<Offer>;
fn apply_action(
table: &mut Table,
player_id: PlayerId,
action: &Action,
rng: &mut Rng,
) -> Result<(), String>;
fn scores(table: &Table) -> Scores;
fn is_game_over(table: &Table, scores: &Scores) -> bool;
}
The game writes pub struct MyGame;, then impl Game for MyGame { ... } with the six functions, then export_game! (below). For a complete game, read the tic-tac-toe seed with get_example, name tic-tac-toe-rust.
setup
Builds a new match: add spaces and pieces, write maps, shuffle and deal, choose who starts with set_active_players, and optionally write the opening's log lines with table.log.
- The platform guarantees:
playersare seated in seat order, and all of them are active untilsetupsays otherwise. setupcannot refuse. It must succeed for every seat count the game allows.
offers
Every legal move for player_id now, each with its intent and label. See Offers, intents and decisions.
- The platform calls it only for seats in
active_player_ids, and never after the game is over. - It also runs on the seat's view, where hidden parts are missing (Hidden information).
- Return an empty list when the seat has nothing to do.
apply_action
Applies one move. Before the call, the platform has checked that the game offered this action to this seat and that a command's value fits its input. So the game logic handles only moves it offered.
- Return
Ok(())after the change. - Return
Err(reason)to refuse a move by a rule the offer could not express, such as a city name another seat already took. The player sees the reason, and every change of the call is undone. Prefer narrowing the offers to refusing. - A panic is a bug. It fails the move and is reported as a match error.
Read a command's value with action.text() or action.integer(). Match a command by name:
match action {
Action::Command { name, .. } if name == "pass" => {
table.log("passes");
}
Action::PieceClick { space_id, index } => {
table.move_piece(space_id, *index, "discard");
}
_ => {}
}
scores and score_labels
scores returns every seat's score after every move. score_labels names the score columns once, for the whole match.
pub struct Score {
pub public_points: Vec<f64>,
pub private_points: Vec<f64>,
}
- Column
iof a seat's score ispublic_points[i] + private_points[i].score_labels()[i]names columni. - Seats rank by the first column, highest first. The second column breaks a tie on the first, and so on.
- During play, every seat sees each other seat's public points and only its own private points. Use private points for scores a seat must not see yet, such as hidden objectives.
is_game_over
Returns whether the match has ended. It receives the scores scores just computed. Once it returns true, nobody is offered anything and every later move is refused.
export_game!
The last line of /src/lib.rs exports the game: boardweaver_sdk::export_game!(MyGame);. A game with bots lists them: export_game!(MyGame, bots: [Greedy, Careful]). See Bots. An empty list, bots: [], does not compile: leave the list out instead.