一:游戏简介
以传统的扫雷游戏规则为模板,将扫雷区域简化成 5 × 10 \text 5\ \times\ \text {10} 5 × 10 的正方形,点开所有安全格子视为获得胜利。
二:功能设计
- 游戏发布者可以在后续任何游戏对局当中持续获得利益,当链上存储的利益余额达到一定数量时自动提现到发布者钱包,当然,发布者也可以手动进行提现。
- 任何人都可以支付 666 b a l a n c e \text {666}\ \mathit {balance} 666 balance 来开启一局游戏,如果你获得了胜利,很可惜,除了快乐和自封的荣誉等精神层面的激励之外,并不会收到任何实质性的奖励。
- 任何人都可以创建挑战任务,除了添加额外的赏金之外,游戏难度并不会发生改变,即使你的任务奖励是天价。赏金最低设置为 1998 s u i b a l a n c e \text {1998}\ \mathit {sui\ balance} 1998 sui balance。
- 在有挑战任务的前提下,任何人都可以选择挑战,一次挑战的费用是当前赏金的一半,这里以最低金额举例说明:
-
- 挑战任务赏金是 1998 \text {1998} 1998,此时有玩家挑战,需要支付它的一半,也就是 1998 ÷ 2 = 999 \text {1998}\ \div\ \text 2\ =\ \text {999} 1998 ÷ 2 = 999(如果有小数点则下取整)。
- 这一笔 999 \text {999} 999 将进一步拆分,它的一半(上取整)将会累加到赏金当中,另一半将按照 1 : 3 \text 1\ :\ \text 3 1 : 3 的比例分配给游戏发布者和挑战任务发布者(如果有小数点,那么游戏发布者赚取的收益下取整),当该挑战被完成时,任务发布者的收益将转账到其钱包。
- 下一个挑战者(即使上一个挑战者的对局尚未结束)将以 ( 1998 + ⌈ ⌊ 1998 2 ⌋ 2 ⌉ ) ÷ 2 = 1249 (\text {1998}\ +\ \lceil \frac{\lfloor \frac{\text {1998}}{\text 2} \rfloor}{\text 2} \rceil)\ \div\ \text 2\ =\ \text {1249} (1998 + ⌈2⌊21998⌋⌉) ÷ 2 = 1249 的金额开启挑战,后续的赏金累计和开启挑战的金额以此类推。
- 对于同一个挑战任务,第一个成功扫雷的人,将收割当前的全部赏金,其他正在进行任务挑战的玩家将终止游戏对局,即使有的人才刚刚开始。
三:结构设计
3.1 游戏发布者
3.1.1 ADMIN
借助 O n e \mathit {One} One- T i m e \mathit {Time} Time- W i t n e s s \mathit {Witness} Witness 生成Publisher
,需要一个与模块同名并全大写的结构体:
public struct ADMIN has drop {}
3.1.2 GameCap
存储游戏发布者的相关信息,主要是 S u i \mathit {Sui} Sui 余额和发布者的地址:
public struct GameCap has key {
id: UID,
balance: Balance<SUI>,
publisher_address: address,
}
3.2 挑战任务
3.2.1 任务信息
奖励金额和发布者收益是必须的,为了清楚任务发布者是谁,还需要他/她的地址,除此之外,还可以维护两个数 —— 正在挑战的人数和尝试挑战的总人数,为游戏更增添一份紧张刺激感:
public struct Task has store {
in_task: u64,
total_attempts: u64,
reward: Balance<SUI>,
earned: Balance<SUI>,
task_publisher_address: address,
}
3.2.2 任务列表
任务信息的结构设计并不能单独存在,我们另外开辟一个列表来存储一系列任务:
public struct TaskList has key {
id: UID,
task_ids: vector<ID>,
tasks: Table<ID, Task>,
}
3.2.3 询问任务详情事件
通过任务编号查询其信息,触发事件并返回时使用的结构体:
public struct QueryTaskEvent has copy, drop {
task_id: ID,
in_task: u64,
total_attempts: u64,
cur_reward: u64,
}
3.3 玩家
3.3.1 游戏内容
存储有扫雷区域现状,每个格子是否安全的哈希码,如果是从任务进入的游戏,还需要对应的任务编号:
public struct GameInfo has key {
id: UID,
task_id: ID,
checkerboard: vector<vector<Char>>,
hash_code: vector<u8>,
}
3.3.2 任务已被他人完成事件
一个任务可以同时被多个人开启,但最终完成并获得奖励的只有一人,此时其他人再尝试该任务时触发:
public struct PlayTooLate has copy, drop {
loser: String,
}
3.4 游戏
一系列在游戏过程中可能触发的情况事件,包括每一次扫雷后的区域变化事件、游戏成功事件以及游戏失败事件:
public struct GameEvent has copy, drop {
checkerboard: vector<String>,
}
public struct GameSuccessEvent has copy, drop {
reward: u64,
}
public struct GameOverEvent has copy, drop {
loser: String,
}
四:功能实现
4.1 游戏发布者
4.1.1 init
通过 O n e \mathit {One} One- T i m e \mathit {Time} Time- W i t n e s s \mathit {Witness} Witness 生成Publisher
,同时根据发布者信息创建并共享GameCap
:
fun init(otw: ADMIN, ctx: &mut TxContext) {
// create and transfer Publisher
package::claim_and_keep(otw, ctx);
// create and transfer GameCap
let game_cap = GameCap {
id: object::new(ctx),
balance: balance::zero(),
publisher_address: ctx.sender(),
};
transfer::share_object(game_cap);
}
4.1.2 withdraw
根据游戏设计,提现到游戏发布者钱包有两种情况,一种是手动(仅限发布者本人调用),另一种是到达一定金额是自动,为了更好地满足这两种情况,将提现的函数进行拆分:
public(package) fun withdraw_(game_cap: &mut GameCap, ctx: &mut TxContext) {
// check the balance value
assert!(game_cap.balance.value() > 0, ENotEarnBalance);
// withdraw all the balance
let all = game_cap.balance.withdraw_all();
transfer::public_transfer(coin::from_balance(all, ctx), game_cap.publisher_address);
}
entry fun withdraw(_: &Publisher, game_cap: &mut GameCap, ctx: &mut TxContext) {
withdraw_(game_cap, ctx);
}
entry
提现函数中用Publisher
来限制可调用的用户,public
提现函数用(package)
来限制只能由包内的其它模块调用,功能是当赚取到收益时体现到游