玩eos上的掷骰子游戏---dice智能合约

dice智能合约的操作步骤,在eos的github上有,这里不再赘述,这里主要讲dice智能合约的实现。
dice在中文中是骰子。聪明的你已经猜到了,这可能是一个掷骰子游戏,没错,这就是一个双人掷骰子游戏。
现在我们沿着操作步骤看它的实现:

第一步:充值(如同进赌场要换筹码):

cleos push action dice deposit '[ "wang", "100.0000 EOS" ]' -p wang
cleos push action dice deposit '[ "zhao", "100.0000 EOS" ]' -p zhao

wang和zhao是我事先创建好的,各自都有1000个EOS。
tips: 充值之前需要取得dice智能合约的授权。

现在我们来看看deposit函数:

void deposit( const account_name from, const asset& quantity ) {
         
 eosio_assert( quantity.is_valid(), "invalid quantity" );
 eosio_assert( quantity.amount > 0, "must deposit positive quantity" );

 auto itr = accounts.find(from);
 if( itr == accounts.end() ) {
    // 如果在dice合约里面没有账户,就创建一个
    itr = accounts.emplace(_self, [&](auto& acnt){
       acnt.owner = from;
    });
 }

 // 向dice转账,由于使用的是eos token,转账通过eosio.token合约执行
 action(
    permission_level{ from, N(active) },
    N(eosio.token), N(transfer),
    std::make_tuple(from, _self, quantity, std::string(""))
 ).send();

// 修改dice合约中该账户的余额
 accounts.modify( itr, 0, [&]( auto& acnt ) {
    acnt.eos_balance += quantity;
 });
}

deposit函数比较简单,就是往dice充值,并且记录下来。

第二步:下注

$ openssl rand 32 -hex
271261da3bedbf0196d43dad5a73abc309e2d40750f09096c01e09b2ee0f91d5

$ echo -n '271261da3bedbf0196d43dad5a73abc309e2d40750f09096c01e09b2ee0f91d5' | xxd -r -p | sha256sum -b | awk '{print $1}'
921e0c66a8866ca0037fbb628acd5f63f3ba119962c9f5ca68d54b5a70292f36

$ cleos push action dice offerbet '[ "3.0000 EOS", "wang", "921e0c66a8866ca0037fbb628acd5f63f3ba119962c9f5ca68d54b5a70292f36" ]' -p wang
executed transaction: 77ff114b6c80bd09e453b113531eb213a555fca888451d94f703ae0dfa512b65  280 bytes  109568 cycles
#        dice <= dice::offerbet             {"bet":"3.0000 EOS","player":"wang","commitment":"921e0c66a8866ca0037fbb628acd5f63f3ba119962c9f5ca68...

首先是使用openssl产生一个随机数,然后求出它的哈希,最后用这个哈希下注。

现在我们来看看deposit函数,这也是dice合约最长的成员函数

void offerbet(const asset& bet, const account_name player, const checksum256& commitment) {

 eosio_assert( bet.symbol == S(4,EOS) , "only EOS token allowed" );
 eosio_assert( bet.is_valid(), "invalid bet" );
 eosio_assert( bet.amount > 0, "must bet positive quantity" );

 eosio_assert( !has_offer( commitment ), "offer with this commitment already exist" );
 require_auth( player );

 auto cur_player_itr = accounts.find( player );
 eosio_assert(cur_player_itr != accounts.end(), "unknown account");

 // Store new offer
 auto new_offer_itr = offers.emplace(_self, [&](auto& offer){
    offer.id         = offers.available_primary_key();
    offer.bet        = bet;
    offer.owner      = player;
    offer.commitment = commitment;
    offer.gameid     = 0;
 });

 // Try to find a matching bet
 auto idx = offers.template get_index<N(bet)>();
 auto matched_offer_itr = idx.lower_bound( (uint64_t)new_offer_itr->bet.amount );

 if( matched_offer_itr == idx.end()
    || matched_offer_itr->bet != new_offer_itr->bet
    || matched_offer_itr->owner == new_offer_itr->owner ) {

    // No matching bet found, update player's account
    accounts.modify( cur_player_itr, 0, [&](auto& acnt) {
       eosio_assert( acnt.eos_balance >= bet, "insufficient balance" );
       acnt.eos_balance -= bet;
       acnt.open_offers++;
    });

 } else {
    // Create global game counter if not exists
    auto gdice_itr = global_dices.begin();
    if( gdice_itr == global_dices.end() ) {
       gdice_itr = global_dices.emplace(_self, [&](auto& gdice){
          gdice.nextgameid=0;
       });
    }

    // Increment global game counter
    global_dices.modify(gdice_itr, 0, [&](auto& gdice){
       gdice.nextgameid++;
    });

    // Create a new game
    auto game_itr = games.emplace(_self, [&](auto& new_game){
       new_game.id       = gdice_itr->nextgameid;
       new_game.bet      = new_offer_itr->bet;
       new_game.deadline = 0;

       new_game.player1.commitment = matched_offer_itr->commitment;
       memset(&new_game.player1.reveal, 0, sizeof(checksum256));

       new_game.player2.commitment = new_offer_itr->commitment;
       memset(&new_game.player2.reveal, 0, sizeof(checksum256));
    });

    // Update player's offers
    idx.modify(matched_offer_itr, 0, [&](auto& offer){
       offer.bet.amount = 0;
       offer.gameid = game_itr->id;
    });

    offers.modify(new_offer_itr, 0, [&](auto& offer){
       offer.bet.amount = 0;
       offer.gameid = game_itr->id;
    });

    // Update player's accounts
    accounts.modify( accounts.find( matched_offer_itr->owner ), 0, [&](auto& acnt) {
       acnt.open_offers--;
       acnt.open_games++;
    });

    accounts.modify( cur_player_itr, 0, [&](auto& acnt) {
       eosio_assert( acnt.eos_balance >= bet, "insufficient balance" );
       acnt.eos_balance -= bet;
       acnt.open_games++;
    });
 }
}

下注的时候,如果找不到匹配的订单,就会保存起来;如果找到匹配订单,游戏就开始了。

第三步: 开盅

cleos push action dice reveal '[ "921e0c66a8866ca0037fbb628acd5f63f3ba119962c9f5ca68d54b5a70292f36", "271261da3bedbf0196d43dad5a73abc309e2d40750f09096c01e09b2ee0f91d5" ]' -p wang

这里需要把第二步生成的随机数和它的哈希传进去。

现在我们来看看reveal函数:

void reveal( const checksum256& commitment, const checksum256& source ) {

 assert_sha256( (char *)&source, sizeof(source), (const checksum256 *)&commitment );

 auto idx = offers.template get_index<N(commitment)>();
 auto curr_revealer_offer = idx.find( offer::get_commitment(commitment)  );

 eosio_assert(curr_revealer_offer != idx.end(), "offer not found");
 eosio_assert(curr_revealer_offer->gameid > 0, "unable to reveal");

 auto game_itr = games.find( curr_revealer_offer->gameid );

 player curr_reveal = game_itr->player1;
 player prev_reveal = game_itr->player2;

 if( !is_equal(curr_reveal.commitment, commitment) ) {
    std::swap(curr_reveal, prev_reveal);
 }

 eosio_assert( is_zero(curr_reveal.reveal) == true, "player already revealed");

// 正常情况下需要两个人开盅,如果一方开盅后,另一方迟迟不开,则算开盅的那一方赢
 if( !is_zero(prev_reveal.reveal) ) {
    // 最后一个玩家揭开,将2个玩家的source和commitment看作一个整体,求出它的哈希
    checksum256 result;
    sha256( (char *)&game_itr->player1, sizeof(player)*2, &result);

    auto prev_revealer_offer = idx.find( offer::get_commitment(prev_reveal.commitment) );

    // 通过比较哈希的第0个字节和第1个字节的大小,决定胜负。
    // 不同的数据,哈希是不一样的,这段数据由玩家1和玩家2提交的数据构成,因此玩家1和玩家2都能影响游戏的结果
    int winner = result.hash[1] < result.hash[0] ? 0 : 1;

    if( winner ) {
       pay_and_clean(*game_itr, *curr_revealer_offer, *prev_revealer_offer);
    } else {
       pay_and_clean(*game_itr, *prev_revealer_offer, *curr_revealer_offer);
    }

 } else {
    // 第一个玩家开盅,记下source,并且启动5分钟倒计时,如果第二个玩家在5分钟内没有开盅,第一个玩家赢得游戏。
    games.modify(game_itr, 0, [&](auto& game){

       if( is_equal(curr_reveal.commitment, game.player1.commitment) )
          game.player1.reveal = source;
       else
          game.player2.reveal = source;

       game.deadline = now() + FIVE_MINUTES;
    });
 }
}

上面的source和commitment对应的是随机数和它的哈希。

以上三步是dice合约的主要功能,还有许多技术细节需要自己摸索。在重写dice智能合约的过程中,我学到了很多C++ 11的编程技巧。
比如DAWN 3.0 使用eosio::multi_index作为容器,这大大方便了开发。
如:

struct account {
 account( account_name o = account_name() ):owner(o){}

 account_name owner;
 asset        eos_balance;
 uint32_t     open_offers = 0;
 uint32_t     open_games = 0;

 bool is_empty()const { return !( eos_balance.amount | open_offers | open_games ); }

 uint64_t primary_key()const { return owner; }

 EOSLIB_SERIALIZE( account, (owner)(eos_balance)(open_offers)(open_games) )
};

typedef eosio::multi_index< N(account), account> account_index;

account_index     accounts;

有了eosio::multi_index,我们可以使用emplace来插入数据,使用modify来修改数据,使用erase删除数据。

还有更加精妙的例子:

struct offer {
 uint64_t          id;
 account_name      owner;
 asset             bet;
 checksum256       commitment;
 uint64_t          gameid = 0;

 uint64_t primary_key()const { return id; }

 uint64_t by_bet()const { return (uint64_t)bet.amount; }

 key256 by_commitment()const { return get_commitment(commitment); }

 static key256 get_commitment(const checksum256& commitment) {
    const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&commitment);
    return key256::make_from_word_sequence<uint64_t>(p64[0], p64[1], p64[2], p64[3]);
 }

 EOSLIB_SERIALIZE( offer, (id)(owner)(bet)(commitment)(gameid) )
};

typedef eosio::multi_index< N(offer), offer,
 indexed_by< N(bet), const_mem_fun<offer, uint64_t, &offer::by_bet > >,
 indexed_by< N(commitment), const_mem_fun<offer, key256,  &offer::by_commitment> >
> offer_index;

offer_index       offers;

offer_index类型既可以通过bet来索引,也可以通过commitment来索引。
例如在上面的offerbet函数中,使用bet进行索引

auto idx = offers.template get_index<N(bet)>();
auto matched_offer_itr = idx.lower_bound( (uint64_t)new_offer_itr->bet.amount );

在上面的reveal函数中,则用commitment来索引

 auto idx = offers.template get_index<N(commitment)>();
 auto curr_revealer_offer = idx.find( offer::get_commitment(commitment)  );

区块链的学习道路还很长,这一篇就到这里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容