솔리디티를 사용해 간단한 가위바위보 컨트랙트를 짜보는 실습
- 사용자와 게임 구조체 생성
- 컨트랙트 틀 짜기
- 플레이어 struct 생성
- 게임 struct 생성
- 게임 생성하기
- 게임을 생성
- 방 번호를 지정 (추가 생성시 방번호 +1)
- 방 참가하기
- 참가자 / 방 번호 / 가위바위보 값 인자 / 베팅금액 설정
- 게임결과 업데이트
- 방별 베팅된 금액을 확인하기
- 게임 종료 후 베팅금액 송금
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract RPS {
constructor () payable {} // 송금이 가능하다는 것을 명시
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 {
address payable addr; // 주소
uint256 playerBetAmount; // 베팅 금액
Hand hand; // 플레이어의 가위바위보값
PlayerStatus playerStatus; // 사용자 상태
}
struct Game {
Player originator; // 방장 정보
Player taker; // 참여자 정보
uint256 betAmount; // 총 베팅 금액
GameStatus gameStatus; // 게임의 현 상태
}
mapping(uint => Game) rooms; // rooms[0], rooms[1]로 접근
uint 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(uint roomNum) {
rooms[roomLen] = Game({
betAmount: msg.value,
gameStatus: GameStatus.STATUS_NOT_STARTED,
originator: Player({
addr: payable(msg.sender),
playerBetAmount: msg.value,
hand: _hand,
playerStatus: PlayerStatus.STATUS_PENDING
}),
taker: Player({
addr: payable(msg.sender),
playerBetAmount: 0,
hand: Hand.rock,
playerStatus: PlayerStatus.STATUS_PENDING
})
});
roomNum = roomLen; // 현재 방 번호를 roomNum에 넣고 리턴
roomLen = roomLen+1; // 다음 방 번호에 1추가
}
function joinRoom (uint roomNum, Hand _hand) public payable isValidHand(_hand) {
rooms[roomLen].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(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) {
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 ((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;
}
}
function checkTotalPay(uint roomNum) public view returns(uint roomNumPay) {
return rooms[roomNum].betAmount;
}
modifier isPlayer (uint roomNum, address sender) {
require(sender == rooms[roomNum].originator.addr || sender == rooms[roomNum].taker.addr);
_;
}
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; // 게임 종료
}
}