이더리움 EVM과 Solidity - 복습

Lumi·2021년 11월 23일
0
post-thumbnail

저번글과 아마 거의 동일한 글이 될 것이다.

가위/바위/보 게임이 어떻게 작성되었는지를 다시 살펴보는 글이 될 것이고

이를 통해서 복습을 할 예정이다.

🔥 열거형 및 기본 셋팅

우리는 열거형이라는 것을 사용한다.

왜냐하면 개발자가 실수로 다른 값을 배정할 수도 있기 때문이다.

  • 오타를 막기 위해서이다.

열거형으로 선언해둔 인자들만 사용 가능하게 만들어서

오류를 줄이기 위해서 사용을 한다.

순서대로 낼수 있는 경우의 수 - 플레이어 승리 유무 - 게임 진행도 를 열거형으로 선언을 하였다.

    enum Hand {
        rock, paper, scissors
    }
    
    enum PlayerStatus{
        STATUS_WIN, STATUS_LOSE, STATUS_TIE, STATUS_PENDING
    }
    
    enum GameStatus {
        STATUS_NOT_STARTED, STATUS_STARTED, STATUS_COMPLETE, STATUS_ERROR
    }

🔥 플레이어와 게임방 구조체

우리는 플레이어의 상태와 게임방의 상태를 만들어 줘야 할 필요가 있다.

일단 플레이어들은 낼수 있는 경우의 수, 지갑의 주소, 승리유무, 금액의 데이터를 기본적으로 가지고 있어야 하며

게임방의 상태는 총 배팅된 금액, 게임 진행 상태, 반장 플레이어, 상대방 플레이어 가 필요할 것이다.

    struct Player {
        Hand hand;
        address payable addr;
        PlayerStatus playerStatus;
        uint256 playerBetAmount;
    }
    
    struct Game {
        uint256 betAmount;
        GameStatus gameStatus;
        Player originator;
        Player taker;
    }
  • 중요한점은 우리는 열거형을 선언하였기 떄문에 열거형을 사용을 해야 한다.

🔥 방 만들기와 검증 설정

우리는 플레이어의 상태와 게임방의 상태를 만들었다.

그러면 이제 mapping을 활용하여 키-값 의 형태로 방을 만들고 다른 함수에 적용할 modifier들을 설정해 주어야 한다.

    mapping(uint => Game) rooms;
    uint roomLen = 0;
    
    modifier isValidHand (Hand _hand) {
        require((_hand  == Hand.rock) || (_hand  == Hand.paper) || (_hand == Hand.scissors));
        _;
    }
    
    modifier isPlayer (uint roomNum, address sender) {
        require(sender == rooms[roomNum].originator.addr || sender == rooms[roomNum].taker.addr);
        _;
    }

rooms같은 경우에는 게임 방을 의미한다.

  • 방 번호는 후에 다루겠지만 한개가 만들어 질떄마다 숫자가 배정이 된다.
  • 값으로 구조체가 들어가기 떄문에 반드시 구조체에 맞는 값을 넣어주어야 한다.

isValidHand 는 낼수 있는 경우의 수가 정확하게 들어왔는지를 확인하는 것이다.

isPlayer 는 방에 참여하고 있는 사용자가 게임을 종료시키는지를 확인하는 것이다.

🔥 게임 방 만들기

기본적인 검증, 구조체, 게임방 등등은 설정을 완료하였으니 게임방을 만드는 함수를 살펴보자.

function createRoom (Hand _hand) public payable isValidHand(_hand) returns (uint roomNum) {
        rooms[roomLen] = Game({
            betAmount: msg.value,
            gameStatus: GameStatus.STATUS_NOT_STARTED,
            originator: Player({
                hand: _hand,
                addr: payable(msg.sender),
                playerStatus: PlayerStatus.STATUS_PENDING,
                playerBetAmount: msg.value
            }),
            taker: Player({ 
                hand: Hand.rock,
                addr: payable(msg.sender),  
                playerStatus: PlayerStatus.STATUS_PENDING,
                playerBetAmount: 0
            })
        });
        roomNum = roomLen;
        roomLen = roomLen+1;
    }

일단 누구나 방을 만들수 있게 public로 선언을 하였고

payable를 적용하여 이더를 받을수 있는 함수로 만들어 주었다.

또한 isValidHand 를 통해 먼저 적절한 경우의 수가 들어오는지를 확인한다.

그리고 방을 만들게 되면 roomNum을 return해주게 만들어 주었다.

일단 방을 만드는 것이기 떄문에 이전에 mapping으로 선언해둔 rooms를 활용을 하였다.

키값은 roomLen이 될 것이며 값은 Game 구조체가 될 것이다.

기본적으로 아직 방을 만드는 반장밖에 없기 떄문에 반장의 값만 해주면 된다.

  • 방을 만들면서 들어오는 배팅값(이더값)을 반장에 셋팅해두고 주소 또만 방을 만드는 반장의 지갑 주소를 설정해 둔다.
  • taker는 후에 설정이 될 것이다.

🔥 상대방이 게임방에 입장

이 부분에서 taker를 설정을 한다.

기존에 방을 만든 주체는 자신의 경우의 수를 미리 설정해 두었고

상대방은 이제 게임방에 입장을 하면서 자신의 경우의 수를 제시한다.

	function joinRoom(uint roomNum, Hand _hand) public payable isValidHand( _hand) {
        rooms[roomNum].taker = Player({
            hand: _hand,
            addr: payable(msg.sender),
            playerStatus: PlayerStatus.STATUS_PENDING,
            playerBetAmount: msg.value
        });
        rooms[roomNum].betAmount = rooms[roomNum].betAmount + msg.value;
        compareHands(roomNum);
    }

마찬가지로 payable, public, isValidHand를 적용을 해주었다.

이번에 들어오는 인자들은 방번호에 맞는 taker를 설정을 한다.

총 배팅금액에 taker의 금액을 합쳐주어야 하기 떄문에 갱신을 해주며

이후 compareHands(roomNum)을 실행시켜 방번호를 인자로 넘겨 주게 된다.

  • 이떄까지 진행이 되면 이제 게임방에 두 명이 존재하게 되고 두 사용자 모두 자신의 경우의 수를 제출한 상태이다.

🔥 승리 유무 확인

앞서 두 선수가 모두 입장을 하고 경우의 수를 냈기 떄문에

이후에는 누가 이겼는지를 판단해야 한다.

 function compareHands(uint roomNum) private{
        uint8 originator = uint8(rooms[roomNum].originator.hand);
        uint8 taker = uint8(rooms[roomNum].taker.hand);
        
        rooms[roomNum].gameStatus = GameStatus.STATUS_STARTED;
        
        if (taker == originator){ //draw
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_TIE;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_TIE;
            
        }
        else if ((taker +1) % 3 == originator) { // originator wins
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_WIN;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_LOSE;
        }
        else if ((originator + 1)%3 == taker){
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_LOSE;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_WIN;
        } else {
            rooms[roomNum].gameStatus = GameStatus.STATUS_ERROR;
        }
       
    }

일단 계산을 해주기 위해서 값을 변환을 해주어야 한다.

우리는 경우의 수를 열거형으로 선언을 하였기 떄문에 이를 uint8로 변환을 해주는 것이다.

  • 열거형의 경우 앞에서부터 0 ,1,2,3,4,... 이렇게 index가 부여가 된다.
  • 즉 주먹은 0, 보는 1, 가위는, 2 이다.

해당 게임방의 진행상태도를 갱신해 준뒤

누가 승리자 인지를 파악한다.
(taker/ originator +1) % 3 이 부분은 별다른 것이 아니라 단순히 누가 승리자인지를 보는 것인다.

  • 수학적 공식이다.
  • 여기까지 진행이 되면 누가 승리자인지 결정이 되었을 것이다.

🔥 보상 처리

우리는 두 사용자가 배팅한 금액을 승리자에게 전송을 해주어야 한다.

이떄 무승부면 다시 배팅한 금액만큼 자신이 가져가게 되고 승자가 결정된 경우에는 승자에게 배팅된 금액이 주어질 것이다.

function payout(uint roomNum) public payable isPlayer(roomNum, msg.sender) {
        if (rooms[roomNum].originator.playerStatus == PlayerStatus.STATUS_TIE && rooms[roomNum].taker.playerStatus == PlayerStatus.STATUS_TIE) {
            rooms[roomNum].originator.addr.transfer(rooms[roomNum].originator.playerBetAmount);
            rooms[roomNum].taker.addr.transfer(rooms[roomNum].taker.playerBetAmount);
        } else {
            if (rooms[roomNum].originator.playerStatus == PlayerStatus.STATUS_WIN) {
                rooms[roomNum].originator.addr.transfer(rooms[roomNum].betAmount);
            } else if (rooms[roomNum].taker.playerStatus == PlayerStatus.STATUS_WIN) {
                rooms[roomNum].taker.addr.transfer(rooms[roomNum].betAmount);
            } else {
                rooms[roomNum].originator.addr.transfer(rooms[roomNum].originator.playerBetAmount);
                rooms[roomNum].taker.addr.transfer(rooms[roomNum].taker.playerBetAmount);
            }
        }
         rooms[roomNum].gameStatus = GameStatus.STATUS_COMPLETE;
    }

이 함수를 작동시키는 주체는 방에 참여한 사용자중 한명이어야 하기 떄문에

isPlayer을 조건으로 설정해 두어야 한다.

transfer를 활용하여 배팅된 금액을 사용자들에게 돌려 줄수가 있다.

처음에는 일단 무승부를 확인하고 이후에는 누가 이겼는지를 판단하게 된다.

이 과정이 끝나게 되면 이제 게임방 상태를 갱신한뒤에 게임을 마무리 하는 것이다.

🔥 후기

다시 한번 살펴보면서 어떤 순서로 로직이 작성되었는지를 확인하였다.

흐름 순서 자체는 어렵지 않다.

하지만 아직 솔리디티에 많이 익숙하지 않아서인지 혼자서 작성을 하게 된다면 정말 오랜시간이 걸릴것같다..ㅠㅠ

계속해서 연습을 해 볼 것이고 그러다보면 언젠가는 익숙해 지지 않을까 싶다~~!!

profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글