4.2 토큰을 활용한 가위바위보 게임

토큰 컨트랙트를 응용해서 토큰을 활용한 간단한 가위바위보 게임을 만들겠습니다.

가위바위보 게임 스마트 컨트랙트 작성하기

규칙은 사용자가 토큰을 걸고 가위 바위 보 중에 하나를 입력하면 컨트랙트의 가위 바위 보와 비교하여 이기면 토큰의 3배를 가지게 되고 비기거나 지면 토큰을 잃게 되는 방식입니다.

play.hpp 작성

https://github.com/DevToothCrew/eos_book_example/blob/master/rock%2Cscissor%2Cpaper/play.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 

#include 

namespace eosio {
  
   using std::string;

   class token : public contract {
       public:
       account_name _owner;

       token(account_name self):contract(self){
           // Init _owner
           _owner = self;

       }

       //@abi action
       void create(account_name issuer, asset maximum_supply);
       //@abi action   
       void issue(account_name to, asset quantity, string memo);
       //@abi action
       void transfer(account_name from, account_name to, asset quantity, string memo);
       //@abi action
       void fight(account_name payer, asset quantity, string type);
      

       private:
           //@abi table accounts i64
           struct account {
               asset balance;

               uint64_t primary_key()const { return balance.symbol.name(); }
           };
           //@abi table stat i64
           struct currencies {
               asset        supply;
               asset        max_supply;
               account_name issuer;

               uint64_t primary_key()const { return supply.symbol.name(); }
           };
           //@abi table scores i64
           struct score {
               account_name user;
               string text;
               asset amount;

               uint64_t primary_key()const { return user; }
           };

           typedef eosio::multi_index accounts;
           typedef eosio::multi_index stats;
           typedef eosio::multi_index scores;

           void sub_balance( account_name owner, asset value );
           void add_balance( account_name owner, asset value, account_name ram_payer );
           void win( account_name winner, asset value );
           void lose( account_name looser, asset value);
   };
}

토큰 컨트랙트와 비교하여 몇가지 추가된것만 알아보겠습니다.

#include <eosiolib/transaction.hpp> #include <eosiolib/crypto.h> //@abi action void fight(account_name payer, asset quantity, string type); //@abi table scores i64 struct score { account_name user; string text; asset amount; uint64_t primary_key()const { return user; } }; void win( account_name winner, asset value ); void lose( account_name looser, asset value);

play.hpp 작성

https://github.com/DevToothCrew/eos_book_example/blob/master/rock%2Cscissor%2Cpaper/play.cpp

#include "play.hpp"
namespace eosio {

   void token::create(account_name issuer, asset maximum_supply)
   {
       require_auth(_self);

       auto sym = maximum_supply.symbol;
       eosio_assert(sym.is_valid(), "Invalid symbol name");
       eosio_assert(maximum_supply.is_valid(), "Invalid Supply");
       eosio_assert(maximum_supply.amount > 0, "Max-supply must be positive");

       stats statstable(_self, sym.name());
       auto existing = statstable.find(sym.name());
       eosio_assert(existing == statstable.end(), "Tokenwith symbol already exists");

       statstable.emplace(_self, [&](auto& s){
           s.supply.symbol = maximum_supply.symbol;
           s.max_supply    = maximum_supply;
           s.issuer        = issuer;
       });
   }

   void token::issue(account_name to, asset quantity, string memo)
   {
       auto sym = quantity.symbol;
       eosio_assert(sym.is_valid(), "Invalid symbol name");
       eosio_assert(memo.size() <= 256, "Memo has more than 256 bytes");

       auto sym_name = sym.name();
       stats statstable(_self, sym_name);
       auto existing = statstable.find(sym_name);
       eosio_assert(existing != statstable.end(), "Token with symbol does now exist, Create token before issue");
       const auto& st = *existing;

       require_auth(st.issuer);
       eosio_assert(quantity.is_valid(), "Invalid quantity");
       eosio_assert(quantity.amount > 0, "Must issue positive quantity");

       eosio_assert(quantity.symbol == st.supply.symbol, "Symbol precision mismatch");
       eosio_assert(quantity.amount <= st.max_supply.amount - st.supply.amount, "Quantity exceeds available supply");

       statstable.modify(st, 0, [&](auto& s){
           s.supply += quantity;
       });

       add_balance(st.issuer, quantity, st.issuer);

       if(to != st.issuer){
           SEND_INLINE_ACTION(*this, transfer, {st.issuer, N(active)}, {st.issuer, to, quantity, memo});
       }
   }

   void token::transfer(account_name from, account_name to, asset quantity, string memo)
   {
       eosio_assert(from != to, "Cannot transfer to self");
       require_auth(from);
       eosio_assert(is_account(to), "To account does not exist");
       auto sym = quantity.symbol.name();
       stats statstable(_self, sym);
       const auto& st = statstable.get(sym, "Not exist symbol");

       require_recipient(from);
       require_recipient(to);

       eosio_assert(quantity.is_valid(), "Invalid quantity");
       eosio_assert(quantity.amount > 0, "Must transfer positive quantity");
       eosio_assert(quantity.symbol == st.supply.symbol, "Symbol precision mismatch");
       eosio_assert(memo.size() <= 250, "Memo has more than 256 bytes");

       sub_balance(from, quantity);
       add_balance(to, quantity, from);
   }

   void token::sub_balance(account_name owner, asset value)
   {
       accounts from_acnts(_self, owner);

       const auto& from = from_acnts.get(value.symbol.name(), "No balance object found");
       eosio_assert(from.balance.amount >= value.amount, "Overdrawn balance");

       if(from.balance.amount == value.amount){
           from_acnts.erase(from);
       }
       else{
           from_acnts.modify(from, owner, [&](auto& a){
               a.balance -= value;
           });
       }
   }

   void token::add_balance(account_name owner, asset value, account_name ram_payer)
   {
       accounts to_acnts(_self, owner);
       auto to = to_acnts.find(value.symbol.name());
       if(to == to_acnts.end()){
           to_acnts.emplace( ram_payer, [&](auto& a){
               a.balance = value;
           });
       }
       else{
           to_acnts.modify(to, 0, [&](auto& a){
               a.balance += value;
           });
       }
   }

   void token::win(account_name winner, asset value)
   {
       asset result = (value * 3);

       scores print_result(_self, winner);
       auto existing = print_result.find(winner);
       if(existing == print_result.end()){
           print_result.emplace(winner, [&](auto& s){
               s.user = winner;
               s.text = "Win! You get ";
               s.amount = result;
           });       
       }
       else{
           print_result.modify(existing, 0, [&](auto& s){
               s.text = "Win! You get ";
               s.amount = result;
           });
       }       

       // Add winner's balance
       accounts winner_acnts(_self, winner);
       const auto& to = winner_acnts.get(value.symbol.name(), "No balance object found");
       winner_acnts.modify(to, 0, [&](auto& a){
           print("     * Win! You get ", result);
           a.balance += result;
           print(" Now your balance ", a.balance);
       });

       // Sub issuer's balance
       accounts owner_acnts(_self, _owner);
       const auto& owner = owner_acnts.get(value.symbol.name(), "No balance object found");
       eosio_assert(owner.balance.amount >= result.amount, "error");
       owner_acnts.modify(owner, 0, [&](auto& a){
           a.balance -= result;
       });
   }

   void token::lose( account_name looser, asset value)
   {
       scores print_result(_self, looser);
       auto existing = print_result.find(looser);
       if(existing == print_result.end()){
           print_result.emplace(looser, [&](auto& s){
               s.user = looser;
               s.text = "You lost ";
               s.amount = value;
           });       
       }
       else{
           print_result.modify(existing, 0, [&](auto& s){
               s.text = "You lost ";
               s.amount = value;
           });
       }
       // Sub looser's balance
       accounts looser_acnts(_self, looser);
       const auto& to = looser_acnts.get(value.symbol.name(), "No balance object found");
       looser_acnts.modify(to, 0, [&](auto& a){
           print("     * You Lost ", value);
           a.balance -= value;
           print(" Now your balance ", a.balance);
       });   

       //Add issuers's balance
       accounts owner_acnts(_self, _owner);
       const auto& owner = owner_acnts.get(value.symbol.name(), "No balance object found");
       owner_acnts.modify(owner, 0, [&](auto& a){
           a.balance += value;
       });
   }

   void token::fight(account_name payer, asset quantity, string type)
   {
       accounts from_acnts(_self, payer);

       const auto& from = from_acnts.get(quantity.symbol.name(), "No balance object found");

       eosio_assert(from.balance.amount >= quantity.amount, "Overdrawn balance");

       print("You has ", from.balance);

       checksum256 result;
       uint64_t source = tapos_block_num() * tapos_block_prefix();
       sha256((char *)&source, sizeof(source), &result);
       uint64_t* p = reinterpret_cast(&result.hash);
       uint64_t randNum = *p % 3;
       if(randNum==0)
       {
       print("     * Result : Rock ");
       }
       if(randNum==1)
       {
       print("     * Result : Paper ");
       }
       if(randNum==2)
       {
       print("     * Result : Scissors ");
       }
       if(type == "rock"){
           if( 2 == randNum){
               win(payer, quantity);
           }
           else{
               lose(payer, quantity);
           }
       }
       if(type == "paper"){
           if( 0 == randNum){
               win(payer, quantity);
           }
           else{
               lose(payer, quantity);
           }
       }
       if(type == "scissors"){
           if( 1 == randNum){
               win(payer, quantity);
               }
          
           else{
               lose(payer, quantity);
           }
       }
       else
       {
           print("Parameter Only 'rock', 'paper', 'scissors'.");
       }
   }
}

EOSIO_ABI( eosio::token, (create)(issue)(transfer)(fight) )

기존의 토큰 컨트랙트에서 가위바위보 게임을 플레이하기 위한 함수를 추가했습니다.

void token::win(account_name winner, asset value) asset result = (value * 3); if(existing == print_result.end()){ print_result.emplace(winner, [&](auto& s){ s.user = winner; s.text = "Win! You get "; s.amount = result; }); } else{ print_result.modify(existing, 0, [&](auto& s){ s.text = "Win! You get "; s.amount = result; }); } // Add winner's balance accounts winner_acnts(_self, winner); const auto& to = winner_acnts.get(value.symbol.name(), "No balance object found"); winner_acnts.modify(to, 0, [&](auto& a){ print(" * Win! You get ", result); a.balance += result; print(" Now your balance ", a.balance); }); // Sub issuer's balance accounts owner_acnts(_self, _owner); const auto& owner = owner_acnts.get(value.symbol.name(), "No balance object found"); eosio_assert(owner.balance.amount >= result.amount, "error"); owner_acnts.modify(owner, 0, [&](auto& a){ a.balance -= result; }); void token::lose( account_name looser, asset value) void token::fight(account_name payer, asset quantity, string type) accounts from_acnts(_self, payer); const auto& from = from_acnts.get(quantity.symbol.name(), "No balance object found"); eosio_assert(from.balance.amount >= quantity.amount, "Overdrawn balance"); checksum256 result; uint64_t source = tapos_block_num() * tapos_block_prefix(); sha256((char *)&source, sizeof(source), &result); uint64_t* p = reinterpret_cast<uint64_t*>(&result.hash); uint64_t randNum = *p % 3; EOSIO_ABI( eosio::token, (create)(issue)(transfer)(fight) )

가위바위보 게임 실행하기

작성한 가위바위보 게임을 간단히 실행해보겠습니다.
cpp 파일과 hpp 파일을 각각 wasm 파일과 abi 파일로 만듭니다.

$ eosiocpp -o play.wasm play.cpp $ eosiocpp -g play.abi play.hpp

 < play.wasm, play.abi 파일 생성  > 

devtooth@DESKTOP-2 :~$cd play
devtooth@DESKTOP-2 :~/play$ eosiocpp -g play.abi play.hpp
3189920ms thread-0 abi_generator.hpp:68	ricardian_contracts  ] Warning, no ricardian clauses found for

Generated play.abi . . . 
devtooth@DESKTOP-2 :~$ eosiocpp -o play.wasm play.cpp
devtooth@DESKTOP-2 :~$

다음 wasm 파일과 abi 파일을 cleos set contract 명령어로 배포합니다. 이번엔 폴더 채로 배포합니다.

$ cleos set contract devtooth ./play

 < play 컨트랙트 배포 결과 화면 > 

devtooth@DESKTOP-2 :~$ cleos set contract devtooth ./play
Reading WAST/WASM from ./play/play.wasm. . . 
Using already assembled WASM. . .
Publishing contract. . . 
executed transaction: cfb67f0cccdd637d2a2463351403a1b6faf57384feb783f8c2a616d1920f6228   11032 bytes  1102 us
#	eosio <= eosio::setcode		{"account":"devtooth","vmtype":0,"vmversion":0,"code":"0061736d01
0000000198011860037f7e7f0060057. . . 
#	eosio <= eosio::setabi		{"account":"devtooth","abi":"0e656f73696f3a3a6162692f312e30000707
6163636f756e7400010762616c616e6. . . 
warning: transaction executed locally, but may not be confirmed by the network yet
devtooth@DESKTOP-2 :~$

이후 토큰 컨트랙트에서 했던 create / issuer / transfer를 통해 devtooth로 토큰을 발행 한 후 devtooth2에게 게임을 하기에 충분한 토큰의 양을 지급합니다.
cleos push action을 통해 가위바위보 게임을 실행합니다.

$ cleos push action devtooth fight ‘[“devtooth2” “20.0000 DEV” “rock”]’ -p devtooth2

컨트랙트는 devtooth에 배포를 했고 devtooth2가 20.0000 DEV를 사용하여 rock으로 도전했습니다.

 < fight 액션 결과 > 

devtooth@DESKTOP-2 :~$ cleos get table devtooth hanbitmedia accounts
{
  “rows”: [{
	“balance”: “50000.000 AAA”
           },{
	“balance”: “300040.000 EPE”
	},{
	“balance”: “4960.000 DEV”
	}
          ],
       “more”: false
}
devtooth@DESKTOP-2 :~$ cleos push action devtooth bet ‘[“hanbitmedia” “20.000 DEV” “rock”]’ -p hanbitmedia
exceuted transaction: 06ba271d18f717eebcd268908128fa6e0995c6306b239bc5d3c715e819b67023  128 bytes  1813 us
# devtooth<=devtooth::bet	{“payer”:”hanbitmedia”,”quantity”:”20.000 DEV”,”betType”:”rock”}
>> You has 4960.000 DEV	* Result : Rock	* You Lost 20.000 DEV Now your balance 4940.000 DEV Parameter Only ‘rock’, ‘paper’, ‘scissors’ .
warning: transaction executed locally, but may not be confirmed by the network yet
devtooth@DESKTOP-2 :~$ cleos get table devtooth hanbitmedia accounts
{
  “rows”: [{
	“balance”: “50000.000 AAA”
           },{
	“balance”: “300040.000 EPE”
	},{
	“balance”: “4940.000 DEV”
	}
          ],
       “more”: false
}
---------------------------------------------------------------------------------------------------------------------------
131852ms thread-0   apply_context.cpp:28	print_debug                   ]
[(devtooth,bet)->devtooth]: CONSOLE OUTPUT BEGIN =================
You has 4960.000 DEV	*Result : Rock		* You Lost 20.000 DEV Now your balance 4940.000 DEV Parameter Only ‘rock’, ‘paper’, ‘scissors’.
[(devtooth,bet)->devtooth]: CONSOLE OUT END ======================

이겼는지 졌는지 블록에 정보를 확인을 할 수 있습니다. 또한 Table을 통해 자신의 balance의 변경이 되어있는지 확인 할 수 있습니다.

이번 가위바위보 게임을 통해 토큰 컨트랙트와 합쳐진 간단한 게임으로 쉽게 토큰을 컨트롤하는 방법에 대해 배웠습니다. 이를 통해 다양한 방법으로 토큰의 이동을 구현한 DApp을 만들 수 있습니다.


Prev | Next