美文网首页EOS Dice Game
玩eos上的掷骰子游戏---dice智能合约

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

作者: 钟晓宏 | 来源:发表于2018-04-28 17:00 被阅读0次

    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)  );
    

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

    相关文章

      网友评论

        本文标题:玩eos上的掷骰子游戏---dice智能合约

        本文链接:https://www.haomeiwen.com/subject/pzeglftx.html