Skip to content

Commit

Permalink
Using plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ronny-mysten committed Jul 10, 2024
1 parent 4631f8e commit 7ae1c5d
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 24 deletions.
53 changes: 46 additions & 7 deletions docs/content/guides/developer/app-examples/tic-tac-toe.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This guide covers three different implementations of the game tic-tac-toe on Sui

In this first example of tic-tac-toe, the game object, including the game board, is controlled by a game admin.

{@inject: examples/tic-tac-toe/move/sources/owned.move#struct=Game noComments}
```move
public struct Game has key, store {
id: UID,
Expand All @@ -21,6 +22,7 @@ public struct Game has key, store {

Because the players don’t own the game board, they cannot directly mutate it. Instead, they indicate their move by creating a `Mark` object with their intended placement and send it to the game object using transfer to object:

{@inject: examples/tic-tac-toe/move/sources/owned.move#struct=Mark noComments}
```move
public struct Mark has key, store {
id: UID,
Expand All @@ -32,6 +34,7 @@ public struct Mark has key, store {

Games are created with the `new` function:

{@inject: examples/tic-tac-toe/move/sources/owned.move#fun=new noComments}
```move
public fun new(x: address, o: address, admin: vector<u8>, ctx: &mut TxContext): Game {
let game = Game {
Expand Down Expand Up @@ -66,6 +69,7 @@ Some things to note:

When playing the game, the admin operates a service that keeps track of marks using events. When a request is received (`send_mark`), the admin tries to place the marker on the board (`place_mark`). Each move requires two steps (thus two transactions): one from the player and one from the admin. This setup relies on the admin's service to keep the game moving.

{@inject: examples/tic-tac-toe/move/sources/owned.move#fun=send_mark,place_mark}
```move
public fun send_mark(cap: TurnCap, row: u8, col: u8, ctx: &mut TxContext) {
assert!(row < 3 && col < 3, EInvalidLocation);
Expand Down Expand Up @@ -132,12 +136,21 @@ To view the entire source code, see the [owned.move source file](https://github.

An alternative version of this game, shared tic-tac-toe, uses shared objects for a more straightforward implementation that doesn't use a centralized service. This comes at a slightly increased cost, as using shared objects is more expensive than transactions involving wholly owned objects.

<details>
<summary>
Toggle full source code
</summary>
{@inject: examples/tic-tac-toe/move/sources/owned.move}
</details>
([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/move/sources/owned.move))

## shared.move

In the previous version, the admin owned the game object, preventing players from directly changing the gameboard, as well as requiring two transactions for each marker placement. In this version, the game object is a shared object, allowing both players to access and modify it directly, enabling them to place markers in just one transaction. However, using a shared object generally incurs extra costs because Sui needs to sequence the operations from different transactions. In the context of this game, where players are expected to take turns, this shouldn't significantly impact performance. Overall, this shared object approach simplifies the implementation compared to the previous method.

As the following code demonstrates, the `Game` object in this example is almost identical to the one before it. The only differences are that it does not include an `admin` field, which is only relevant for the multisig version of the game, and it does not have `store`, because it only ever exists as a shared object (so it cannot be transferred or wrapped).

{@inject: examples/tic-tac-toe/move/sources/shared.move#struct=Game noComments}
```move
public struct Game has key {
id: UID,
Expand All @@ -150,6 +163,7 @@ public struct Game has key {

Take a look at the `new` function:

{@inject: examples/tic-tac-toe/move/sources/shared.move#fun=new noComments}
```move
public fun new(x: address, o: address, ctx: &mut TxContext) {
transfer::share_object(Game {
Expand All @@ -169,6 +183,7 @@ public fun new(x: address, o: address, ctx: &mut TxContext) {

Instead of the game being sent to the game admin, it is instantiated as a shared object. The other notable difference is that there is no need to mint a `TurnCap` because the only two addresses that can play this game are `x` and `o`, and this is checked in the next function, `place_mark`:

{@inject: examples/tic-tac-toe/move/sources/shared.move#fun=place_mark}
```move
public fun place_mark(
game: &mut Game,
Expand Down Expand Up @@ -203,7 +218,13 @@ public fun place_mark(
}
```

You can find the full source code in [shared.move](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/move/sources/shared.move)
<details>
<summary>
Toggle full source code
</summary>
{@inject: examples/tic-tac-toe/move/sources/shared.move}
</details>
([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/move/sources/shared.move))

## Multisig

Expand All @@ -215,7 +236,7 @@ In this implementation of the game, the game is in a 1-of-2 multisig account tha

A multisig account is defined by the public keys of its constituent keypairs, their relative weights, and the threshold -- a signature is valid if the sum of weights of constituent keys having signed the signature exceeds the threshold. In our case, there are at most two constituent keypairs, they each have a weight of 1 and the threshold is also 1. A multisig cannot mention the same public key twice, so keys are deduplicated before the multisig is formed to deal with the case where a player is playing themselves:

```typescript
```typescript title="examples/tic-tac-toe/ui/src/MultiSig.ts"
export function multiSigPublicKey(keys: PublicKey[]): MultiSigPublicKey {
const deduplicated: { [key: string]: PublicKey } = {};
for (const key of keys) {
Expand All @@ -231,13 +252,19 @@ export function multiSigPublicKey(keys: PublicKey[]): MultiSigPublicKey {
}
```

([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/MultiSig.ts)).
<details>
<summary>
Toggle full source code
</summary>
{@inject: examples/tic-tac-toe/ui/src/MultiSig.ts}
</details>
([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/MultiSig.ts))

Note that an address on Sui can be derived from a public key (this fact is used in the previous example to deduplicate public keys based on their accompanying address), but the opposite is not true. This means that to start a game of multisig tic-tac-toe, players must exchange public keys, instead of addresses.

When creating a multisig game, we make use of `owned::Game`'s `admin` field to store the multisig public key for the admin account. Later, it will be used to form the signature for the second transaction in the move. This does not need to be stored on-chain, but we are doing so for convenience so that when we fetch the `Game`'s contents, we get the public key as well:

```typescript
```typescript title="examples/tic-tac-toe/ui/src/hooks/useTransactions.ts"
newMultiSigGame(player: PublicKey, opponent: PublicKey): Transaction {
const admin = multiSigPublicKey([player, opponent]);
const tx = new Transaction();
Expand All @@ -257,13 +284,19 @@ newMultiSigGame(player: PublicKey, opponent: PublicKey): Transaction {
}
```

([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/hooks/useTransactions.ts)).
<details>
<summary>
Toggle full source code
</summary>
{@inject: examples/tic-tac-toe/ui/src/hooks/useTransactions.ts}
</details>
([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/hooks/useTransactions.ts))

### Placing a mark

Placing a mark requires two transactions, just like the owned example, but they are both driven by one of the players. The first transaction is executed by the player as themselves, to send the mark to the game, and the second is executed by the player acting as the admin to place the mark they just sent. In the React frontend, this is performed as follows:

```typescript
```typescript title="examples/tic-tac-toe/ui/src/pages/Game.tsx"
function OwnedGame({
game,
trophy,
Expand Down Expand Up @@ -328,7 +361,13 @@ function OwnedGame({
}
```

([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/pages/Game.tsx)).
<details>
<summary>
Toggle full source code
</summary>
{@inject: examples/tic-tac-toe/ui/src/pages/Game.tsx}
</details>
([Source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/pages/Game.tsx))

The first step is to get the multisig public key, which was written to `Game.admin` earlier. Then two executor hooks are created: The first is to sign and execute as the current player, and the second is to sign and execute as the multisig/admin account. After the wallet has serialized and signed the transaction the second executor creates a multisig from the wallet signature and executes the transaction with two signatures: Authorizing on behalf of the multisig and the wallet.

Expand Down
18 changes: 9 additions & 9 deletions examples/tic-tac-toe/move/sources/owned.move
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ module tic_tac_toe::owned {
/// The state of an active game of tic-tac-toe.
public struct Game has key, store {
id: UID,

///
/// Marks on the board.
board: vector<u8>,

///
/// The next turn to be played.
turn: u8,

///
/// The address expected to send moves on behalf of X.
x: address,

///
/// The address expected to send moves on behalf of O.
o: address,

///
/// Public key of the admin address.
admin: vector<u8>,
}
Expand All @@ -62,16 +62,16 @@ module tic_tac_toe::owned {
/// is one, or to both players in the case of a draw.
public struct Trophy has key {
id: UID,

///
/// Whether the game was won or drawn.
status: u8,

///
/// The state of the board at the end of the game.
board: vector<u8>,

///
/// The number of turns played
turn: u8,

///
/// The other player (relative to the player who owns this Trophy).
other: address,
}
Expand Down
16 changes: 8 additions & 8 deletions examples/tic-tac-toe/move/sources/shared.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ module tic_tac_toe::shared {
/// The state of an active game of tic-tac-toe.
public struct Game has key {
id: UID,

///
/// Marks on the board.
board: vector<u8>,

///
/// The next turn to be played.
turn: u8,

///
/// The address expected to send moves on behalf of X.
x: address,

///
/// The address expected to send moves on behalf of O.
o: address,
}
Expand All @@ -33,16 +33,16 @@ module tic_tac_toe::shared {
/// is one, or to both players in the case of a draw.
public struct Trophy has key {
id: UID,

///
/// Whether the game was won or drawn.
status: u8,

///
/// The state of the board at the end of the game.
board: vector<u8>,

///
/// The number of turns played
turn: u8,

///
/// The other player (relative to the player who owns this Trophy).
other: address,
}
Expand Down

0 comments on commit 7ae1c5d

Please sign in to comment.