Walking through tic-tac-toe
Open crates/dg-games/src/tictactoe.rs. It is a complete game with a stake, invitations, a timeout and tests.
The ops
kind | Fields | Who |
|---|---|---|
create | stake?, opponent? | anyone |
join | id | anyone but x, or only the invited opponent |
move | id, cell (0..=8) | the player whose turn it is |
cancel | id | x, while the game is open |
claim_timeout | id | the player waiting, after 150 blocks |
apply is just a dispatch on kind:
match op_str(op, "kind")? {
"create" => create(ctx, sender, op),
"join" => join(ctx, sender, op),
"move" => play(ctx, sender, op),
...
other => Err(ActionError::invalid(format!("unknown tictactoe op `{other}`"))),
}
create
- Parse the optional
stake(a decimal string of GOLD base units) and refuse more thanMAX_STAKE(100000, which is 1000 GOLD). - The game id is the first 16 hex characters of the transaction's txid, so it is unique and needs no counter. If that id already exists the op fails with a conflict.
escrow_intakes the creator's stake intomodule:tictactoe.- Write
game/<id>,open/<id>andplayer/<address>/<id>, then emittictactoe.created.
join and move
join checks the game is open, that the joiner is not the creator, and that an invited game is only joined
by the invited address. It escrows an equal stake and changes the status to playing.
play checks, in order: the game is in play, the sender is a player (unauthorized otherwise), it is their
turn, the cell is a JSON number from 0 to 8, and the cell is empty. Then it places the mark, emits
tictactoe.moved, and checks the eight LINES for a win or a full board.
finish and the timeout
finish pays out: the winner gets 2 * stake via escrow_out; a draw refunds each player. If a player stops
moving, the other can call claim_timeout once ctx.height() is at least last_move_height + 150. The
module never looks at a clock, only at the block height, so the result is the same on every replay.