玩eos上的擲骰子游戲---dice智慧合約
阿新 • • 發佈:2019-01-09
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) );
學習區塊鏈的道路還很長,這一篇就到這裡。