Validating moves
Let's add another validation. How about making sure we can't overwrite a space that's already been marked? Here's an additional test:
function test_cannot_overwrite_marked_space() public {
ttt.markSpace(0, "X");
vm.expectRevert("Already marked");
ttt.markSpace(0, "O");
}
Note that we need to put vm.expectRevert
on the line directly before the call in our test that we expect to error.
Here's the result:
$ forge test
[⠊] Compiling...
[⠆] Compiling 1 files with 0.8.10
[⠰] Solc finished in 177.31ms
Compiler run successful
Running 6 tests for src/test/TicTacToken.t.sol:TicTacTokenTest
[PASS] test_can_mark_space_with_O() (gas: 33319)
[PASS] test_can_mark_space_with_X() (gas: 32325)
[PASS] test_cannot_mark_space_with_Z() (gas: 12799)
[FAIL. Reason: Call did not revert as expected] test_cannot_overwrite_marked_space() (gas: 40135)
[PASS] test_get_board() (gas: 44622)
[PASS] test_has_empty_board() (gas: 47219)
Test result: FAILED. 5 passed; 1 failed; finished in 1.13ms
Failed tests:
[FAIL. Reason: Call did not revert as expected] test_cannot_overwrite_marked_space() (gas: 40135)
Encountered a total of 1 failing tests, 5 tests succeeded
Let's add another require statement. We can even reuse our string comparison helper function:
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
contract TicTacToken {
string[9] public board;
function getBoard() public view returns (string[9] memory) {
return board;
}
function markSpace(uint256 space, string calldata symbol) public {
require(_validSymbol(symbol), "Invalid symbol");
require(_compareStrings(board[space], ""), "Already marked");
board[space] = symbol;
}
function _validSymbol(string calldata symbol) internal pure returns (bool) {
return _compareStrings(symbol, "X") || _compareStrings(symbol, "O");
}
function _compareStrings(string memory a, string memory b) internal pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}
Tests should pass:
$ forge test
[⠊] Compiling...
[⠆] Compiling 2 files with 0.8.10
[⠰] Solc finished in 183.60ms
Compiler run successful
Running 6 tests for src/test/TicTacToken.t.sol:TicTacTokenTest
[PASS] test_can_mark_space_with_O() (gas: 34537)
[PASS] test_can_mark_space_with_X() (gas: 33543)
[PASS] test_cannot_mark_space_with_Z() (gas: 12800)
[PASS] test_cannot_overwrite_marked_space() (gas: 40038)
[PASS] test_get_board() (gas: 44622)
[PASS] test_has_empty_board() (gas: 47219)
Test result: ok. 6 passed; 0 failed; finished in 781.38µs
And just like last time, let's extract and name an internal helper:
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;
contract TicTacToken {
string[9] public board;
function getBoard() public view returns (string[9] memory) {
return board;
}
function markSpace(uint256 space, string calldata symbol) public {
require(_validSymbol(symbol), "Invalid symbol");
require(_emptySpace(space), "Already marked");
board[space] = symbol;
}
function _emptySpace(uint256 i) internal view returns (bool) {
return _compareStrings(board[i], "");
}
function _validSymbol(string calldata symbol) internal pure returns (bool) {
return _compareStrings(symbol, "X") || _compareStrings(symbol, "O");
}
function _compareStrings(string memory a, string memory b) internal pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}
Note that this helper is a view
, rather than a pure
function, because it reads from the board
state variable. We've got a good start on a basic board.