Divi Games

Courses / Build a game

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

kindFieldsWho
createstake?, opponent?anyone
joinidanyone but x, or only the invited opponent
moveid, cell (0..=8)the player whose turn it is
cancelidx, while the game is open
claim_timeoutidthe 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

  1. Parse the optional stake (a decimal string of GOLD base units) and refuse more than MAX_STAKE (100000, which is 1000 GOLD).
  2. 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.
  3. escrow_in takes the creator's stake into module:tictactoe.
  4. Write game/<id>, open/<id> and player/<address>/<id>, then emit tictactoe.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.