Move on Sui入门 005-在sui链上发布一个猜硬币正反面的小游戏

  • Gavin
  • 更新于 2024-12-04 16:39
  • 阅读 336

一、游戏思路1、初始化一个游戏池,合约发布者拥有向游戏池存钱和从游戏池取钱的权限2、玩家玩游戏,押注一定额度代币,玩家猜硬币正反面和合约随机生成的boolean值比较,若一致则玩家赢,从游戏池拿出等额代币给玩家,若不一致则玩家输,玩家押注的代币存入游戏池二、代码设置错误码//错误码

一、游戏思路

1、初始化一个游戏池,合约发布者拥有向游戏池存钱和从游戏池取钱的权限

2、玩家玩游戏,押注一定额度代币,玩家猜硬币正反面和合约随机生成的boolean值比较,若一致则玩家赢,从游戏池拿出等额代币给玩家,若不一致则玩家输,玩家押注的代币存入游戏池

二、代码

  1. 设置错误码
// 错误码
const EPLAYERNOTENOUGH:u64 = 101; // 玩家额度不够
const EGAMEPOOLNOTENOUGH:u64 = 102; // 游戏池额度不够
const EINPUTNOTRIGHT: u64 = 103; // 玩家输入信息不正确
const EDEPOSITNOTENOUGH:u64 = 104; // 存款额度不够
  1. 创建游戏池结构体

游戏池结构体是玩家共享的,都可以参与游戏

游戏池中显示的是代币balance,要注意和coin的区别

// 游戏池结构体
public struct GamePool has key {
    id: UID, // 资金池id
    name: String, // 资金池名称
    balance: Balance<PUBLICCOIN>  // 资金池余额,存放的是PUBLICCOIN,所以显示的是PUBLICCOIN的余额
}
  1. 创建管理员(合约权限)结构体
// 游戏池控制权结构体,拥有这个控制权的地址才能从游戏池取钱
public struct AdminCap has key {
    id: UID
}
  1. 初始化游戏池,将游戏池共享,将合约权限转移给合约发布者
// 初始化init
fun init (ctx: &mut TxContext) {
    let game_pool = GamePool {
        id: object::new(ctx),
        name: utf8(b"资金池"),
        balance: balance::zero()
    };
    // 共享资金池,所有玩家都可以看到
    transfer::share_object(game_pool);
    let admin_cap = AdminCap{ id: object::new(ctx) };
    // 转移资金池的控制权限给合约发布者,只有合约发布者才能取钱
    // 此处transfer和public_transfer有区别,注意结构体ability
    transfer::transfer(admin_cap, ctx.sender());
}
  1. 存钱方法

理论上应该只有合约发布者可以向游戏池存钱,此处没有设置权限,玩家也可向游戏池存钱.

这里存钱,不一定是将账户所有代币都存入资金池,所以这里需要coin和balance的转换

// 游戏池存钱
public entry fun deposit(game_pool: &mut GamePool, input_coin: Coin<PUBLICCOIN>, coin_amount: u64, ctx: &mut TxContext) {
    // 判断存钱账户额度是否足够
    let coin_value = input_coin.value();
    assert!(coin_value >= coin_amount, EDEPOSITNOTENOUGH);
    // 拆出要存储的代币额度,储存到游戏池
    let mut input_balance = coin::into_balance(input_coin);
    let split_balance = input_balance.split(coin_amount);
    game_pool.balance.join(split_balance);
    // 将剩余代币返还给存钱账户
    let refund_coin = coin::from_balance(input_balance, ctx);
    transfer::public_transfer(refund_coin, ctx.sender());
}
  1. 取钱方法

只有合约发布者可以取钱,限制权限

将balance转成coin,然后转移到用户地址

// 合约发布者(有AdminCap权限)取钱
public entry fun withdrow(_:&AdminCap, game_pool: &mut GamePool, coin_amount: u64, ctx: &mut TxContext) {
    // 判断游戏池里的balance是否足够
    let game_pool_balance = game_pool.balance.value();
    assert!(game_pool_balance >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 取钱:把balance转换成coin,然后把coin转移给合约发布者
    let withdrow_coin = coin::take(&mut game_pool.balance, coin_amount, ctx);
    transfer::public_transfer(withdrow_coin, ctx.sender());
}
  1. 玩游戏
// 玩游戏,参数:游戏池、玩家猜字、质押代币、质押代币额度、随机数、TxContent
entry fun play (game_pool: &mut GamePool, input_guess: u64, input_coin: &mut Coin<PUBLICCOIN>, coin_amount: u64, r: &Random, ctx: &mut TxContext) {
    // 获取传入代币的额度
    let coin_value = input_coin.value();
    // 判断玩家有没有足够的代币额度
    assert!(coin_value >= coin_amount, EPLAYERNOTENOUGH);
    // 获取游戏池中代币额度
    let game_pool_value:u64 = game_pool.balance.value();
    // 判断游戏池有没有足够的代币额度
    assert!(game_pool_value >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 判断输入参数是否正确
    assert!(input_guess == 1 || input_guess == 2, EINPUTNOTRIGHT);
    // 开始玩游戏
    // 创建一个随机数生成器
    let mut random_generator = random::new_generator(r, ctx);
    let random_bool = random::generate_bool(&mut random_generator);
    if((input_guess == 1 && random_bool) || (input_guess == 2 && !random_bool)) { // 猜中
        // 先从游戏池拆出blance,再转换成coin,再转移给玩家
        let reward_blance = balance::split(&mut game_pool.balance, coin_amount);
        let reward_coin = coin::from_balance(reward_blance, ctx);
        transfer::public_transfer(reward_coin, ctx.sender());
    }else { // 猜错
        // 玩家拆出coin,转换成balance,再转移到游戏池
        let player_coin = input_coin.split(coin_amount, ctx);
        let player_balance = coin::into_balance(player_coin);
        game_pool.balance.join(player_balance);
    }
}
  1. 完整代码
/*
猜硬币正反面
质押一定额度代币
输入 1-true代表正面 2-false代表反面
系统随机数生成boolean
比较:两者一致从游戏池拿走同样额度的代币;不一致质押代币放入游戏池
*/
module mygame::mygame;
use std::string::{String, utf8};
use publiccoin::publiccoin::PUBLICCOIN; // 引用的另一个合约
use sui::balance;
use sui::balance::Balance;
use sui::coin;
use sui::coin::Coin;
use sui::object;
use sui::random;
use sui::random::Random;
use sui::transfer;

// 错误码
const EPLAYERNOTENOUGH:u64 = 101; // 玩家额度不够
const EGAMEPOOLNOTENOUGH:u64 = 102; // 游戏池额度不够
const EINPUTNOTRIGHT: u64 = 103; // 玩家输入信息不正确
const EDEPOSITNOTENOUGH:u64 = 104; // 存款额度不够
// 游戏池结构体
public struct GamePool has key {
    id: UID, // 资金池id
    name: String, // 资金池名称
    balance: Balance<PUBLICCOIN>  // 资金池余额,存放的是PUBLICCOIN,所以显示的是PUBLICCOIN的余额
}
// 游戏池控制权结构体,拥有这个控制权的地址才能从游戏池取钱
public struct AdminCap has key {
    id: UID
}
// 初始化init
fun init (ctx: &mut TxContext) {
    let game_pool = GamePool {
        id: object::new(ctx),
        name: utf8(b"资金池"),
        balance: balance::zero()
    };
    // 共享资金池,所有玩家都可以看到
    transfer::share_object(game_pool);
    let admin_cap = AdminCap{ id: object::new(ctx) };
    // 转移资金池的控制权限给合约发布者,只有合约发布者才能取钱
    // 此处transfer和public_transfer有区别,注意结构体ability
    transfer::transfer(admin_cap, ctx.sender());
}
// 游戏池存钱
public entry fun deposit(game_pool: &mut GamePool, input_coin: Coin<PUBLICCOIN>, coin_amount: u64, ctx: &mut TxContext) {
    // 判断存钱账户额度是否足够
    let coin_value = input_coin.value();
    assert!(coin_value >= coin_amount, EDEPOSITNOTENOUGH);
    // 拆出要存储的代币额度,储存到游戏池
    let mut input_balance = coin::into_balance(input_coin);
    let split_balance = input_balance.split(coin_amount);
    game_pool.balance.join(split_balance);
    // 将剩余代币返还给存钱账户
    let refund_coin = coin::from_balance(input_balance, ctx);
    transfer::public_transfer(refund_coin, ctx.sender());
}

// 玩游戏,参数:游戏池、玩家猜字、质押代币、质押代币额度、随机数、TxContent
entry fun play (game_pool: &mut GamePool, input_guess: u64, input_coin: &mut Coin<PUBLICCOIN>, coin_amount: u64, r: &Random, ctx: &mut TxContext) {
    // 获取传入代币的额度
    let coin_value = input_coin.value();
    // 判断玩家有没有足够的代币额度
    assert!(coin_value >= coin_amount, EPLAYERNOTENOUGH);
    // 获取游戏池中代币额度
    let game_pool_value:u64 = game_pool.balance.value();
    // 判断游戏池有没有足够的代币额度
    assert!(game_pool_value >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 判断输入参数是否正确
    assert!(input_guess == 1 || input_guess == 2, EINPUTNOTRIGHT);
    // 开始玩游戏
    // 创建一个随机数生成器
    let mut random_generator = random::new_generator(r, ctx);
    let random_bool = random::generate_bool(&mut random_generator);
    if((input_guess == 1 && random_bool) || (input_guess == 2 && !random_bool)) { // 猜中
        // 先从游戏池拆出blance,再转换成coin,再转移给玩家
        let reward_blance = balance::split(&mut game_pool.balance, coin_amount);
        let reward_coin = coin::from_balance(reward_blance, ctx);
        transfer::public_transfer(reward_coin, ctx.sender());
    }else { // 猜错
        // 玩家拆出coin,转换成balance,再转移到游戏池
        let player_coin = input_coin.split(coin_amount, ctx);
        let player_balance = coin::into_balance(player_coin);
        game_pool.balance.join(player_balance);
    }
}
// 合约发布者(有AdminCap权限)取钱
public entry fun withdrow(_:&AdminCap, game_pool: &mut GamePool, coin_amount: u64, ctx: &mut TxContext) {
    // 判断游戏池里的balance是否足够
    let game_pool_balance = game_pool.balance.value();
    assert!(game_pool_balance >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 取钱:把balance转换成coin,然后把coin转移给合约发布者
    let withdrow_coin = coin::take(&mut game_pool.balance, coin_amount, ctx);
    transfer::public_transfer(withdrow_coin, ctx.sender());
}

三、调用其它合约

该合约依赖了publiccion合约,需要将其作为依赖引入

1、在Move.toml文件中的dependencies引入

[dependencies]
Sui = { git = "https://gitee.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
publiccoin = { local = "../../task2/publiccoin" }
# 第一种引入方式:远程引入
# 将引入的合约放到git仓库,git 为https地址,subdir 为引入合约Move.toml文件所在的文件夹,rev为分支、标签或者提交哈希。
# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
# Revision can be a branch, a tag, and a commit hash.
# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
# 第二种引入方式:本地引入,引入合约项目本地相对地址
# For local dependencies use `local = path`. Path is relative to the package root
# Local = { local = "../path/to" }

2、在合约中将需要用到的依赖结构体、方法等引入

use publiccoin::publiccoin::PUBLICCOIN; // 引用的另一个合约

四、coin和balance的相互转换

coin和balance都是sui标准库提供的功能,可以相互转换。结合以下合约代码,学习理解。

1、coin转换为balance

// 游戏池存钱----方法一
public entry fun deposit(game_pool: &mut GamePool, input_coin: Coin<PUBLICCOIN>, coin_amount: u64, ctx: &mut TxContext) {
    // 判断存钱账户额度是否足够
    let coin_value = input_coin.value();
    assert!(coin_value >= coin_amount, EDEPOSITNOTENOUGH);
    // 拆出要存储的代币额度,储存到游戏池
    let mut input_balance = coin::into_balance(input_coin);
    let split_balance = input_balance.split(coin_amount);
    game_pool.balance.join(split_balance);
    // 将剩余代币返还给存钱账户
    let refund_coin = coin::from_balance(input_balance, ctx);
    transfer::public_transfer(refund_coin, ctx.sender());
}

// 游戏池存钱----方法二
public entry fun deposit_V2(game_pool: &mut GamePool, input_coin: &mut Coin<PUBLICCOIN>, coin_amount: u64, ctx: &mut TxContext) {
    // 判断存钱账户额度是否足够
    let coin_value = input_coin.value();
    assert!(coin_value >= coin_amount, EDEPOSITNOTENOUGH);
    // 先拆出要存储的coin,然后转换成balance,存入游戏池
    let split_coin = input_coin.split(coin_amount,ctx);
    // 转换成balance
    let split_balance = coin::into_balance(split_coin);
    // 存入游戏池
    game_pool.balance.join(split_balance);
}

从存钱方法一中可以看到,合约先将传入的coin转换为balance,然后再将balance拆分,之后再将剩余balance转换成coin,再将coin转移给存钱用户。其中参数input_coin为不可变变量。

从存钱方法二中可以看到,合约先将传入的coin拆分出一个要存入额度的coin,然后再将这个coin转换成balance,再将balance存入游戏池。其中参数input_coin为可变变量,并且不需要将剩余coin再进行转移。

2、balance转换为coin

// 合约发布者(有AdminCap权限)取钱----方法一
public entry fun withdrow(_:&AdminCap, game_pool: &mut GamePool, coin_amount: u64, ctx: &mut TxContext) {
    // 判断游戏池里的balance是否足够
    let game_pool_balance = game_pool.balance.value();
    assert!(game_pool_balance >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 取钱:把balance转换成coin,然后把coin转移给合约发布者,这里拆分和转换同时进行
    let withdrow_coin = coin::take(&mut game_pool.balance, coin_amount, ctx);
    transfer::public_transfer(withdrow_coin, ctx.sender());
}
// 合约发布者(有AdminCap权限)取钱----方法二
public entry fun withdrow_V2(_:&AdminCap, game_pool: &mut GamePool, coin_amount: u64, ctx: &mut TxContext) {
    // 判断游戏池里的balance是否足够
    let game_pool_balance = game_pool.balance.value();
    assert!(game_pool_balance >= coin_amount, EGAMEPOOLNOTENOUGH);
    // 先拆分出balance
    let split_balance = balance::split(&mut game_pool.balance, coin_amount);
    // 再将拆分出的balance转为coin
    let split_coin = coin::from_balance(split_balance, ctx);
    // 转移
    transfer::public_transfer(split_coin, ctx.sender());
}

从取钱方法一中可以看到,coin::take()方法将拆分balance和balance转化为coin同时进行了,直接得到了需要转移的coin。

从取钱方法二中可以看到,拆分balance和将balance转为coin是分开进行的。

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

0 条评论

请先 登录 后评论
Gavin
Gavin
0xAA3C...A0dD
江湖只有他的大名,没有他的介绍。