Skip to content

Instantly share code, notes, and snippets.

@danielkhoo
Last active February 7, 2022 07:39
Show Gist options
  • Save danielkhoo/36463ad69a52a1eee5b9ed3eff4d042a to your computer and use it in GitHub Desktop.
Save danielkhoo/36463ad69a52a1eee5b9ed3eff4d042a to your computer and use it in GitHub Desktop.
pragma solidity >=0.8.0 <0.9.0;
//SPDX-License-Identifier: MIT
contract RockPaperScissors {
// 4 Game Phases: Join, Commit, Reveal, Result
enum GameState {
JoinPhase,
CommitPhase,
RevealPhase,
ResultPhase
}
// 3 Game Results: P1 win, P2 win, draw
enum GameResult {
P1Win,
P2Win,
Draw
}
// Holds the game data for a single match
struct GameStruct {
bool initialized;
address player1;
address player2;
GameState gameState;
bytes32 commit1;
bytes32 commit2;
bytes32 reveal1;
bytes32 reveal2;
uint256 revealDeadline;
GameResult gameResult;
}
// Maps Game address => Game data
mapping(address => GameStruct) public games;
// Maps Player address to their current 'active' game
mapping(address => address) public activeGame;
/**
* @notice Modifier that checks game is initialized, the sender is player 1/2
* and that the game state to be in the expected phase
* @param gameHash - the game code
* @param gameState - the three possible game phases
*/
modifier validGameState(address gameHash, GameState gameState) {
// Check that the game exists
require(
games[gameHash].initialized == true,
"Game code does not exist"
);
// Check player is either player 1 or player 2
require(
games[gameHash].player1 == msg.sender ||
games[gameHash].player2 == msg.sender,
"Player not in this game"
);
// Check that game is in expected state
require(
games[gameHash].gameState == gameState,
"Game not in correct phase"
);
_;
}
/**
* @notice Creates a new game, generating a game hash and setting player 1 as sender
* and player 2 as the address provided
* @param otherPlayer - address for player 2
*/
function createGame(address otherPlayer) public returns (address) {
//
address gameHash = generateGameHash();
require(
!games[gameHash].initialized,
"Game code already exists, please try again"
);
// Check other player isn't host
require(
msg.sender != otherPlayer,
"Invited player must have a different address"
);
games[gameHash].initialized = true;
games[gameHash].player1 = msg.sender;
games[gameHash].player2 = otherPlayer;
// Set game phase to initial join phase
games[gameHash].gameState = GameState.JoinPhase;
// Set P1 active game to game hash
activeGame[msg.sender] = gameHash;
// Return the game hash so it can be shared
return gameHash;
}
/**
* @notice Function for player 2 to join a game with the game address
* @param gameHash - game address shared by player 1
*/
function joinGame(address gameHash)
public
validGameState(gameHash, GameState.JoinPhase)
{
// Set game phase to commit phase
games[gameHash].gameState = GameState.CommitPhase;
// Set P2 active game to game hash
activeGame[msg.sender] = gameHash;
}
/// @notice Util Functions for generating hashes, computing winners and fetching data
function generateGameHash() public view returns (address) {
bytes32 prevHash = blockhash(block.number - 1);
// Game hash is a pseudo-randomly generated address from last blockhash + p1
return
address(bytes20(keccak256(abi.encodePacked(prevHash, msg.sender))));
}
/**
* @notice Fetches the game data of the player's active game
* @param player - address of player
*/
function getActiveGameData(address player)
public
view
returns (GameStruct memory)
{
// Get the game hash from active game mapping
address gameHash = activeGame[player];
return games[gameHash];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment