4.1 테이블을 활용한 숫자 야구 게임

이번 장에서는 간단한 야구 게임을 만들어 보도록 하겠습니다.

야구 게임의 룰은 1~9로 이루어진 3자리 숫자를 맞추는 걸 목표로 합니다. 숫자를 제시하면 힌트를 줍니다.

숫자야구 게임 코드 작성하기

규칙은 다음과 같습니다.

자릿수와 숫자가 모두 일치한다면 Strike
자릿수는 일치하지 않지만 숫자가 일치한다면 Ball

해당 규칙을 생각하여 스마트 컨트랙트를 작성하겠습니다.

baseball.hpp 작성

https://github.com/DevToothCrew/eos_book_example/blob/master/baseball/baseball.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 

#include 

namespace eosio {
  
   using std::string;

   class baseball : public contract {
       public:
       account_name _owner;
       uint64_t num_table[504];

       baseball(account_name self):contract(self){
           // Init _owner
           _owner = self;
      
           // Init Number Table
           uint64_t temp = 0;
           for(uint64_t i=100 ; i < 1000; i++){
               if( (i/10)%11 != 0 ){
                   if( i/100 != i%10 ){
                       if( (i%100)%11 != 0 ){
                           if( i%10 != 0 ){
                               if( (i%100)/10 != 0 ){
                                   num_table[temp] = i;
                                   temp++;
                               }
                           }
                       }
                   }
               }
           }
       }

       //@abi action
       void start(account_name payer);
      
       //@abi action
       void throwball(account_name player,uint64_t value);

       void matchnum(uint64_t num, uint64_t array[]);
       void createnum(account_name player);


       private:
           //@abi table scores i64
           struct score {
               account_name user;
               uint64_t num;
               uint64_t primary_key()const { return user; }
           };

           typedef eosio::multi_index scores;
   };
}
#pragma once #include <eosiolib/asset.hpp> #include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> #include <eosiolib/transaction.hpp> #include <eosiolib/crypto.h> #include <string> account_name _owner; uint64_t num_table[504];
// Init Number Table
uint64_t temp = 0;
           for(uint64_t i=100 ; i < 1000; i++){
               if( (i/10)%11 != 0 ){
                   if( i/100 != i%10 ){
                       if( (i%100)%11 != 0 ){
                           if( i%10 != 0 ){
                               if( (i%100)/10 != 0 ){
                                   num_table[temp] = i;
                                   temp++;
                               }
                           }
                       }
                   }
               }
           }
자리가 중복되지 않는 숫자 503개를 테이블에 저장합니다.

//@abi action
void start(account_name payer);
//@abi action
void throwball(account_name player,uint64_t value); void matchnum(uint64_t num, uint64_t array[]); void createnum(account_name player);
//@abi table scores i64
           struct score {
               account_name user;
               uint64_t num;
               uint64_t primary_key()const { return user; }
           };

baseball.cpp 작성

https://github.com/DevToothCrew/eos_book_example/blob/master/baseball/baseball.cpp

#include "baseball.hpp"
namespace eosio {

   void baseball::start(account_name player)
   {
       print("Baseball game start!!!");
       print("\n create goal number");

       baseball::createnum(player);       
   }

   void  baseball::throwball(account_name player,uint64_t value )
   {
       scores goalnum(_self,player);
       const auto& data = goalnum.get(player,"No Goal Num");
       uint64_t num = data.num;
       uint64_t strike = 0, ball = 0;
       uint64_t goalarray[3];
       uint64_t playervalue[3];

      matchnum(num, goalarray);
      matchnum(value, playervalue);

       for(int i=0; i<3;i++)
       {
           for(int j=0; j<3; j++)
           {
               if(goalarray[i]==playervalue[j]&& i==j)
               {
                   strike++;
               }
               if(goalarray[i]==playervalue[j]&& i!= j)
               {
                   ball++;
               }
           }
       }
  
       if(strike==3)
       {
           print("Homerun!! You Win.\n");
       }
       else
       {
           print("  Strike : ", strike, "   Ball : ", ball);
       }
   }

   void baseball::createnum(account_name player)
   {
       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 % 504);

       scores num(_self, player);
       auto iter=num.find(player);
       if(iter==num.end())
       {
           num.emplace(_self,[&](auto& s){
               s.user = player;
               s.num = num_table[randNum];
           });
       }
       else{
           num.modify(iter,_self,[&](auto& s){
               s.num = num_table[randNum];
           });
       }
   }

   void baseball::matchnum(uint64_t num, uint64_t array[])
   {
       array[0] = num/100;
       array[1] = num/10;
       array[1] = array[1]%10;
       array[2] = num%10;
   }
}

EOSIO_ABI( eosio::baseball, (start)(throwball) )
#include "baseball.hpp" void baseball::start(account_name player) { print("Baseball game start!!!"); print("\n create goal number"); baseball::createnum(player); } void baseball::throwball(account_name player,uint64_t value ) scores goalnum(_self,player); const auto& data = goalnum.get(player,"No Goal Num"); uint64_t num = data.num; uint64_t strike = 0, ball = 0; uint64_t goalarray[3]; uint64_t playervalue[3]; matchnum(num, goalarray); matchnum(value, playervalue); for(int i=0; i<3;i++) { for(int j=0; j<3; j++) { if(goalarray[i]==playervalue[j]&& i==j) { strike++; } if(goalarray[i]==playervalue[j]&& i!= j) { ball++; } } } if(strike==3) { print("Homerun!! You Win.\n"); } else { print(" Strike : ", strike, " Ball : ", ball); } void baseball::createnum(account_name player) 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 % 504); scores num(_self, player); auto iter=num.find(player); if(iter==num.end()) { num.emplace(_self,[&](auto& s){ s.user = player; s.num = num_table[randNum]; }); } else{ num.modify(iter,_self,[&](auto& s){ s.num = num_table[randNum]; }); } void baseball::matchnum(uint64_t num, uint64_t array[]) { array[0] = num/100; array[1] = num/10; array[1] = array[1]%10; array[2] = num%10; } EOSIO_ABI( eosio::baseball, (start)(throwball) )

숫자야구 게임 실행하기

작성한 cpp파일로 wasm파일을, hpp파일로 abi로 만들어 줍니다.

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

 < baseball.wasm, baseball.abi 파일 생성 화면 > 

devtooth@DESKTOP-2 :~/baseball$ eosiocpp -o baseball.wasm baseball.cpp devtooth@DESKTOP-2 :~/baseball$ eosiocpp -g baseball.abi baseball.hpp 1138431ms thread-0 abi_generator.hpp:68 ricardian_contracts ] Warning, no ricardian clauses found for Genereated baseball.abi. . . devtooth@DESKTOP-2 :~/baseball$

만들어진 wasm파일과 abi파일을 배포합니다.

$ cleos set contract devtooth ./ ./baseball.wasm ./baseball.abi

 < baseball 컨트랙트 배포 > 

Using already assembled WASM. . . Publishing contract. . . executed transaction: 6e7587d0751ed46b2b9873efee39a8a9fb2c598ebac7cae600840c4fdb28d01d 3648bytes 419 us # eosio <= eosio::setcode {"account":"devtooth","vmtype":0,"vmversion":0,"code": "0061736d0100000001741460027f7e0060037f7e7. . . # eosio <= eosio::setabi {"account:"devtooth","abi": "0e656f73696f3a3a6162692f312e3000030573636f726500020475736572046e616. . . warning: transaction executed locally, but may not be confirmed by the network yet devtooth@DESKTOP-2 :~$

cleos push action 명령어로 Start 함수를 실행시켜 게임을 시작합니다.

$ cleos push action devtooth start ‘[“dvetooth”]’ -p dvetooth

 < start 액션 실행 결과 > 

devtooth@DESKTOP-2 :~$ cleos push action devtooth start '["devtooth"]' -p devtooth executed transaction: a3c4ce60cb3a1ffc2accd6bdb73f9d315929a98807dac7e99ca0084ea5d1ce4d8 104 bytes 3162 us # devtooth <= devtooth::start {"payer":"devtooth"} >>Baseball game start!!! warning: transaction executed locally, but may not be confirmed by the network yet devtooth@DESKTOP-2 :~$

throwball함수를 실행하여 숫자를 맞춰봅니다.

$ cleos push action devtooth throwball ‘[“devtooth” 123]’ -p devtooth

 < throwball 액션 실행 결과 > 

devtooth@DESKTOP-2 :~$ cleos push action devtooth throwball '["devtooth" 123]' -p devtooth executed transaction: 1b8ccf5a0daac69fa69475b08cfd03dedd42725dac290bef4a41bade420ddaed 112 bytes 3027 us # devtooth <= devtooth::throwball {"player":"devtooth","value":123} >> Strike: 0 Ball : 1 warning: transaction executed locally, but may not be confirmed by the network yet devtooth@DESKTOP-2 :~$ cleos push action devtooth throwball '["devtooth" 456]' -p devtooth executed transaction: 739fea2f31115be810f6796e3bdbda429f531a0991839c29ff5d277772c9ac289 112 bytes 2935 us # devtooth <= devtooth::throwball {"player":"devtooth","value":456} >> Strike: 1 Ball : 0 warning: transaction executed locally, but may not be confirmed by the network yet devtooth@DESKTOP-2 :~$ cleos push action devtooth throwball '["devtooth" 751]' -p devtooth executed transaction: 9a9576efbbfc390804762a59970107bd1d19009e7c962b1d2f279b396b422e1 112 bytes 2965 us # devtooth <= devtooth::throwball {"player":"devtooth","value":751} >> Strike: 2 Ball : 0 warning: transaction executed locally, but may not be confirmed by the network yet

strike에 성공했다면 socres 테이블을 조회하여 결괏값이 맞는지 확인합니다.

$ cleos get table devtooth devtooth scores

 < scores 테이블 정보 확인 > 

devtooth@DESKTOP-2 :~$ cleos get table devtooth devtooth scores { "rows" : [{ "user" : "devtooth", "num" : 752 } ], "more": false } devtooth@DESKTOP-2 :~$

SmartContract로 간단한 게임을 만들어 보았습니다. 위 게임을 통해 우리는 컨트랙트 내부에서만 사용하는 함수, 컨트랙트 생성자 초기화, 오류 텍스트를 띄우는 방법, 난수 를 생성하는 방법, 함수를 실행하여 결괏값을 얻는 방법을 익혔습니다. 퍼블릭 블록체인의 특징은 모두 다 보여진다 입니다. 결국 해당 게임은 간단한 테이블 조회를 통해 답을 쉽게 찾을 수 있습니다.


Prev | Next