Offers, intents and decisions
The offers game function returns the seat's offers: every legal move now. The client draws only these, Boardweaver accepts only these, and bots choose only among these. Each offer carries an intent, and the intent decides how the move flows. Tagging intents right is the most important rule of a game.
Actions
pub enum Action {
SpaceClick { space_id: String },
PieceClick { space_id: String, index: usize },
Command { name: String, value: Option<InputValue> },
}
SpaceClickandPieceClickare clicks on the board. A piece click names the piece by its index in the state the game logic receives.- A
Commandis every other move, such as passing or naming a city. The game names it. The client draws its button. Make one withAction::command("pass"), and match it withAction::Command { name, .. }.
Offers
Offer::new(Action::command("pass"), Intent::Confirm, "Pass")
labelsays what the move does in words. Bots and logs read it, and a client may show it on a command's button. Only the seat that receives the offer sees it..revealing()marks a reveal (Hidden information).- The same action may be offered more than once with different inputs. A move matches if it fits any of them.
Command inputs
A command may ask for one value:
Offer::command_with(
"name-city",
Input::Text { max_len: 24, suggestions: vec!["Aldmoor".into(), "Brennick".into()] },
Intent::Choice,
"Name your city",
)
Offer::command_with(
"bid",
Input::Integer { min: 0, max: coins, step: Some(5) },
Intent::Confirm,
"Bid coins",
)
Texttakes 1 tomax_lencharacters. It needs at least one suggestion, because bots give only suggestions.Integertakesmintomax, both included, and whenstepis set, onlyminplus a multiple ofstep.command_withpanics on an input nobody can give, such asminabovemax.- Read the value in
apply_actionwithaction.text()oraction.integer(). The platform has already checked that it fits.
A move that needs several values is several steps, one value each, so each step can narrow the next. "Give 3 wood to Ana" is three steps: a choice of Ana, a choice of wood, then a confirm of the amount whose max is the wood the seat holds.
Intents
| Intent | Means | Bots |
|---|---|---|
Choice |
Adds new information to the open decision and moves it forward. | take it |
Confirm |
Ends the decision and sends it. | take it |
Cancel |
Changes or takes back something already chosen in the open decision. | ignore it |
Two rules follow, and a game must keep both:
- Every state inside an open decision offers at least one
ChoiceorConfirm. A bot ignoresCancel, so it must always have something else to take. - A
Choicenever returns the decision to a state it had before. A bot taking onlyChoiceandConfirmmust always reach the end.
Worked example: a draft of 12 cards
The seat stages one card, may switch to another, and then takes the staged card.
- Nothing staged: each of the 12 cards is a
Choice. - One card staged: the other 11 cards are still offered, so the seat can switch, but as
Cancel. Thetakecommand is aConfirm.
#[derive(Default, Serialize, Deserialize)]
#[serde(crate = "boardweaver_sdk::serde", default)]
struct Pick {
staged: Option<usize>,
}
fn offers(table: &Table, player_id: PlayerId) -> Vec<Offer> {
let pick: Pick = table.state().read(StateOf::PlayerPrivate(player_id));
let switch = if pick.staged.is_some() { Intent::Cancel } else { Intent::Choice };
let mut offers: Vec<Offer> = (0..table.state().space("market").pieces.len())
.filter(|&index| pick.staged != Some(index))
.map(|index| {
Offer::new(
Action::PieceClick { space_id: "market".into(), index },
switch,
format!("Stage card {index}"),
)
})
.collect();
if pick.staged.is_some() {
offers.push(Offer::new(Action::command("take"), Intent::Confirm, "Take the staged card"));
}
offers
}
The common mistake is to tag the 11 switches as Choice. Then staging card A, then card B, then card A again returns the decision to a state it had, and a bot can switch between cards forever.
What ends a decision
A decision stays open, as the seat's draft, until a step ends it. The first rule that applies decides:
- The step drew from
Rng: the decision ends, whatever the step's intent. - The step is a reveal: the decision ends.
- The step's intent is
Confirm: the decision ends. - Otherwise the decision stays open.
So only the last step of a decision can draw randomness or reveal. Plan multi-step moves so the random or revealing part comes last.
Undo
Undo is not an intent and not an action: it is the platform's, and the client shows its button. The game logic allows it with table.allow_undo() (Changing the state).