forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TicTacToe tutorial doc (MystenLabs#836)
* Add TicTacToe tutorial * Switch to non-interactive
- Loading branch information
Showing
1 changed file
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
--- | ||
title: An end-to-end tutorial of playing TicTacToe on Sui | ||
--- | ||
|
||
In this tutorial, we will demonstrate an end-to-end process of starting a Sui network locally, collecting to it through our demo wallet app, publish a TicTacToe game on Sui and play it to the end. | ||
|
||
## Start the network | ||
In the Sui repository, build Sui: | ||
``` | ||
$ cargo build | ||
``` | ||
The Sui binary will be in the `target/debug` directory. For the rest of the tutorial, we will stay inside this directory. | ||
First of all, generate a genesis config that the network will use: | ||
``` | ||
$ cd target/debug | ||
./sui genesis | ||
``` | ||
Note that if you have already run the above command once before (perhaps when trying out the wallet), then you can skip it. | ||
It will generate three config files: | ||
- network.conf: contains the config of the authorities. | ||
- wallet.conf: contains the config of a few pre-generated accounts. | ||
- wallet.key: contains the private keys of the accounts in the wallet. | ||
|
||
Now start the network: | ||
``` | ||
$ ./sui start | ||
``` | ||
This command starts the Sui network. It won't print anything, but don't worry it's running. | ||
|
||
## Gather Accounts and Gas Objects | ||
Now switch to a new terminal window (keep the above running). | ||
First take a look at the account addresses we own in our wallet: | ||
``` | ||
$ ./wallet --no-shell addresses | ||
Showing 5 results. | ||
ECF53CE22D1B2FB588573924057E9ADDAD1D8385 | ||
7B61DA6AACED7F28C1187D998955F10464BEAE55 | ||
251CF224B6BA3A019D04B6041357C20490F7A322 | ||
DB4C7667636471AFF396B900EB7B63FACAF629B6 | ||
A6BBB1930E01495EE93CE912EA01C29695E07890 | ||
``` | ||
Note that since these addresses are random generated, they will be different from what you see. We are going to need 3 addresses to play TicTacToe. Let's pick the first 3 addresses. Let's call them ADMIN, PLAYER_X and PLAYER_O. | ||
Since we will be using these addresses and gas objects repeatedly in the rest of this tutorial, let's make them environment variables so that we don't have to retype them every time: | ||
``` | ||
export ADMIN=ECF53CE22D1B2FB588573924057E9ADDAD1D8385 | ||
export PLAYER_X=7B61DA6AACED7F28C1187D998955F10464BEAE55 | ||
export PLAYER_O=251CF224B6BA3A019D04B6041357C20490F7A322 | ||
``` | ||
|
||
For each of these addresses, let's discover their gas objects (replace the addresses with what you see, repeat the following command for these 3 addresses): | ||
``` | ||
$ ./wallet --no-shell gas --address $ADMIN | ||
Object ID | Version | Gas Value | ||
---------------------------------------------------------------------- | ||
38B89FE9F4A4823F1406938E87A8767CBD7F0B93 | 0 | 100000 | ||
4790500A28AB5B4F9A3988E2A5E201D56996CBB0 | 0 | 100000 | ||
6AB7D15F41B28FF1EBF6D32499214BBD9035D1EB | 0 | 100000 | ||
800F2704E22637A036C4325B539D711BB83CA6C2 | 0 | 100000 | ||
D2F52301D5343DD2C1FA076401BC6283C3E4AA34 | 0 | 100000 | ||
$ ./wallet --no-shell gas --address $PLAYER_X | ||
Object ID | Version | Gas Value | ||
---------------------------------------------------------------------- | ||
6F675038CAA48184707DBBE95ACFBA2030E87CD8 | 0 | 100000 | ||
80C91F0B31EFBC1C7BF639A531301AAF3A1D3AB6 | 0 | 100000 | ||
9FED1FC3D21F284DC53DE87C0E19718971D96D8C | 0 | 100000 | ||
E293F935F015C23216867442DB4E712518E7CAB7 | 0 | 100000 | ||
F19384C06AE538F9C3C9D9762002B4DAEA49FE3A | 0 | 100000 | ||
$ ./wallet --no-shell gas --address $PLAYER_O | ||
Object ID | Version | Gas Value | ||
---------------------------------------------------------------------- | ||
2110ADFB7BAF889A05EA6F5889AF7724299F9BED | 0 | 100000 | ||
8C04A5D8D62155B9E90093D6CB300DA304B9E581 | 0 | 100000 | ||
9602B7C0869E7E5AB314FB3D99395A8C640E0E34 | 0 | 100000 | ||
A0DBF58C3801EC2FEDA1D039E190A6B31A25B199 | 0 | 100000 | ||
D5EBB8A19A35874A18B7A1D883EBFC8D897F5693 | 0 | 100000 | ||
``` | ||
We only need one gas object per account address. So let's pick the first gas object of each account. In the above example, it's `38B89FE9F4A4823F1406938E87A8767CBD7F0B93`, `6F675038CAA48184707DBBE95ACFBA2030E87CD8` and `2110ADFB7BAF889A05EA6F5889AF7724299F9BED` respectively. By now, we have 3 account addresses, and for each address, we have one gas object. | ||
Since we will be using these addresses and gas objects repeatedly in the rest of this tutorial, let's make them environment variables so that we don't have to retype them every time: | ||
``` | ||
export ADMIN_GAS=38B89FE9F4A4823F1406938E87A8767CBD7F0B93 | ||
export X_GAS=6F675038CAA48184707DBBE95ACFBA2030E87CD8 | ||
export O_GAS=2110ADFB7BAF889A05EA6F5889AF7724299F9BED | ||
``` | ||
|
||
## Publish the TicTacToe game on Sui | ||
We implemented a TicTacToe game in [TicTacToe.move](../../../sui_programmability/examples/games/sources/TicTacToe.move). To publish the game, we run the publish command and specify the path to the game package: | ||
``` | ||
$ ./wallet --no-shell publish --path ../../sui_programmability/examples/games --gas $ADMIN_GAS --gas-budget 30000 | ||
----- Certificate ---- | ||
Signed Authorities : ... | ||
Transaction Kind : Publish | ||
Gas Budget : 30000 | ||
----- Publish Results ---- | ||
The newly published package object: (A613A7FF8CB03E0DFC0D157E232BBA50C5F19D17, SequenceNumber(1), o#fb70b9d1ac25250a00e35031289f89cd9c9d739f8663e1cc8ed739095a104e68) | ||
List of objects created by running module initializers: [] | ||
Updated Gas : Coin { id: 38B89FE9F4A4823F1406938E87A8767CBD7F0B93, value: 92939 } | ||
``` | ||
As we can see, the package was successfully published. Some gas was charged: the initial gas value was 10000, now it's 92939 (note: the current gas charging mechanism is rather arbitrary, we will come up with a gas mechanism shortly). | ||
The newly published package has the ID `A613A7FF8CB03E0DFC0D157E232BBA50C5F19D17`. Note that this ID will also be different in your terminal. We add the package to another environment variable: | ||
``` | ||
export PACKAGE=A613A7FF8CB03E0DFC0D157E232BBA50C5F19D17 | ||
``` | ||
|
||
## Playing TicTacToe | ||
As we mentioned earlier, we will need 3 parties to participate in this game: Admin, PlayerX and PlayerO. | ||
As a high level, the game works as following: | ||
1. The admin creates a game, which also specifies the addresses or the two players. This will also creates two capability objects and send to each of the addresses to give them permission to participate in the same game. This ensures that arbitrary person cannot attempt to join their game. | ||
2. Each player takes turns to send a Marker object to the admin indicating where they want to place their marker. | ||
3. The admin, upon receiving markers (in practice, this is done through monitoring events), place the marker to the gameboard. | ||
4. (2) and (3) repeats until game ends. | ||
Because the admin owns the gameboard, each individual player cannot place a marker directly to the gameboard (they don't own the object, and hence cannot mutate it, see [Object Model](../build/objects.md)), each marker placement is split to 2 steps, that each player first sends a marker, and then the admin places the marker. A sample gameplay can also be found in the [TicTacToeTests](../../../sui_programmability/examples/games/tests/TicTacToeTests.move). | ||
|
||
Now let's begin the game! | ||
First of all, let's create a game: | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function create_game --args \"0x$PLAYER_X\" \"0x$PLAYER_O\" --gas $ADMIN_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
Signed Authorities : ... | ||
Transaction Kind : Call | ||
Gas Budget : 1000 | ||
Package ID : 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17 | ||
Module : TicTacToe | ||
Function : create_game | ||
Object Arguments : [] | ||
Pure Arguments : [[123, 97, 218, 106, 172, 237, 127, 40, 193, 24, 125, 153, 137, 85, 241, 4, 100, 190, 174, 85], [37, 28, 242, 36, 182, 186, 58, 1, 157, 4, 182, 4, 19, 87, 194, 4, 144, 247, 163, 34]] | ||
Type Arguments : [] | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 284 } | ||
Created Objects: | ||
5851B7EA07B93E68696BC0CF811D2E266DFB880D SequenceNumber(1) o#02b1de02a055a8edb62c7677652f3ff1d92b40202504bd2da50e8d900b266a8e | ||
A6D3B507D4533822E690291166891D42694A2721 SequenceNumber(1) o#930533ef8b324909c65d3586c73a6db1b7ee116704d6bc986a2c3a8f51d8bf10 | ||
F1B8161BD97D3CD6627E739AD675089C5ACFB452 SequenceNumber(1) o#1c92bdf7646cad2a65357fadf60605abc1669dfaa2de0a08709b706ac4b69c8f | ||
Mutated Objects: | ||
38B89FE9F4A4823F1406938E87A8767CBD7F0B93 SequenceNumber(2) o#26dbaf7ec2032a6270a45498ad46ac0b1ddbc361fcff20cadafaf5d39b8181b1 | ||
``` | ||
The above call created 3 objects. For each object, it printed out a tuple of 3 values (object_id, version, object_digest). Object ID is what we care about here. Since we don't have a real application here to display things for us, we need a bit of object printing magic to figure out which object is which. Print out the metadata of each created object (replace the object ID with what you see on your screen): | ||
``` | ||
$ ./wallet --no-shell object --id 5851B7EA07B93E68696BC0CF811D2E266DFB880D | ||
Owner: AddressOwner(k#251cf224b6ba3a019d04b6041357c20490f7a322) | ||
Version: 1 | ||
ID: 5851B7EA07B93E68696BC0CF811D2E266DFB880D | ||
Readonly: false | ||
Type: 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17::TicTacToe::MarkMintCap | ||
$ ./wallet --no-shell object --id A6D3B507D4533822E690291166891D42694A2721 | ||
Owner: AddressOwner(k#7b61da6aaced7f28c1187d998955f10464beae55) | ||
Version: 1 | ||
ID: A6D3B507D4533822E690291166891D42694A2721 | ||
Readonly: false | ||
Type: 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17::TicTacToe::MarkMintCap | ||
$ ./wallet --no-shell object --id F1B8161BD97D3CD6627E739AD675089C5ACFB452 | ||
Owner: AddressOwner(k#ecf53ce22d1b2fb588573924057e9addad1d8385) | ||
Version: 1 | ||
ID: F1B8161BD97D3CD6627E739AD675089C5ACFB452 | ||
Readonly: false | ||
Type: 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17::TicTacToe::TicTacToe | ||
``` | ||
There are two MarkMintCap objects (for capability of minting a Marker for each player) and a TicTacToe object (the game object). Take a look at each of their `Owner` field you will see that: | ||
1. MarkMintCap Object `5851B7EA07B93E68696BC0CF811D2E266DFB880D` is owned by PLAYER_O. | ||
2. MarkMintCap Object `A6D3B507D4533822E690291166891D42694A2721` is owned by PLAYER_X. | ||
3. TicTacToe Object `F1B8161BD97D3CD6627E739AD675089C5ACFB452` is owned by ADMIN. | ||
We add the above 3 object ids to the environment variables: | ||
``` | ||
export XCAP=A6D3B507D4533822E690291166891D42694A2721 | ||
export OCAP=5851B7EA07B93E68696BC0CF811D2E266DFB880D | ||
export GAME=F1B8161BD97D3CD6627E739AD675089C5ACFB452 | ||
``` | ||
|
||
By convention, Player X goes first. Player X wants to put a mark at the center of the gameboard ((1, 1)). This needs to take two steps. First Player X creates a Mark object with the placement intention and send it to the admin. | ||
We will call the `send_mark_to_game` function in `TicTacToe`, whose signature looks like this: | ||
``` | ||
public fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext); | ||
``` | ||
The `cap` argument will be Player X's capability object (XCAP), and `game_address` argument will be the admin's address (ADMIN): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function send_mark_to_game --args \"0x$XCAP\" \"0x$ADMIN\" 1 1 --gas $X_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
Signed Authorities : ... | ||
Transaction Kind : Call | ||
Gas Budget : 1000 | ||
Package ID : 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17 | ||
Module : TicTacToe | ||
Function : send_mark_to_game | ||
Object Arguments : [(A6D3B507D4533822E690291166891D42694A2721, SequenceNumber(1), o#930533ef8b324909c65d3586c73a6db1b7ee116704d6bc986a2c3a8f51d8bf10)] | ||
Pure Arguments : [[236, 245, 60, 226, 45, 27, 47, 181, 136, 87, 57, 36, 5, 126, 154, 221, 173, 29, 131, 133], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0]] | ||
Type Arguments : [] | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 102 } | ||
Created Objects: | ||
AE3CE9176F1A8C1F21D922722486DF667FA00394 SequenceNumber(1) o#d40c0e3c74a2badd60c754456f0b830348bf7df629b0762e8b841c7cab5f4b2e | ||
Mutated Objects: | ||
... | ||
``` | ||
The above call would have created a Mark object, with ID `AE3CE9176F1A8C1F21D922722486DF667FA00394`, and it was sent to the admin. | ||
The admin can now place the mark on the gameboard. The function to place the mark looks like this: | ||
``` | ||
public fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext); | ||
``` | ||
The first argument is the game board, and the second argument is the mark the admin just received from the player. We will call this function (replace the second argument with the Mark object ID above): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function place_mark --args \"0x$GAME\" \"0xAE3CE9176F1A8C1F21D922722486DF667FA00394\" --gas $ADMIN_GAS --gas-budget 1000 | ||
``` | ||
The gameboard now looks like this (this won't be printed out, so keep it in your imagination): | ||
``` | ||
_|_|_ | ||
_|X|_ | ||
| | | ||
``` | ||
|
||
Player O now tries to put a marker at (0, 0) (note, in the second call, the second argument comes from the created objects in the first call): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function send_mark_to_game --args \"0x$OCAP\" \"0x$ADMIN\" 0 0 --gas $O_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 102 } | ||
Created Objects: | ||
7A16D266DAD41145F34649258BC1F744D147BF2F SequenceNumber(1) o#58cb018be98dd828c10f5b2045329f6ec4dab56c5a90e719ad225f0bc195908a | ||
... | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function place_mark --args \"0x$GAME\" \"0x7A16D266DAD41145F34649258BC1F744D147BF2F\" --gas $ADMIN_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 679 } | ||
... | ||
``` | ||
The gameboard now looks like this: | ||
``` | ||
O|_|_ | ||
_|X|_ | ||
| | | ||
``` | ||
|
||
Player X puts a mark at (0, 2): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function send_mark_to_game --args \"0x$XCAP\" \"0x$ADMIN\" 0 2 --gas $X_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 102 } | ||
Created Objects: | ||
2875D50BD9021ED2009A1278C7CB6D4C876FFF6A SequenceNumber(1) o#d4371b72e77bfc07bd088a9113ef7bf870198066649f6c9e9e4abf5f7a7fbd2a | ||
... | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function place_mark --args \"0x$GAME\" \"0x2875D50BD9021ED2009A1278C7CB6D4C876FFF6A\" --gas $ADMIN_GAS --gas-budget 1000 | ||
... | ||
``` | ||
The gameboard now looks like this: | ||
``` | ||
O|_|X | ||
_|X|_ | ||
| | | ||
``` | ||
|
||
Player O places a mark at (1, 0): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function send_mark_to_game --args \"0x$OCAP\" \"0x$ADMIN\" 1 0 --gas $O_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 102 } | ||
Created Objects: | ||
4F7391F172063D87013DD9DC95B8BD45C35FD2D9 SequenceNumber(1) o#ee7ba8ea66e574d7636e159da9f33c96d724d71f141a57a1e4929b2b928c298d | ||
... | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function place_mark --args \"0x$GAME\" \"0x4F7391F172063D87013DD9DC95B8BD45C35FD2D9\" --gas $ADMIN_GAS --gas-budget 1000 | ||
... | ||
``` | ||
The gameboard now looks like: | ||
``` | ||
O|_|X | ||
O|X|_ | ||
| | | ||
``` | ||
This is a chance for Player X to win! X now mints the winning mark at (2, 0): | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function send_mark_to_game --args \"0x$XCAP\" \"0x$ADMIN\" 2 0 --gas $X_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 102 } | ||
Created Objects: | ||
AA7A6624E16E5E447801462FF6614013FC4AD156 SequenceNumber(1) o#e5e1b15f03531db118efaa9667244b876f32e7ad2cc17bdbc7d4cb1eaca1560d | ||
... | ||
``` | ||
And then finally the admin places the winning mark: | ||
``` | ||
$ ./wallet --no-shell call --package $PACKAGE --module TicTacToe --function place_mark --args \"0x$GAME\" \"0xAA7A6624E16E5E447801462FF6614013FC4AD156\" --gas $ADMIN_GAS --gas-budget 1000 | ||
----- Certificate ---- | ||
... | ||
----- Transaction Effects ---- | ||
Status : Success { gas_used: 870 } | ||
Created Objects: | ||
54B58C0D5B14A269B1CD424B3CCAB1E315C43343 SequenceNumber(1) o#7a093db738f6708c33d264d023c0eb07bcd9d22f038dbdcf1cbfdad50b0c1e42 | ||
Mutated Objects: | ||
... | ||
``` | ||
As we can see, the last transaction created a new object. Let's find out what object was created: | ||
``` | ||
$ ./wallet --no-shell object --id 54B58C0D5B14A269B1CD424B3CCAB1E315C43343 | ||
Owner: AddressOwner(k#7b61da6aaced7f28c1187d998955f10464beae55) | ||
Version: 1 | ||
ID: 54B58C0D5B14A269B1CD424B3CCAB1E315C43343 | ||
Readonly: false | ||
Type: 0xa613a7ff8cb03e0dfc0d157e232bba50c5f19d17::TicTacToe::Trophy | ||
``` | ||
PlayerX has received a Trophy object, and hence won the game! | ||
|
||
This concludes the tutorial. |