Detailed references

v3 · Preview

Framework v3 is an opt-in preview available to everyone. New games use the stable framework by default.

The game functions

A game is a type that implements the Game trait. Write all five functions. They take no self, so the game keeps nothing between calls: everything lives in the state.

pub trait Game {
    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<(), Message>;
    fn scores(table: &Table) -> Scores;
    fn is_game_over(table: &Table, scores: &Scores) -> bool;
}

Write pub struct MyGame;, then impl Game for MyGame { ... } with the five functions, then export_game! (below). Every new game starts as Hexbloom, a complete game. To read its game functions, call get_example with name: "hexbloom" and path: "/src/lib.rs".

setup

Builds a new match: add spaces and pieces, write maps, shuffle and deal, and choose who starts with set_active_players. Write the opening's log lines with table.log when players should see them.

  • Boardweaver seats players in seat order before setup runs. Every seat is active until setup calls set_active_players.
  • setup cannot refuse. Make it work for every seat count the game allows.

offers

Returns every offer player_id may take now, each with its intent and label. See Offers, intents and decisions.

  • Boardweaver calls it only for seats in active_player_ids, and never once is_game_over returns true.
  • 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 step. Before the call, Boardweaver checks that the game offered this action to this seat, and that a command's value fits its input. Handle only the actions your offers returns.

  • Return Ok(()) after the change.
  • Return Err(reason) to refuse a step by a rule the offer cannot express, such as a city name another seat already took. Boardweaver discards every change the call made.
  • Write reason as a message from the game's catalogs: Err(msg!("refusal.city-taken", city = name)). The player sees it in their language.
  • A reason follows the rules of a label (Offers): it has no {player}, and every seat it names must be seated. Otherwise the call panics.
  • Narrow the offers instead of refusing wherever you can.
  • A panic is a bug. Boardweaver fails the step and records a match error (Builds, limits and errors).

Read a command's value with action.text() or action.integer(). Match a command by its name:

match action {
    Action::Command { name, .. } if name == "pass" => {
        table.log(msg!("log.passes"));
    }
    Action::PieceClick { space_id, index } => {
        table.move_piece(space_id, *index, "discard");
    }
    _ => {}
}

scores

Returns every seat's score. Boardweaver calls it after every step.

pub struct Score {
    pub private_points: Vec<f64>,
    pub public_points: Vec<f64>,
}
  • Column i of a seat's score is public_points[i] + private_points[i]. A missing column counts as 0. Name each column in the client: see Scores.
  • Seats rank by the first column, highest first. The second column breaks a tie on the first, and so on.
  • A seat sees every seat's public points and only its own private points. Use private points for a score other seats must not see yet, such as a hidden objective.

is_game_over

Returns whether the match has ended. It receives the scores that scores just returned. Once it returns true, Boardweaver offers nothing to any seat and refuses every later step as NotOffered.

export_game!

The last line of /src/lib.rs exports the game: boardweaver_sdk::export_game!(MyGame);. To ship bots, list them: export_game!(MyGame, bots: [Greedy, Careful]) (Bots). For a game without bots, leave the list out: the build refuses an empty list, bots: [].

Need a hand with this?

Join our Discord