Jim's
Tutorials

Spring 2019
course
site
pragma solidity ^0.4.17;

// factory for deploying individual rock-paper-scissors contracts
contract RockPaperScissorsFactory {
    address[] public deployedRockPaperScissors;  // an array that holds the addresses
    // of each deployed contract

    // takes a wager and game name, deploys a rockpaperscissors contract, adds it to array
    function createRockPaperScissors(uint wager, string gameName) public {
        address newRockPaperScissors = new RockPaperScissors(wager, msg.sender, gameName);
        deployedRockPaperScissors.push(newRockPaperScissors);
    }

    function getDeployedRockPaperScissors() public view returns(address[]) {
        return deployedRockPaperScissors;
    }
}


contract RockPaperScissors {

  // data structure that holds info about an individual game
    struct Game {
        string title;
        uint wager;
        address playerOne;
        address playerTwo;
        uint playerOneMove;
        uint playerTwoMove;
        address winner;
    }

    address public manager;  // creator of game
    uint public gameWager;  // wager amount
    string public gameName;
    Game public game;

    // constructor
     function RockPaperScissors(uint wager, address creator, string name) public {
        manager = creator;
        gameWager = wager;
        gameName = name;
    }

    // creates a new game of rock paper scissors, sets initial values of mapping
    function createGame() public payable{
        require(msg.sender == manager);
        require(msg.value == gameWager);
        Game memory newGame = Game({
            title: gameName,
            wager: gameWager,
            playerOne: manager,
            playerTwo: 0,
            playerOneMove: 0,
            playerTwoMove: 0,
            winner: 0
        });
        game = newGame;
    }

    // function to join a game of rock paper scissors
    function joinGame() public payable{
        require(msg.value == game.wager);
        game.playerTwo = msg.sender;
    }

    // function to declare first players move
    function playOneMove(uint move) public{
        require(msg.sender == game.playerOne);
        require(move == 1 || move == 2 || move == 3);
        require(game.playerOneMove == 0);
        game.playerOneMove = move;
        if(game.playerTwoMove != 0){    // if both players have acted, selectwinner
            selectWinner();
        }
    }

    // function to declare second players move
    function playerTwoMove(uint move) public {
        require(msg.sender == game.playerTwo);
        require(move == 1 || move == 2 || move == 3);
        require(game.playerTwoMove == 0);
        game.playerTwoMove = move;
        if(game.playerOneMove != 0){
            selectWinner();
        }
    }

    // game logic to select a winner based on selected moves
    function selectWinner() private {
        if(game.playerOneMove == game.playerTwoMove){  // reset the moves if tied
            game.playerOneMove = 0;
            game.playerTwoMove = 0;
        }
        else if(game.playerOneMove == 1 && game.playerTwoMove == 2){
            game.winner = game.playerOne;
        }
        else if(game.playerOneMove == 1 && game.playerTwoMove == 3){
            game.winner = game.playerTwo;
        }
        else if(game.playerOneMove == 2 && game.playerTwoMove == 1){
            game.winner = game.playerTwo;
        }
        else if(game.playerOneMove == 2 && game.playerTwoMove == 3){
            game.winner = game.playerOne;
        }
        else if(game.playerOneMove == 3 && game.playerTwoMove == 2){
            game.winner = game.playerTwo;
        }
        else if(game.playerOneMove == 3 && game.playerTwoMove == 1){
            game.winner = game.playerTwo;
        }
        if(game.winner != 0){                             // send winner balance
            game.winner.transfer(this.balance);           // delete contract
            selfdestruct(this);
        }
    }
}

/* TODO Refactor to include support for best of x games - SelectWinner increments
a counter, value is sent when that counter reaches a set thresh-hold

Here we created two contracts. One contract is a factory that deploys individual rock-paper-scissors contracts. It stores an array of the address of each contract and it contains two functions - createRockPaperScissors and getDeployedRockPaperScissors. CreateRockPaperScissors takes a uint and a string, deploys a RockPaperScissors contract then pushes the address into an array. One thing to note is that we are passing the name of the creator of the contract as msg.sender so we know who created the contract in the deployed instance of the RockPaperScissors contract. getDeployedRockPaperScissors just returns an array of all the addresses of deployed Rock Papaer Scissors contracts.

First in the RockPaperScissors contract we define a struct that keeps track of the state of game. It contains a label of the game, the amount wagered, an address for both players, which move they have selected and the eventual address of the winner. We also keep track of a couple of other variables that get passed to the constructor, namely the manager of the contract, the amount to wagered, the name of the game, and a game struct once it gets initiated.

The RockPaperScissors() function is our constructor. It takes a wager, the address of a creator and a string thats the name of the game and saves them to variables in memory.

The createGame() function has 2 require statements, one requires the caller to be the contract manager and the other requires the value sent to the contract to be the same as the game wager. The function then initiates a game struct and sets some of the initial values, then saves it to memory.

The joinGame() function has one require statement that states the value sent needs to be equal to the wager of the game. It then saves the address of the sender as player two.

The playerOneMove() function requires the sender of the message to be player one, the move to either be 1 2 or 3 and the move to initially be 0. playerTwoMove() does the same for player 2. This will hopefully stop a player from changing their move after a move has been declared or from sending an invalid move. The function then sets player one move in the game struct to be the declared move. It then checks to see if the other move has been declared, if both moves have been declared, Select winner is initiated.

The selectWinner() function resets the moves in the case of a tie and determines a winner in the case of different moves. It then sends the balance of the contract to the winner and destroys the contract.

There are a few holes in this implementation. First and foremost it only handles for best of 1 rock paper scissors which is not ideal. There is also no way for a front-end interracting with these two contracts to know when a game is complete and the deployed contract is destroyed. Otherwise I think it should be largely functional. I tested it quite a bit in the Remix online IDE. The require statements make it secure from outside accounts manipulating the games, and when moves are selected value is transferred properly.