Sui Move 双手石头剪刀布

  • Nigdle
  • 更新于 2024-04-02 17:48
  • 阅读 136

双手同时给出选择(石头剪刀布),对方同样也给出自己的选择,这个时候出拳情况是透明的(玩家可见),经过分析后选择自己的左手或者右手作为最终的选择,和对方的抉择进行比较,如果玩家赢了,那么押金......

一:游戏简介

双手同时给出选择(石头剪刀布),对方同样也给出自己的选择,这个时候出拳情况是透明的(玩家可见),经过分析后选择自己的左手或者右手作为最终的选择,和对方的抉择进行比较,如果玩家赢了,那么押金就原封不动退回,如果玩家输了(平局也算输),押金就归对方(也就是此游戏的发布者)所有。

二:结构设计

2.1 Vector

首先考虑的是双方出手情况该如何存储,当然可以 $\mathit {user_left},\ \mathit {user_right},\ \mathit {robot_left},\ \mathit {robot_right}$ 这样子存储多个 $\mathit {String}$,但如果后面遇到了更多的选择呢?也一个一个列举吗?答案是否定的,这里就引入一个动态数组 $\mathit {Vector}$ 的概念。

它是一个泛型类型,这意味着它可以保存任何类型的数据,但一个 $\mathit {Vector}$ 当中的数据类型是统一。你不需要像某些编程语言一样,在一开始声明数组容量,因为它是动态的,可以根据情况增加或减少,只是,当存储数据过多的时候,可能会导致各项相关的操作速度变慢。

也就是说,我们只需要一个 $\mathit {Vector}$<$\mathit {String}$>,就可以存储多个字符串类型的数据,那么如何区分 $\mathit {user}$ 和 $\mathit {robot}$ 呢?

2.2 Table

一个最简单的方式当然就是在编写代码的时候人为固定规则,例如 $\mathit {Vector}$ 中下标为 $\text 0$ 的数据表示 $\mathit {user_left}$,下标为 $\text 1$ 的数据表示 $\mathit {user_right}$,以此类推,但还是那个问题,如果后面遇到的情况更为复杂呢?这样显然不现实。

$\mathit {Table}$ 也是 $\mathit {Move}$ 当中的一个动态数据结构,简单来说就是一张键-值映射表,可以通过 $\mathit {Key}$ 来找到其对应的 $\mathit {Value}$。<br>这里有一个限制,$\mathit {Key}$ 必须具备 $\mathit {copy\ +\ drop\ +\ store}$ 能力,而 $\mathit {Value}$ 必须具备 $\mathit {store}$ 能力,因为 $\mathit {Table}$ 当中的键往往需要被复制出来使用,而值则往往需要被存储在对象当中。<br>一般而言,$\mathit {Move}$ 语言自带的数据类型大部分都可以直接作为键或者值使用,如果有自定义结构的需要,那么不要忘记上述规则。

回到刚刚的问题,只需要将 $\mathit {Table}$ 和 $\mathit {Vector}$ 简单结合就可以用来存储我们所需要的数据:Table&lt;String, vector&lt;String>>

2.3 押金

$\mathit {Sui}\ \mathit {Move}$ 提供了一套完善的资金服务,具体可见 $\mathit {Coin}$$\mathit {Balance}$

$\mathit {Coin}$ 就像是一个钱包,而 $\mathit {Balance}$ 则是该钱包当中的现金余额,后者无法单独存在,必须被保存在某个地方,前者虽然也有 $\mathit {store}$ 能力,理论上讲也可以被存储到自定义的结构当中,但一般不这么做,而是将其中的余额取出存储在自定义的对象当中来进行后续操作。

调用对应的函数方法,就可以将余额从一个地方转移到另一个地方,如果想要将其支付给某个人,只需要将其包装成 $\mathit {Coin}$(也有对应的函数可直接调用)再将其所有权转移到对应地址即可,就像 $\mathit {transfer}$ 一个 $\mathit {Hello\ World}$ 一样简单。

结合上述三点,可以定义如下结构:

struct Game has key {
    id: UID,
    balance: Balance&lt;SUI>,
    hands: Table&lt;String, vector&lt;String>>,
}

2.4 发布者

我们还需要一个能够存储游戏发布者地址的结构,它应该在一开始就被定义,所有人可见但无法被更改,某种程度上就像是执行最后一步(猜拳最终结果)的权杖所在。

struct GameCap has key {
    id: UID,
    creator: address,
}

fun init(ctx: &mut TxContext) {
    transfer::freeze_object(GameCap {
        id: object::new(ctx),
        creator: tx_context::sender(ctx),
    });
}

三:代码实现

3.1 创建一局游戏

玩家缴纳押金,给出自己双手究竟出的是石头、剪刀还是布,同时传入0x6代表clock所在的地址用作截取当前时间戳,以此来决定对方出手。<br>每个 $\mathit {Vector}$ 由于只有两个值,所以在定义的时候直接通过vector[sth1, sth2, ...]的方式赋值,而 $\mathit {Table}$ 则是先新建一个空的结构table::new&lt;String, vector&lt;String>>(ctx)再通过table::add(&mut table, key, val)的形式添加。

const ENOTBALANCE: u64 = 0;
const ENOTCORRECTHANDS: u64 = 1;

const ROCK: vector&lt;u8> = b"rock";
const PAPER: vector&lt;u8> = b"paper";
const SCISSOR: vector&lt;u8> = b"scissor";

fun check(hand: vector&lt;u8>): bool {
    hand == ROCK || hand == PAPER || hand == SCISSOR
}

fun hand_to_number(hand: vector&lt;u8>): u64 {
    if (hand == ROCK)
        0
    else if (hand == PAPER)
        1
    else
        2
}

fun number_to_hand(number: u64): vector&lt;u8> {
    if (number == 0)
        ROCK
    else if (number == 1)
        PAPER
    else
        SCISSOR
}

entry fun create_game(left_hand: vector&lt;u8>, right_hand: vector&lt;u8>, coin: Coin&lt;SUI>, clock: &Clock, ctx: &mut TxContext) {
    assert!(coin::value(&coin) > 0, ENOTBALANCE);
    assert!(check(left_hand) && check(right_hand), ENOTCORRECTHANDS);

    let robot_left_hand = number_to_hand(clock::timestamp_ms(clock) % 3);
    let robot_right_hand = number_to_hand((hand_to_number(left_hand) + hand_to_number(right_hand) + hand_to_number(robot_left_hand)) % 3);

    let hands = table::new&lt;String, vector&lt;String>>(ctx);
    let user_hands = vector[string::utf8(left_hand), string::utf8(right_hand)];
    let robot_hands = vector[string::utf8(robot_left_hand), string::utf8(robot_right_hand)];
    table::add(&mut hands, string::utf8(b"user"), user_hands);
    table::add(&mut hands, string::utf8(b"robot"), robot_hands);

    let game = Game {
        id: object::new(ctx),
        balance: coin::into_balance(coin),
        hands,
    };
    transfer::transfer(game, tx_context::sender(ctx));
}

3.2 选择左右手

创建完游戏后可以查看 $\mathit {Game\ Object}$ 当中双方的出手情况,经过头脑风暴决定自己最后是出左手还是右手,这里还需要传入 $\mathit {GameCap}$ 来确保玩家输了之后押金可以发送给游戏发布者,同样的,对方的出手依旧由时间戳来决定,最后,不要忘记把需要手动 $\mathit {drop}$ 的操作一下。<br>$\mathit {Vector}$ 和 $\mathit {Table}$ 的取值需要用到各自的 $\mathit {borrow}$ 函数,只不过前者用的是下标$\mathit {(index)}$,后者用的是键$\mathit {(Key)}$。

const ENOTCORRECTCHOOSE: u64 = 2;

const LEFT: vector&lt;u8> = b"left";
const RIGHT: vector&lt;u8> = b"right";

fun choose_to_number(hand: vector&lt;u8>): u64 {
    if (hand == LEFT)
        0
    else
        1
}

fun user_win(user_hand: String, robot_hand: String): bool {
    let rock = string::utf8(ROCK);
    let paper = string::utf8(PAPER);
    let scissor = string::utf8(SCISSOR);
    user_hand == rock && robot_hand == scissor || user_hand == scissor && robot_hand == paper || user_hand == paper && robot_hand == rock
}

entry fun choose_hand(game_cap: &GameCap, game: Game, hand: vector&lt;u8>, clock: &Clock, ctx: &mut TxContext) {
    assert!(hand == LEFT || hand == RIGHT, ENOTCORRECTCHOOSE);

    let Game {
        id,
        balance,
        hands,
    } = game;
    object::delete(id);

    let user_idx = choose_to_number(hand);
    let robot_idx = clock::timestamp_ms(clock) % 2;

    let user_hand = vector::borrow(table::borrow(&hands, string::utf8(b"user")), user_idx);
    let robot_hand = vector::borrow(table::borrow(&hands, string::utf8(b"robot")), robot_idx);

    let recipient = if (user_win(*user_hand, *robot_hand)) tx_context::sender(ctx) else game_cap.creator;
    let amount = math::min(balance::value(&balance), 1000000000);
    transfer::public_transfer(coin::take(&mut balance, amount, ctx), recipient);

    table::drop(hands);
    if (balance::value(&balance) > 0) {
        amount = balance::value(&balance);
        transfer::public_transfer(coin::take(&mut balance, amount, ctx), tx_context::sender(ctx));
    };
    balance::destroy_zero(balance);
}

四:链上部署及准备工作

sui move build解决报错后发布sui client publish --gas-budget 100000000

在成功后得到的信息当中,获取关键的 $\mathit {Object\ ID}$ 设置环境变量,方便后续调用。

export PACKAGE_ID=0x679f4f04bce8d849a1f6488655cd67c9b91c1d5c757bebee8e8ff59ca14311bb
export GAMECAP=0xd265d859652446def1539abb200ce526a9ae4d1593387e96a2672f8c8909220b

切换其他用户(玩家)并分出参加游戏时用的 $\mathit {Coin}$:

sui client switch --address peaceful-hiddenite

sui client gas
# 记录上述命令得到的gasCoinId
export COIN=0xa5e9cc06d7bfb34b49fd2f6631e0580129fa8124a36030ae5107ebf785fca37c

# 分出999
sui client split-coin --coin-id $COIN --amounts 999 --gas-budget 10000000

sui client gas
# 记录上述命令得到的gasCoinId(用新分出的999)
export COIN=0xa4e204dd0fcf85910aa6411bfab04a56203ae67de25d743e55f28bb494062a8f

在 $\mathit {split}$-$\mathit {coin}$ 环节(以及后面所有可能的将 $\mathit {Coin}$ 作为参数传递的交易当中),可能会得到余额不足的报错,这是因为你将唯一的 $\mathit {gasCoinId}$ 用作参数传递(分割的本体),以至于没有其它余额支付这一笔交易了。<br>想要解决很简单,如果是测试网直接开水就行,如果是主网就用另一个账户给你转一笔账(左手倒右手)。

五:游戏时间

5.1 创建一局游戏

sui client call --package $PACKAGE_ID --module hands_rock_paper_scissors --function create_game --args rock paper $COIN 0x6 --gas-budget 10000000

在得到的信息当中,将 $\mathit {Game}$ 这个对象地址也设为环境变量:export GAME=0xb603fe81956368419dab526e5f502bbeb0ccb95380d5c0a4690132900f325864

如果你直接sui client $GAME会发现,看不到内部存储的 $\mathit {Table}$ 和 $\mathit {Vector}$ 具体的值,没关系,不是还有两个类型为0x2::dynamic_field::Field&lt;0x1::string::String, vector&lt;0x1::string::String>>的对象么?它们一个是你双手的石头剪刀布的情况,一个是对手双手的石头剪刀布的情况,都可以通过sui client object &lt;Object ID>进行查看。

5.2 选择左右手

一阵头脑风暴过后,决定选择左手,那么:sui client call --package $PACKAGE_ID --module hands_rock_paper_scissors --function choose_hand --args $GAMECAP $GAME left 0x6 --gas-budget 10000000

在后半段信息当中,有一块是 $\mathit {Created\ Objects}$,这里新建了一个 $\mathit {Coin}$<$\mathit {SUI}$>,也就是押金从余额$\mathit {(balance)}$又变成了钱包$\mathit {(coin)}$,而它的拥有者就是Owner: Account Address后的地址。<br>不难发现,这个地址跟我们(玩家)的地址一致,说明在这一局双手石头剪刀布的游戏当中,玩家取得了胜利。<br>这一点,也可以通过sui client gas来确认,可以看见有一笔余额为 $\text {999}$ 的 $\mathit {gasCoinId}$ 新增了。

5.3 其它

这里给出一局赢和一局输的游戏调用的 $\mathit {hash}$,有兴趣的可以在浏览器进行查看:

注意: 本篇所涉及的双手石头剪刀布游戏已发布至主网,有兴趣的欢迎来体验 送钱 ,同时请思考,如果想要在最后得知具体的出手情况,该对代码作何修改?

六:加入组织,共同进步!

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Nigdle
Nigdle
0xa745...Fe19
江湖只有他的大名,没有他的介绍。