2.0 Finding a Winner!¶
First define which combinations within the game board, which indexes, define a “win”
/** * Winning filters: * 0, 1, 2 * 3, 4, 5 * 6, 7, 8 * * 3 in a row: * [0,1,2] || [3,4,5] || [6,7,8] * * 3 in a column: * [0,3,6] || [1,4,7] || [2,5,8] * * Diagonals: * [0,4,8] || [6,4,2] */
Create a function to compute a winner and implement these combintations as filters to filter the board with
function isWinner(address player) private view returns(bool) { uint8[3][8] memory winningFilters = [ [0,1,2],[3,4,5],[6,7,8], // rows [0,3,6],[1,4,7],[2,5,8], // columns [0,4,8],[6,4,2] // diagonals ]; }
Create a for loop to iterate over each filter, within the
isWinnerfunctionfor (uint8 i = 0; i < winningFilters.length; i++) { uint8[3] memory filter = winningFilters[i]; }
Result:
function isWinner(address player) private view returns(bool) { uint8[3][8] memory winningFilters = [ [0,1,2],[3,4,5],[6,7,8], // rows [0,3,6],[1,4,7],[2,5,8], // columns [0,4,8],[6,4,2] // diagonals ]; for (uint8 i = 0; i < winningFilters.length; i++) { uint8[3] memory filter = winningFilters[i]; } }
Add a storage variable at the top of the contract beneath
address public lastPlayed_;to define the winneraddress public winner_;
Within the above
for loopcompare each filter against the game board and see if the player has won with their latest turnif ( gameBoard_[filter[0]]==playerNumbers_[player] && gameBoard_[filter[1]]==playerNumbers_[player] && gameBoard_[filter[2]]==playerNumbers_[player] ) { return true; }
Result:
function isWinner(address player) private view returns(bool) { uint8[3][8] memory winningFilters = [ [0,1,2],[3,4,5],[6,7,8], // rows [0,3,6],[1,4,7],[2,5,8], // columns [0,4,8],[6,4,2] // diagonals ]; for (uint8 i = 0; i < winningFilters.length; i++) { uint8[3] memory filter = winningFilters[i]; if ( gameBoard_[filter[0]]==playerNumbers_[player] && gameBoard_[filter[1]]==playerNumbers_[player] && gameBoard_[filter[2]]==playerNumbers_[player] ) { return true; } } }
At the end of the
takeTurnfunction, after each turn is taken see if there is a winner, update the storage variable if there is a winnerif (isWinner(msg.sender)) { winner_ = msg.sender; }
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; if (isWinner(msg.sender)) { winner_ = msg.sender; } }
Try it out! See if the winner is set if 3 in a row is found
Important
Are we done?
… still a few problems
- Turns can still continue to be taken, no notification that the game has ended
- What happens in the case of a draw?
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_; address public winner_; /** 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; if (isWinner(msg.sender)) { winner_ = msg.sender; } } function getBoard() external view returns(uint256[9]) { return gameBoard_; } function isWinner(address player) private view returns(bool) { uint8[3][8] memory winningFilters = [ [0,1,2],[3,4,5],[6,7,8], // rows [0,3,6],[1,4,7],[2,5,8], // columns [0,4,8],[6,4,2] // diagonals ]; for (uint8 i = 0; i < winningFilters.length; i++) { uint8[3] memory filter = winningFilters[i]; if ( gameBoard_[filter[0]]==playerNumbers_[player] && gameBoard_[filter[1]]==playerNumbers_[player] && gameBoard_[filter[2]]==playerNumbers_[player] ) { return true; } } } }
Add a storage variable to signify the game has ended, beneath
address public winner_;bool public gameOver_;
If a winner was found update that the game has ended, within the
takeTurnfunctiongameOver_ = true;
Result:
if (isWinner(msg.sender)) { winner_ = msg.sender; gameOver_ = true; }
Add a storage variable to count how many turns have been taken, beneath
bool public gameOver_;, will use this variable to define if a draw has occured
uint256 public turnsTaken_;
After a turn is taken update the turns taken storage variable, within the
takeTurnfunctionturnsTaken_++;
Add a conditional that if 9 turns have been taken the game has ended with no winner, within the
takeTurnfunction
else if (turnsTaken_ == 9) { gameOver_ = true; }
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; if (isWinner(msg.sender)) { winner_ = msg.sender; gameOver_ = true; } else if (turnsTaken_ == 9) { gameOver_ = true; } turnsTaken_++; }
Add a last pre condition check that the game is still active, within the
takeTurnfunctionrequire(!gameOver_, "Sorry game has concluded.");
Try it out!!
- Start a game with account 1 and 2
- Take turns back and forth
- view turns taken are updating the game board
- view no winner yet
- view game has not ended
- View that the winner has been set
- View that the game has ended
- Try and take another turn and view the output in Remix’s console
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_; address public winner_; bool public gameOver_; uint256 public turnsTaken_; /** 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."); require(!gameOver_, "Sorry game has concluded."); gameBoard_[_boardLocation] = playerNumbers_[msg.sender]; lastPlayed_ = msg.sender; if (isWinner(msg.sender)) { winner_ = msg.sender; gameOver_ = true; } else if (turnsTaken_ == 9) { gameOver_ = true; } turnsTaken_++; } function getBoard() external view returns(uint256[9]) { return gameBoard_; } function isWinner(address player) private view returns(bool) { uint8[3][8] memory winningFilters = [ [0,1,2],[3,4,5],[6,7,8], // rows [0,3,6],[1,4,7],[2,5,8], // columns [0,4,8],[6,4,2] // diagonals ]; for (uint8 i = 0; i < winningFilters.length; i++) { uint8[3] memory filter = winningFilters[i]; if ( gameBoard_[filter[0]]==playerNumbers_[player] && gameBoard_[filter[1]]==playerNumbers_[player] && gameBoard_[filter[2]]==playerNumbers_[player] ) { return true; } } } }