토큰 컨트랙트를 응용해서 토큰을 활용한 간단한 가위바위보 게임을 만들겠습니다.
규칙은 사용자가 토큰을 걸고 가위 바위 보 중에 하나를 입력하면 컨트랙트의 가위 바위 보와 비교하여 이기면 토큰의 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을 만들 수 있습니다.