1.0 Contract Creation and Basic Functionality¶
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_; }
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; }
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 {}
Mark the board, within the
takeTurnfunction update thegameBoardarraygameBoard_[_boardLocation] = playerNumbers_[msg.sender];
Result:
function takeTurn(uint256 _boardLocation) external { gameBoard_[_boardLocation] = playerNumbers_[msg.sender]; }
Add a function to return the contents of the game board
function getBoard() external view returns(uint256[9]) { return gameBoard_; }
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!
Important
Require that only player 1 or player 2 may take turns, within the
takeTurnfunctionrequire(msg.sender == player1_ || msg.sender == player2_, "Not a valid player.");
Add a pre condition check to confirm the spot on the board is not already taken, within the
takeTurnfunctionrequire(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]; }
Add a storage variable, at the top of the contract beneath
mapping(address => uint256) public playerNumbers_;to track who just took a turnaddress 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_;
Following a turn being taken update the storage variable, within the
takeTurnfunctionlastPlayed_ = msg.sender;
Check that the same player is not trying to take another turn, within the
takeTurnfunctionrequire(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_; } }