토큰 컨트랙트를 응용해서 토큰을 활용한 간단한 가위바위보 게임을 만들겠습니다.
규칙은 사용자가 토큰을 걸고 가위 바위 보 중에 하나를 입력하면 컨트랙트의 가위 바위 보와 비교하여 이기면 토큰의 3배를 가지게 되고 비기거나 지면 토큰을 잃게 되는 방식입니다.
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); }; }
토큰 컨트랙트와 비교하여 몇가지 추가된것만 알아보겠습니다.
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) )
기존의 토큰 컨트랙트에서 가위바위보 게임을 플레이하기 위한 함수를 추가했습니다.
작성한 가위바위보 게임을 간단히 실행해보겠습니다.
cpp 파일과 hpp 파일을 각각 wasm 파일과 abi 파일로 만듭니다.
< 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 명령어로 배포합니다. 이번엔 폴더 채로 배포합니다.
< 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을 통해 가위바위보 게임을 실행합니다.
컨트랙트는 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을 만들 수 있습니다.