Solidity - 가위바위보 게임 만들기

프동프동·2022년 6월 17일
0

솔리디티(Solidity)

목록 보기
7/20
post-thumbnail

가위바위보 게임 만들기

진행 방식

두 명의 플레이어가 가위바위보 게임을 진행하고, 이긴 경우 참여자의 베팅 금액 모두를 가져가는 방식

해당 컨트랙트는 3가지 Public 함수를 가지고 있습니다.

  • createRoom: 가위바위보 게임을 하기 위한 방을 만듭니다.
  • joinRoom : 만들어진 방에 참가합니다.
  • payout : 게임을 마칩니다. 게임의 결과에 따라 베팅 금액을 송금합니다.
  1. 방장(originaotr)가 createRoom을 호출합니다.
    1.1 방장은 인자로 자신이 낼 가위/바위/보 값과 베팅 금액을 넘겨줍니다.
    1.2 createRoom은 새로운 방을 만들고, 방의 번호를 리턴합니다.
  2. 참가자(taker)는 joinRoom을 호출합니다.
    1.1 참가자는 인자로 참여할 방 번호, 자신이 낼 가위/바위/보 값과 베팅 금액을 넘겨줍니다.
    1.2 joinRoom은 참자자를 방에 참여시킵니다.
    1.3 joinRoom은 방장과 참가자의 가위/바위/보 값을 확인하고 해당 방의 승자를 설정합니다.
  3. 방장 또는 참가자가 payout 함수를 호출합니다.
    3.1 인자로 게임을 끝낼 방 번호를 넘겨줍니다.
    3.2 게임의 결과에 따라 베팅 금액을 송금합니다.
// //SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract RPS {
    constructor() payable {} // 해당 컨트렉트는 송금이 가능함을 명시

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

    // 플레이어의 상태 (enum은 각 순서에 따라 0,1,2...로 초기값이 정해진다.)
    enum PlayerStatus {
        STATUS_WIN, // 게임을 이김
        STATUS_LOSE, // 게임을 짐
        STATUS_TIE, // 게임 비김
        STATUS_PENDING // 게임 대기중
    }

    // 게임 방의 상태
    enum GameStatus {
        STATUS_NOT_STARTED, // 시작하지 않은 상태
        STATUS_STARTED, // 시작한 상태
        STATUS_COMPLETE, // 게임이 끝난 상태
        STATUS_ERROR // 게임이 에러난 상태
    }
    struct Player {
        // 참여한 플레이어의 주소와 베팅금액, 낸 값, 현재 상태를 알기 위한 구조체
        address payable addr; // 주소
        uint256 playerBetAmount; // 베팅 금액
        Hand hand; // 참여자의 손 상태
        PlayerStatus playerStatus; // 플레이어의 상태
    }

    struct Game {
        Player originator; //  방장의 정보
        Player taker; // 참여자 정보
        uint256 betAmount; // 총 베팅 금액
        GameStatus gameStatus; // 게임의 현 상태
    }

    mapping(uint256 => Game) rooms; //Game 구조체 형식으로 각 방을 만들기 위함
    uint256 roomLen = 0; // rooms의 키 값, 방이 생성될 때마다 1씩 올라감.

    function createRoom(Hand _hand)
        public
        payable
        isValidHand(_hand)
        returns (uint256 roomNum)
    {
        // 베팅 금액을 입금하기 때문에 payable 키워드 사용
        // 생성한 방의 번호를 반환
        rooms[roomLen] = Game({
            betAmount: msg.value,
            gameStatus: GameStatus.STATUS_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; // 다음 새롭게 만들어질 방을 위해 1 올려준다.
        return roomNum;
    }

    modifier isValidHand(Hand _hand) {
        require(
            (_hand == Hand.rock) ||
                (_hand == Hand.paper) ||
                (_hand == Hand.scissors)
        );
        _;
    }

    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
        });
        rooms[_roomNum].betAmount = rooms[_roomNum].betAmount + msg.value;
        compareHands(_roomNum); //  게임 결과 업데이트 함수
    }

    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 (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_LOSE;
        } else if ((taker + 1) % 3 == taker) {
            // 참가자가 이긴 경우
            rooms[_roomNum].originator.playerStatus = PlayerStatus.STATUS_LOSE;
            rooms[_roomNum].taker.playerStatus = PlayerStatus.STATUS_WIN;
        } else {
            // 그 외의 상황은 에러
            rooms[_roomNum].gameStatus = GameStatus.STATUS_ERROR;
        }
    }

    // 비긴 경우에는 베팅금액을 돌려받고, 이긴 경우에는 전체 베팅 금액을 돌려 받습니다.
    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; // 종료 되었으므로 게임 상태 변경
        }
    }

    modifier isPlayer(uint256 _roomNum, address _sender) {
        require(
            _sender == rooms[_roomNum].originator.addr ||
                _sender == rooms[_roomNum].taker.addr
        );
        _;
    }
    // 가스비 절약
    // 초기값 확인 - Enum의 초기값은 무엇이 들어갈까
    // modifier 함수에 대한 설명 추가
    // msg.sender, msg.value
}
profile
좋은 개발자가 되고싶은

0개의 댓글