9-May-15 Notes on Style Testing the TicTacToe game.
-
Upload
edward-warren -
Category
Documents
-
view
220 -
download
2
Transcript of 9-May-15 Notes on Style Testing the TicTacToe game.
Apr 18, 2023
Notes on Style
Testing the TicTacToe game
A first approach
I want to create a large number of tic-tac-toe partial games for testing purposes
I could do it this way: char[][] array; array = new char[][] { { 'X', ' ', 'O' },
{ ' ', 'X', ' ' }, { 'O', ' ', 'O' } };
...and I could reset the array for each tic-tac-toe board I want to use as input
This looks nice, and is easy to read, but it is a real nuisance to type out a lot of these
So I did this instead
setBoard(" o x o o");
private void setBoard(String xxx) { xxx = xxx.toUpperCase(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { array[i][j] = Character.toUpperCase(xxx.charAt(3 * i + j)); } }}
Now it's a lot easier to create tic-tac-toe boards for testing
Morals
Use methods to make your life easier If something is ugly, hide it in a method
Also... While our main goal should be to write programs that
are easy to read, it isn’t our only goal The best thing to do with hard-to-read methods is to
rewrite them Second best is to explain them in comments I didn’t include the comments on the slide, but they are in
my code!
Refactoring
Refactoring is reorganizing a program without changing what it does
Refactor in order to: Make a program easier to understand Make a program easier to modify
Before refactoring public final void testMakeCornerMove() {
setBoard(" oxoxxoxo"); computerPlayer.makeMove(board); assertBoardIs("xoxoxxoxo");
setBoard("oo xxooxx"); computerPlayer.makeMove(board); assertBoardIs("ooxxxooxx");
setBoard("oxoxxoxo "); computerPlayer.makeMove(board); assertBoardIs("oxoxxoxox");}
I seem to be doing the same thing over and over...
After refactoring private void beforeAndAfterMove(String before, String after)
{ setBoard(before); computerPlayer.makeMove(board); assertBoardIs(after);}
public final void testMakeCornerMove() { // Center and all other corners taken beforeAndAfterMove(" o x o o", "x o x o o"); beforeAndAfterMove("o x o o", "o x x o o"); beforeAndAfterMove("o o x o ", "o o x o x"); beforeAndAfterMove("o o x o", "o o x x o"); // Corner move is all that's left beforeAndAfterMove(" oxoxxoxo", "xoxoxxoxo"); beforeAndAfterMove("oo xxooxx", "ooxxxooxx"); beforeAndAfterMove("oxoxxoxo ", "oxoxxoxox"); beforeAndAfterMove("xxooxxxoo", "xxooxxxoo");}
Moral
The DRY principle: Don’t Repeat Yourself Every piece of data should have a single unique representation
“A man with a watch knows what time it is. A man with two watches is never sure.” -- Segal’s Law
Example: If you have a measure of distance, don’t keep it in two variables, distanceInFeet and distanceInMeters -- keep it in one variable, and use a method to convert to the other units as needed
Each nontrivial operation should be represented by a unique piece of code
Don’t “cut and paste” code--turn it into a method Variations in code can often be handled by a parameter list Corrections and updates are much simpler
Testing for a winning move
Here’s one way to test for a winning move: if (board.get(1, 1) == 'X' && board.get(1, 2) == 'X'
&& board.get(1, 3) == ' ') { board.set(1, 3, 'X'); return true;}
There are 24 combinations to test for This is why I made testing for a winning move optional!
Using a method would help some if (winningMove(1, 1, 1, 2, 1, 3)) return true; But that’s still 24 error-prone lines, plus a method
A bright idea
For each location on the tic-tac-toe board, Put an 'X' in that location Check for a win (with our computerHasWon() method)
If it’s a win, that’s our move Otherwise, put a blank in that location, and keep trying
We can do something very similar for testing if we need to make a blocking move
The code private boolean makeWinningMove(TicTacToeBoard board) {
for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (!board.isEmpty(i, j)) continue; board.set(i, j, 'X'); if (board.computerHasWon()) return true; board.set(i, j, ' '); } } return false;}
This code works, but...
An unexpected consequence Row 1, column 1 is already taken.
Row 1, column 2 is already taken.Row 2, column 1 is already taken.Row 2, column 1 is already taken.Row 2, column 3 is already taken.Row 1, column 2 is already taken.Row 2, column 1 is already taken.Row 1, column 2 is already taken.Row 2, column 1 is already taken.Row 2, column 3 is already taken.Row 3, column 2 is already taken.
Why did this happen? I did check for a blank space before placing my 'X'
In the set method of TicTacToeBoard
if (board[row - 1][column - 1] != ' ') { error("Row " + row + ", column " + column + " is already taken.");}
I can only “set” a location if it is initially blank I never thought about “erasing” an X or an O Proposed solution: Modify the set() method
Problem: I asked you not to modify the provided methods Under these constraints, my “bright idea” cannot be made
to work :-(
Morals
Insofar as possible, methods should do a single thing In particular, it’s usually a bad idea to mix computation
and input/output in the same method If you mix computation and input/output in the same method,
then you can’t do the computation without also doing the input/output
Example: In a previous assignment I specified methods findDayOfWeek to only do computation, and findAndPrintDayOfWeek to call the former and print the results
This allowed me to test your computations without getting a bunch of output
Fix #1 for board.set(row, column, ch)
I could have made set return a boolean--true if the location was set, false if it wasn’t
boolean set(int row, int column, char ch) { if (board[row - 1][column - 1] == ' ' && (ch == 'X' || ch == 'O')) { board[row – 1][column – 1] = ch; return true; } else return false;}
Disadvantage: The user might not check the result Disadvantage: I test for two things that could go wrong (location
is taken, bad character) and this doesn’t distinguish between them
Fix#2 for board.set(row, column, ch)
I could assert that the location is available, and assert that the character is legal
void set(int row, int column, char ch) { assert board[row - 1][column - 1] == ' '; assert ch == 'X' || 'O' ; board[row – 1][column – 1] = ch;}
Disadvantage: Bad use of assert--it should be used for things you believe to be true, not for error checking
Fix#3 and #4
I could throw an Exception for each error condition This is the best solution We haven't covered Exceptions yet
(I nearly forgot this one) I could just skip error checking Big disadvantage: No warning to the user if something is
wrong
The End