手摸手教你完成HOH Move共学Task4-Move Game

  • Rex
  • 更新于 2024-11-12 01:02
  • 阅读 564

使用Move语言实现了类似翻硬币的游戏,使用了随机数,并部署到Sui主网。

HOH.webp

Move 共学由 HOH 社区、HackQuest、OpenBuild、KeyMap 联合发起。共学营集齐了多位 Move 语言领域内的专业导师,帮助新手小白快速了解 Move 语言及应用于 Web3 开发。

本教程面向新手,即使没有任何编程基础也能快速上手,有基础的同学可以跳着看。

写在前面

经过前三个task的学习,相信你已经掌握Move的基本语法、项目的部署流程、链上交互流程,在之前文章中提到的内容之后不会重复讲解。

如果你还未完成task1-3,可以翻看我之前的教程:

Task4需求

  • 完成 链游相关知识的学习
  • 完成 随机数的学习,游戏必须包含随机数
  • 完成 存和取游戏资金池
  • 完成 如何存储Coin在合约的学习,游戏必须能存取[task2] 发行的Faucet Coin,用task2的 Faucet Coin作为游戏输赢的资产
  • 完成 第一个游戏合约部署主网

游戏玩法

  • 任何人都可以向资金池投入资金,一般而言,游戏创建之初需要有管理员存入初始资金
  • 只有管理员可以提取资金池里的资金
  • 玩家投入代币并猜测结果
    • 如果猜对,玩家获得双倍奖励
    • 如果猜错,则损失所有投入的代币,损失的金额存入游戏资金池

代码详解

定义对象

首先先创建游戏对象,该对象中只需要存储资金池的余额

public struct Game has key {
    id: UID,
    balance: Balance<CHAINREX_FAUCET_COIN>,
}

另外需要一个对象用于控制资金池的取款权限,该对象将在模块部署时将权限转移给部署者,并在取款函数中作为参数,这样只有部署者才能调用取款函数

public struct Admin has key {
    id: UID,
}

初始化

初始化函数也比较简洁,创建了game对象并将其作为共享对象,创建了Admin对象并将其权限转移给部署者

fun init(ctx: &mut TxContext) {
    let game = Game {
        id: object::new(ctx),
        balance: balance::zero(),
    };
    transfer::share_object(game);

    let admin = Admin {id: object::new(ctx)};
    transfer::transfer(admin, ctx.sender());
}

存款

public entry fun Deposit(
    game: &mut Game,
    coin: &mut Coin<CHAINREX_FAUCET_COIN>,
    amount: u64,
) {
    assert!(
        coin::value(coin) >= amount,
        EUserInsufficientBalance
    );
    let split_balance = balance::split(coin::balance_mut(coin), amount);
    balance::join(&mut game.balance, split_balance);
}

首先我们需要判断用户传入的coin余额是否大于等于amount,不满足的话则使用assert抛出异常,其中异常代码的定义为:

const EUserInsufficientBalance: u64 = 1000;

接着从用户的coin中分离出指定数额的balance,并使用join合并到资金池中

let split_balance = balance::split(coin::balance_mut(coin), amount);
balance::join(&mut game.balance, split_balance);

取款

public entry fun Withdraw(
    game: &mut Game,
    _: &Admin,
    amount: u64,
    ctx: &mut TxContext
) {
    assert!(
        game.balance.value() >= amount,
        EGameInsufficientBalance
    );
    let cash = coin::take(&mut game.balance, amount, ctx);
    transfer::public_transfer(cash, ctx.sender());
}

取款函数只有管理员能调用,所以需要在参数中传入Admin对象,这样其他用户调用该函数时就会报错。

同样在一开始先检查资金池的余额是否满足要取款的金额

assert!(
    game.balance.value() >= amount,
    EGameInsufficientBalance
);

接着从资金池中取出指定数量余额并存入一个新的coin中,最后将该coin转移给管理员。

let cash = coin::take(&mut game.balance, amount, ctx);
transfer::public_transfer(cash, ctx.sender());

开始游戏

public entry fun Play(
    game: &mut Game,
    rnd: &Random,
    guess: bool,
    coin: &mut Coin<CHAINREX_FAUCET_COIN>,
    amount: u64,
    ctx: &mut TxContext
) {
    // 检查合约余额是否充足,确保用户获胜时有足够金额奖励
    assert!(
        game.balance.value() >= amount,
        EGameInsufficientBalance
    );
    // 检查用户的余额是否充足
    assert!(
        coin::value(coin) >= amount,
        EUserInsufficientBalance
    );

    // 生成随机数
    let mut gen = random::new_generator(rnd, ctx);
    let flag = random::generate_bool(&mut gen);

    // 如果获胜
    if (flag == guess) {
        // 投入的代币不变,另外奖励等额的代币
        let reward = coin::take(&mut game.balance, amount, ctx);
        coin::join(coin, reward);
    }
    // 猜错了就损失投入的代币
    else {
        Self::Deposit(game, coin, amount)
    }
}

首先同样需要检查两个内容

  • 资金池的余额是否大于等于用户将要投入的资金,如果用户获胜需要奖励双倍资金(包括原始投入),则需要给用户等额的资金
  • 用户的coin余额是否大于等于声称要投入的资金
assert!(
    game.balance.value() >= amount,
    EGameInsufficientBalance
);
// 检查用户的余额是否充足
assert!(
    coin::value(coin) >= amount,
    EUserInsufficientBalance
);

接着需要生成一个随机数,这里使用了sui::random模块,其中Random的object id为0x8,将作为Play函数的rnd: &Random参数。使用时,需要先创建一个随机数生成器,然后使用该生成器生成一个bool值。

let mut gen = random::new_generator(rnd, ctx);
let flag = random::generate_bool(&mut gen);

最后将生成的bool值和用户的猜测值进行比对

  • 如果相等,则说明用户获胜,另外奖励用户等额的资金
    • 使用coin::take从资金池余额获取指定资金,并存入新的coin,然后将新的coin合并进用户的coin中
  • 如果不相等,则说明用户输了,通过调用本模块的存款函数,则将用户的资金存入资金池中
// 如果获胜
if (flag == guess) {
    // 投入的代币不变,另外奖励等额的代币
    let reward = coin::take(&mut game.balance, amount, ctx);
    coin::join(coin, reward);
}
// 猜错了就损失投入的代币
else {
    Self::Deposit(game, coin, amount)
}

完整代码

最后贴一下完整代码

module game::game {
    use sui::coin::{Self, Coin};
    use chainrex_faucet_coin::chainrex_faucet_coin::CHAINREX_FAUCET_COIN;
    use sui::balance::{Self, Balance};
    use sui::random::{Self, Random};

    const EUserInsufficientBalance: u64 = 1000;
    const EGameInsufficientBalance: u64 = 1001;

    public struct Game has key {
        id: UID,
        balance: Balance<CHAINREX_FAUCET_COIN>,
    }

    public struct Admin has key {
        id: UID,
    }

    fun init(ctx: &mut TxContext) {
        let game = Game {
            id: object::new(ctx),
            balance: balance::zero(),
        };
        transfer::share_object(game);

        let admin = Admin {id: object::new(ctx)};
        transfer::transfer(admin, ctx.sender());
    }

    public entry fun Deposit(
        game: &mut Game,
        coin: &mut Coin<CHAINREX_FAUCET_COIN>,
        amount: u64,
    ) {
        assert!(
            coin::value(coin) >= amount,
            EUserInsufficientBalance
        );
        let split_balance = balance::split(coin::balance_mut(coin), amount);
        balance::join(&mut game.balance, split_balance);
    }

    // 只有管理员能够提现
    public entry fun Withdraw(
        game: &mut Game,
        _: &Admin,
        amount: u64,
        ctx: &mut TxContext
    ) {
        assert!(
            game.balance.value() >= amount,
            EGameInsufficientBalance
        );
        let cash = coin::take(&mut game.balance, amount, ctx);
        transfer::public_transfer(cash, ctx.sender());
    }

    // 如果用户猜对了,获得双倍奖励。如果用户猜错了,损失投入的代币

    public entry fun Play(
        game: &mut Game,
        rnd: &Random,
        guess: bool,
        coin: &mut Coin<CHAINREX_FAUCET_COIN>,
        amount: u64,
        ctx: &mut TxContext
    ) {
        // 检查合约余额是否充足,确保用户获胜时有足够金额奖励
        assert!(
            game.balance.value() >= amount,
            EGameInsufficientBalance
        );
        // 检查用户的余额是否充足
        assert!(
            coin::value(coin) >= amount,
            EUserInsufficientBalance
        );

        // 生成随机数
        let mut gen = random::new_generator(rnd, ctx);
        let flag = random::generate_bool(&mut gen);

        // 如果获胜
        if (flag == guess) {
            // 投入的代币不变,另外奖励等额的代币
            let reward = coin::take(&mut game.balance, amount, ctx);
            coin::join(coin, reward);
        }
        // 猜错了就损失投入的代币
        else {
            Self::Deposit(game, coin, amount)
        }
    }
}

⚠️注意事项

  • 需要在Move.toml中引入自己的faucet代币模块,并且会使用你最后一次部署的faucet代币地址

    chainrex_faucet_coin = { local = "../../task2/chainrex_faucet_coin" }
  • Play函数的第二个参数sui::random::Random的object id 为0x8

  • 在区块链浏览器上无法输入Bool类型的参数,需要使用命令行进行调用,具体参数修改为自己的即可

    sui client call --package 0x37c3152c9b1dcce88d27f6d2375fdbf9fbd357eb0a7baaea4b268a12af15b524 --module game --function Play --args 0x044b841ce7633af6c98067359b6215227049ff14142185ca2e4a30047527f805 0x8 true 0x184f91c7f9daea73caee3af40b408b86929acfd2c837be8ea48bd531fd47996e 1000000000 --gas-budget 1000000
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Rex
Rex
0x79C1...1Cd8
江湖只有他的大名,没有他的介绍。