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
#include
#include
#include
#include
#include
- - EOSIO에서 제공하는 헤더 파일을 불러옵니다. 난수 생성을 위한 crypto와 transaction 헤더파일을 추가했습니다.
account_name _owner;
- - 배포할 계정을 저장하기위한 변수를 선언합니다.
uint64_t num_table[504];
- - 코드를 간단하게 짜기 위한 정답이 될 숫자를 저장할 table을 생성합니다
- - 999까지의 숫자중에 0이없고 같은 숫자가 중복되지 않는 숫자가 503개입니다.
- - table의 크기를 503 + 1로 설정합니다.
// 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);
- - action으로 사용할 함수를 정의합니다.
void matchnum(uint64_t num, uint64_t array[]);
void createnum(account_name player);
- - action으로는 사용하지 않지만 Contract내부에서 사용될 함수를 정의합니다.
//@abi table scores i64
struct score {
account_name user;
uint64_t num;
uint64_t primary_key()const { return user; }
};
- - 계정의 정보와 정답을 포함할 table을 정의합니다.
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"
- - 함수 및 테이블을 정의했던 baseball.hpp 파일을 불러옵니다.
void baseball::start(account_name player)
{
print("Baseball game start!!!");
print("\n create goal number");
baseball::createnum(player);
}
- - 야구 게임을 시작 하는 start 함수 입니다. 인자값으로 게임을 실행할 계정정보를 받아오면 간단한 텍스트가 출력이되고 createnum함수에 계정정보를 입력하여 그 플레이할 계정 테이블에 정답 숫자를 입력합니다.
void baseball::throwball(account_name player,uint64_t value )
- - 게임을 플레이할 throwball 함수입니다. 인자값으로 계정정보와 유추할 숫자를 입력합니다.
scores goalnum(_self,player);
- - 플레이하는 유저의 socres 테이블을 가져옵니다.
const auto& data = goalnum.get(player,"No Goal Num");
- - 가져온 유저의 테이블 정보를 data라는 변수에 가져옵니다. 만약 테이블이 비어있다면 “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);
- - 정답이되는 숫자와 유저가 기입한 숫자를 18~19줄에 선언한 배열에 자릿수를 나누어서 입력합니다.
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++;
}
}
}
- - 정답과 유저가 기입한 숫자를 비교해 자릿수와 숫자가 같다면 strike 변수를 증가 시키고, 숫자는 같지만 자릿수가 다르다면 ball 변수를 증가 시킵니다.
if(strike==3)
{
print("Homerun!! You Win.\n");
}
else
{
print(" Strike : ", strike, " Ball : ", ball);
}
- - strike가 3이라면 게임에 승리하게 되고, 그렇지 않다면 Strike과 Ball이 몇인지 출력해 줍니다.
void baseball::createnum(account_name player)
- - 정답이 될 숫자를 랜덤으로 생성해주는 createnum 함수입니다.
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);
- - Crypto.h에 있는 sha256 함수를 사용해 현재 실행중인 트랜잭션에서 TAPOS에 사용된 블록 번호와 사용된 블록의 접두사를 곱한 숫자를 504로 나누어 503가지 숫자로 RandNum 을 표현합니다.
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];
});
}
- - Contract에 숫자를 저장하기 위해서 scores 테이블에 생성된 난수를 저장합니다. 테이블에 num이 없을경우에는 emplace로 생성된 난수를 채워주고, 테이블에 저장된 num이 있을 경우에는 modify함수로 변경해 줍니다.
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;
}
- - 3자리 숫자를 크기가 3인 배열에 각 자릿수 마다 넣어주는 함수 입니다.
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