[solidity] 가위바위보 게임

워뇽쿤·2022년 9월 26일
0

Solidity

목록 보기
5/10
post-thumbnail

1. 설명

  • 방장이 방을 생성함
  • 참가자는 방에 입장함
    • 참가자는 참여할 방번호, 가위/바위/보 값과 배팅금액을 넘겨줌
    • 입장하면 방장과 참가자가 낸 가위/바위/보 값에 따라 결과를 냄
  • 방장 또는 참가자가 결과반환을 요청하면 결과에 따라 송금함

2. 코드

필요한 구조체 선언

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract RPS {
    // constructor () payble {}    // 송금전달

    // 플레이어 구조체 만들기
    // 플레이어의 주소와 배팅 금액을 알고 있어야 한다.

    enum Hand {
        // 가위/바위/보 값에 대한 enum
        rock,
        paper,
        scissors
        // 주먹 , 보자기, 가위
    }

    enum PlayerStatus {
        // 플레이어의 상태
        STATUS_WIN,
        STATUS_LOST,
        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 {
        // 게임 방 정보
        Player originator; // 방장 정보
        Player taker; // 참여자 정보
        uint256 betAmount; // 총 배팅 금액
        GameStatus gameStatus; // 게임의 상태
    }
    
   mapping(uint256 => Game) rooms; // rooms[0], rooms[1] 형식으로 접근 가능
   uint256 roomLen = 0; // rooms의 키 값, 방이 생성될떄마다 +1 될 예정

게임 생성 및 게임 입장

    // 방이 만들어지기전에 방장이 낸 가위/바위/보 값이 올바른지 확인
    modifier isValidHand(Hand _hand) {
        // 함수제어자
        require(
            (_hand == Hand.rock) ||
                (_hand == Hand.paper) ||
                (_hand == Hand.scissors)
        ); // 에러헨들링 조건이 참이면 넘어가고 거짓이면 에러출력
        _; // 함수실행
    }

    function createRoom(Hand _hand)
        public
        payable
        isValidHand(_hand)
        returns (uint256 roomNum)
    {
        // 배팅 금액을 성정하기 때문에 payble 사용

        // batAmount  : 아직 방장만 잇기 때문에 방장의 배팅 금액을 넣는다
        // gameStatus : 아직 시작하지 않은 상태이기 때문에 GameStatus.STATUS_NOT_STARTED 값을 넢음
        // originator : Play 구조체의 인스턴스를 만들어 방장의 정보를 넣어준다
        // taker : Player 구조체 형식의 데이터로 초기화되어야 하므로 addr에는 방장의 주소를 , hand 는 Hand.rock으로 할당한다.
        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 // 참여자 배팅 금액 (모르니까 0으로 설정)
            })
        });
        roomNum = roomLen; // roomNum을 반환
        roomLen = roomLen + 1; // 다음 방번호를 설정
    }

    // 기존에 만들어진 방에 참가함
    // 참가자는 방번호와, 자신이 낼 값을 인자로 보냄
    function joinRoom(uint256 roomNum, Hand _hand)
        public
        payable
        isValidHand(_hand)
    {
        rooms[roomNum].taker = Player({
            hand: _hand,
            addr: payable(msg.sender),
            playerStatus: PlayerStatus.STATUS_PENDING,
            playerBetAmount: msg.value
        });
        // 참가자가 참여하면서 게임의 배팅 금액이 추가되어서 Game의 betAmount 도 변경해줌 기존의 배팅금액 + 참가자 배팅금액
        rooms[roomNum].betAmount = rooms[roomNum].betAmount + msg.value;
    }

게임결과 업데이트

    // 게임결과 업데이트
    // joinRoom함수가 끝나는 시점에서 방장이랑 참가자 모두 가위바위보값을 냈기 떄문에 게임의 승패를 확인 할 수 있다.
    function compareHands(uint256 roomNum) private {
        uint8 originator = uint8(rooms[roomNum].originator.hand);
        uint8 taker = uint8(rooms[roomNum].taker.hand);

        rooms[roomNum].gameStatus = GameStatus.STATUS_STARTED;

        // if((takerHand + 1) % 3 == originatorhand){
        //     // 주먹 0, 보 1, 가위 2
        //     // 0+1 % 3 == 1
        //     // 1+1 % 3 == 2
        //     // 2+1 % 3 == 0
        // }
        if (taker == originator) {
            // 비긴경우
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_TIE;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_TIE;
        } else if ((taker + 1) % 3 == originator) {
            // 방장이 이긴경우
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_WIN;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_LOST;
        } else if ((originator + 1) % 3 == taker) {
            // 참가자가 이긴경우
            rooms[roomNum].originator.playerStatus = PlayerStatus.STATUS_LOST;
            rooms[roomNum].taker.playerStatus = PlayerStatus.STATUS_WIN;
        } else {
            // 그 외의 경우는 에러임
            rooms[roomNum].gameStatus = GameStatus.STATUS_ERROR;
        }
    }

배팅 금액 확인 기능

    // 배팅 금액 확인
    function checkTotalPay(uint256 roomNum)
        public
        view
        returns (uint256 roomNumPay)
    {
        return rooms[roomNum].betAmount;
    }

배팅 금액 송금하기

    // 참가자 확인
    modifier isPlayer(uint256 roomNum, address sender) {
        require(
            sender == rooms[roomNum].originator.addr ||
                sender == rooms[roomNum].taker.addr
        );
        _;
    }
    
    // 배팅 금액 송금하기
    function payout(uint256 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; // 게임이 종료 되었으므로 게임 상태 변경
    }
}

3. 느낀점

이번 챕터 진행하면서 오?! 솔리디티 재밌는데?와 신기하다?! 를 느꼈다.. 예전에 한참 코딩할때 밤샘하면서 개발하던 그때가 노스텔지어처럼 지나갔다... 지금도 그렇게 해야하는게 맞지만...(잠이오냐 워뇽아) 노후된 하드웨어(몸뚱이)는 방전이 빨리 되는거 같다...
이번 챕터 하면서 페어분들에게 너무 기초적인것부터 물어봐서,, 죄송스럽기도 했고 감사하기도,,(많이) 했다.
다들 이렇게 열심히하는데 나만 뒤쳐진거 같기도하고,,, 더 분발해야겠다.

  • 추가
    방장의 값을 노출시키지 않는 방법을 공부하라고 예제가 추가로 있는데, keccak로 암호화를 해주면 된다고 한다. https://velog.io/@udt6236/rock-scissors-paper 이분이 작성한 코드를 한번더 확인해봐야겠다. (감사합니다)
profile
QA 성장기

0개의 댓글