1.0 Contract Creation and Basic Functionality

  1. Create the contract and initial storage variables

    pragma solidity 0.4.24;
    
    
    contract TicTacToe {
    
        address public player1_;
        address public player2_;
        mapping(address => uint256) public playerNumbers_;  // Map ugly address to number for simpler inspection of game board
    
        /** The game board itself
         * 0, 1, 2
         * 3, 4, 5
         * 6, 7, 8
         */
        uint256[9] private gameBoard_;
    }
    
  2. Create a function to allow a game to be started

    function startGame(address _player1, address _player2) external {
        player1_ = _player1;
        playerNumbers_[_player1] = 1;
    
        player2_ = _player2;
        playerNumbers_[_player2] = 2;
    }
    
  3. Now players need to be able to take a turn, specifying where they want to place their x or o

    • create a function to allow this
    /**
    * @notice Take your turn, selecting a board location
    * @param _boardLocation Location of the board to take
    */
    function takeTurn(uint256 _boardLocation) external {}
    
  4. Mark the board, within the takeTurn function update the gameBoard array

    gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
    
    • Result:

      function takeTurn(uint256 _boardLocation) external {
          gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
      }
      
  5. Add a function to return the contents of the game board

    function getBoard() external view returns(uint256[9]) {
        return gameBoard_;
    }
    
  6. Give it a shot! Try starting a game and taking turns, watch as the game board’s indexes are filled


  • Completed code:

    pragma solidity 0.4.24;
    
    
    contract TicTacToe {
    
        address public player1_;
        address public player2_;
        mapping(address => uint256) public playerNumbers_;  // Map ugly address to number for simpler inspection of game board
    
        /** The game board itself
        * 0, 1, 2
        * 3, 4, 5
        * 6, 7, 8
        */
        uint256[9] private gameBoard_;
    
        function startGame(address _player1, address _player2) external {
                player1_ = _player1;
                playerNumbers_[_player1] = 1;
    
                player2_ = _player2;
                playerNumbers_[_player2] = 2;
        }
    
        /**
        * @notice Take your turn, selecting a board location
        * @param _boardLocation Location of the board to take
        */
        function takeTurn(uint256 _boardLocation) external {
            gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
        }
    
        function getBoard() external view returns(uint256[9]) {
            return gameBoard_;
        }
    }
    

  • Now take a look, what problems do you notice?
  • Did you have some time to play with the contract?
  • Any big issues come up?

Important

What problems currently exist with this?

  • Anyone can take turns!
  • A player can overwrite a spot that has already been taken
  • A player may take many turns in a row, alternating must be enforced

Let’s tackle these problems first!


  1. Require that only player 1 or player 2 may take turns, within the takeTurn function

    require(msg.sender == player1_ || msg.sender == player2_, "Not a valid player.");
    
  2. Add a pre condition check to confirm the spot on the board is not already taken, within the takeTurn function

    require(gameBoard_[_boardLocation] == 0, "Spot taken!");
    
    • Result:

      function takeTurn(uint256 _boardLocation) external {
          require(msg.sender == player1_ || msg.sender == player2_, "Not a valid player.");
          require(gameBoard_[_boardLocation] == 0, "Spot taken!");
      
          gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
      }
      
  3. Add a storage variable, at the top of the contract beneath mapping(address => uint256) public playerNumbers_; to track who just took a turn

    address public lastPlayed_;
    
    • Result:

      address public player1_;
      address public player2_;
      mapping(address => uint256) public playerNumbers_;  // Map ugly address to number for simpler inspection of game board
      address public lastPlayed_;
      
  4. Following a turn being taken update the storage variable, within the takeTurn function

    lastPlayed_ = msg.sender;
    
  5. Check that the same player is not trying to take another turn, within the takeTurn function

    require(msg.sender != lastPlayed_, "Not your turn.");
    
    • Result:

      function takeTurn(uint256 _boardLocation) external {
          require(msg.sender == player1_ || msg.sender == player2_, "Not a valid player.");
          require(gameBoard_[_boardLocation] == 0, "Spot taken!");
          require(msg.sender != lastPlayed_, "Not your turn.");
      
          gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
          lastPlayed_ = msg.sender;
      }
      

Try taking turns now! More restricted / protected?

Important

Happy?

What else do we need to fix?

How about a conclusion to the game?

Let’s look into how we can compute a winner


  • Completed code:

    pragma solidity 0.4.24;
    
    
    contract TicTacToe {
    
        address public player1_;
        address public player2_;
        mapping(address => uint256) public playerNumbers_;  // Map ugly address to number for simpler inspection of game board
        address public lastPlayed_;
    
        /** The game board itself
        * 0, 1, 2
        * 3, 4, 5
        * 6, 7, 8
        */
        uint256[9] private gameBoard_;
    
        function startGame(address _player1, address _player2) external {
                player1_ = _player1;
                playerNumbers_[_player1] = 1;
    
                player2_ = _player2;
                playerNumbers_[_player2] = 2;
        }
    
        /**
        * @notice Take your turn, selecting a board location
        * @param _boardLocation Location of the board to take
        */
        function takeTurn(uint256 _boardLocation) external {
            require(msg.sender == player1_ || msg.sender == player2_, "Not a valid player.");
            require(gameBoard_[_boardLocation] == 0, "Spot taken!");
            require(msg.sender != lastPlayed_, "Not your turn.");
    
            gameBoard_[_boardLocation] = playerNumbers_[msg.sender];
            lastPlayed_ = msg.sender;
        }
    
        function getBoard() external view returns(uint256[9]) {
            return gameBoard_;
        }
    }