Last active
October 5, 2021 09:49
-
-
Save qkdxorjs1002/2a1e8ad752308df3935de10c1feb0c7a to your computer and use it in GitHub Desktop.
Rock, Scissor, Paper with Solidity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.5.1; | |
contract RSP { | |
/** | |
* 각 패의 2진 값 | |
* | |
* 001: 바위 / 010: 가위 / 100: 보 | |
*/ | |
uint8 constant ROCK = 1; | |
uint8 constant SCISSOR = 2; | |
uint8 constant PAPER = 4; | |
/** | |
* 각 패 XOR 한 값으로 승패 여부 판단 | |
* | |
* 000: 비길 경우 | |
* 011: 바위가 이겼을 경우 | |
* 110: 가위가 이겼을 경우 | |
* 101: 보가 이겼을 경우 | |
*/ | |
uint8 constant WIN_ROCK = ROCK ^ SCISSOR; | |
uint8 constant WIN_SCISSOR = SCISSOR ^ PAPER; | |
uint8 constant WIN_PAPER = PAPER ^ ROCK; | |
/** | |
* 가위바위보 Room 구조체 | |
* | |
* uint numberOfUser - Room에 참여한 유저 수 | |
* (uint => User) userList - Room에 참여한 유저 목록 | |
* uint betWei - Room 호스트가 설정한 베팅 기준 금액 | |
* uint totalPaid - 지불된 전체 금액 | |
*/ | |
struct Room { | |
uint numberOfUser; | |
mapping (uint => User) userList; | |
uint betWei; | |
uint totalPaid; | |
} | |
/** | |
* 가위바위보 유저 구조체 | |
* | |
* address(payable) userAddress - 유저 주소 | |
* uint8 rsp - 유저가 제시한 가위바위보 패 | |
*/ | |
struct User { | |
address payable userAddress; | |
uint8 rsp; | |
} | |
// Room 목록 | |
mapping (address => Room) private roomList; | |
/** | |
* 가위바위보 혼자 플레이 | |
* | |
* @param _rsp 가위바위보 패 [rock, scissor, paper] | |
* @return string(memory) 승패 여부 | |
*/ | |
function playAlone(string memory _rsp) public returns(string memory) { | |
uint8[3] memory rsps = [ROCK, SCISSOR, PAPER]; | |
uint8 rsp = stringToRSP(_rsp); | |
// 랜덤 패와 유저가 제시한 패 XOR | |
uint8 result = stringToRSP(_rsp) ^ rsps[uint(keccak256(abi.encodePacked(now, msg.sender, uint(1)))) % 3]; | |
// WIN CASE 전부 비교 | |
if (result == WIN_ROCK) { | |
if (rsp == ROCK) { | |
return "win"; | |
} else { | |
return "lose"; | |
} | |
} | |
if (result == WIN_SCISSOR) { | |
if (rsp == SCISSOR) { | |
return "win"; | |
} else { | |
return "lose"; | |
} | |
} | |
if (result == WIN_PAPER) { | |
if (rsp == PAPER) { | |
return "win"; | |
} else { | |
return "lose"; | |
} | |
} | |
return "draw"; | |
} | |
/** | |
* 가위바위보 Room 생성 | |
* | |
* @param _betEther Room 베팅 기준 금액 | |
* @param _rsp 가위바위보 패 [rock, scissor, paper] | |
* @return address 생성된 Room 주소 | |
*/ | |
function createRoom(uint _betEther, string memory _rsp) public payable returns(address) { | |
// 기존에 생성된 Room이 있는지 확인 | |
require(roomList[msg.sender].numberOfUser == 0, | |
"Room is already created."); | |
// 기준 베팅 금액이 0을 초과하는지 확인 | |
require(_betEther > 0, | |
"\'_betEther\' must be bigger than 0."); | |
// 지불한 금액이 기준 베팅 금액과 일치하는지 확인 | |
require(msg.value == (_betEther * 1 ether), | |
string(abi.encodePacked("You must be pay value that equal with ", _betEther))); | |
// 제시한 패가 올바른지 확인 | |
require(compareStrings(_rsp, "rock") || compareStrings(_rsp, "scissor") || compareStrings(_rsp, "paper"), | |
"You must be set \'_rsp\' in \"rock\", \"scissor\" and \"paper\""); | |
// Room initialize | |
roomList[msg.sender].numberOfUser += 1; | |
roomList[msg.sender].userList[0].userAddress = msg.sender; | |
roomList[msg.sender].userList[0].rsp = stringToRSP(_rsp); | |
roomList[msg.sender].betWei = _betEther * 1 ether; | |
roomList[msg.sender].totalPaid += msg.value; | |
// Room 고유 번호 반환 | |
return msg.sender; | |
} | |
/** | |
* 가위바위보 Room 참여 | |
* | |
* @param _roomId Room ID | |
* @param _rsp 가위바위보 패 [rock, scissor, paper] | |
*/ | |
function joinRoom(address _roomId, string memory _rsp) public payable returns(address, string memory) { | |
// 존재하는 Room인지 확인 | |
require(roomList[_roomId].numberOfUser == 1, | |
"\'_roomId\' is not valid."); | |
// 지불한 금액이 기준 베팅 금액과 일치하는지 확인 | |
require(msg.value == roomList[_roomId].betWei, | |
string(abi.encodePacked("You must be pay value that equal with ", roomList[_roomId].betWei))); | |
// 제시한 패가 올바른지 확인 | |
require(compareStrings(_rsp, "rock") || compareStrings(_rsp, "scissor") || compareStrings(_rsp, "paper"), | |
"You must be set \'_rsp\' in \"rock\", \"scissor\" and \"paper\""); | |
// Room에 유저 추가 | |
roomList[_roomId].numberOfUser += 1; | |
roomList[_roomId].userList[1].userAddress = msg.sender; | |
roomList[_roomId].userList[1].rsp = stringToRSP(_rsp); | |
roomList[_roomId].totalPaid += msg.value; | |
// 승패 여부 확인 후 결과 반환 | |
return checkWinner(_roomId); | |
} | |
/** | |
* 가위바위보 Room 제거 | |
* | |
* @param _roomId Room ID | |
*/ | |
function removeRoom(address _roomId) public { | |
// 요청자가 Room 호스트인지 확인 | |
require(roomList[_roomId].userList[0].userAddress == msg.sender, | |
"You are not room owner."); | |
if (!roomList[_roomId].userList[0].userAddress.send(roomList[_roomId].betWei)) { | |
revert(); | |
} else { | |
delete roomList[_roomId]; | |
} | |
} | |
/** | |
* 가위바위보 Room 확인 | |
* | |
* @param _roomId Room ID | |
* @return (uint, uint, uint) Room 참여 유저 수, 기준 베팅 금액, 지불된 전체 금액 | |
*/ | |
function checkRoom(address _roomId) public returns(uint, uint, uint) { | |
// 존재하는 Room인지 확인 | |
require(roomList[_roomId].numberOfUser > 0, | |
"Room is not exist."); | |
// Room 정보 반환 | |
return (roomList[_roomId].numberOfUser, | |
roomList[_roomId].betWei, | |
roomList[_roomId].totalPaid); | |
} | |
/** | |
* 가위바위보 승자 확인 및 처리 | |
* | |
* @param _roomId Room ID | |
* @return (address, string(memory)) 승리한 유저 주소, 승자가 제시한 패 | |
*/ | |
function checkWinner(address _roomId) private returns(address, string memory) { | |
Room storage room = roomList[_roomId]; | |
// Room에 유저가 모두 참여했는지 확인 | |
require(room.numberOfUser == 2, | |
"The competitor is not joined."); | |
User memory userA = room.userList[0]; | |
User memory userB = room.userList[1]; | |
// 유저 패 XOR | |
uint8 result = userA.rsp ^ userB.rsp; | |
User memory winner; | |
// WIN CASE 비교 | |
if (result == WIN_ROCK) { | |
if (userA.rsp == ROCK) { | |
winner = userA; | |
} | |
if (userB.rsp == ROCK) { | |
winner = userB; | |
} | |
} else if (result == WIN_SCISSOR) { | |
if (userA.rsp == SCISSOR) { | |
winner = userA; | |
} | |
if (userB.rsp == SCISSOR) { | |
winner = userB; | |
} | |
} else if (result == WIN_PAPER) { | |
if (userA.rsp == PAPER) { | |
winner = userA; | |
} | |
if (userB.rsp == PAPER) { | |
winner = userB; | |
} | |
} else { | |
// 비겼을 경우 지불된 금액 모두 각 참여 유저에게 반환 | |
if (!userA.userAddress.send(room.betWei) || | |
!userB.userAddress.send(room.betWei)) { | |
revert("Error occured while refunding bet."); | |
} | |
delete roomList[_roomId]; | |
return (address(0x0), "draw"); | |
} | |
// 승자에게 지불된 베팅 금액 모두 전송 | |
if (!winner.userAddress.send(roomList[_roomId].totalPaid)) { | |
revert("Error occured while send bet to winner."); | |
} else { | |
delete roomList[_roomId]; | |
} | |
// 승자 주소와 제시한 패 반환 | |
return (winner.userAddress, rspToString(winner.rsp)); | |
} | |
/** | |
* 문자열 비교 | |
* | |
* @param _a 비교대상 1 | |
* @param _b 비교대상 2 | |
* @return bool 비교 결과 | |
*/ | |
function compareStrings(string memory _a, string memory _b) private pure returns(bool) { | |
// 인코딩한 String 해싱하여 비교 | |
return (keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b))); | |
} | |
/** | |
* 문자열 패를 uint8 형식으로 변환 | |
* | |
* @param _rsp 문자열 패 | |
* @return uint8 결과 | |
*/ | |
function stringToRSP(string memory _rsp) private returns(uint8) { | |
if (compareStrings(_rsp, "rock")) { | |
return ROCK; | |
} else if (compareStrings(_rsp, "scissor")) { | |
return SCISSOR; | |
} else if (compareStrings(_rsp, "paper")) { | |
return PAPER; | |
} else { | |
return 0; | |
} | |
} | |
/** | |
* uint8 패를 문자열로 변환 | |
* | |
* @param _rsp uint8 패 | |
* @return string(memory) 결과 | |
*/ | |
function rspToString(uint8 _rsp) private returns(string memory) { | |
if (_rsp == ROCK) { | |
return "rock"; | |
} else if (_rsp == SCISSOR) { | |
return "scissor"; | |
} else if (_rsp == PAPER) { | |
return "paper"; | |
} else { | |
return ""; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment