Here is the code we covered in class today: Kakuro.tar.gz.
This is a backtracking solver that checks for consistency after each variable assignment. It is based on the pseudocode in Figure 6.5 in the text. It does not do inference, and consequently cannot handle larger / harder Kakuro boards.
If you haven’t already started the assignment, then I suggest you start by doing so using your notes from today’s class, rather than just using this code verbatim.
If you must use this code as is, then I strongly suggest you retype it rather than copy-pasting it or directly editing it.
If your current level of programming ability is such that writing this code independently or just using your notes seems very difficult to you, then you’ll benefit from the exercise of re-typing it. You can improve your ability by training your eyes, your hands, and your mind in how to read, write, and understand code. Typing in someone else’s code, rather than just copying and pasting it, helps develop those neural pathways.
I spent another forty minutes making a few more changes to complete the Minimax solver for 3x3 Tic-Tac-Toe. You can download the files here: TicTacToeInClassUpdated.tar.gz.
I also had to identify one bug that stumped me for a bit, details about which are below.
Generally, I broke methods down into smaller pieces. There’s a saying that things are “either obviously not wrong or not obviously wrong.” I find that simplifying methods such that they do only one thing at a time moves them toward the former category and away from the latter. There is some redundancy in the various checks for the end-game conditions now, but the code for each individual one is clearer, in my opinion. It also made catching the aforementioned bug easier.
I moved the test class(es) to their own directory — I think this will make your life easier if you use Eclipse. I also encoded three of the autograder test cases as unit tests on minimax(). Add a few more, and you could start coding the more advanced optimizations with some confidence that you hadn’t broken anything.
I broke up isEndGame() into a set of three checks: victory for each player, or a draw.
I refactored the victory check. Now allEqual() checks whether the values in a series of cells, denoted by indices, are all equal to a given value, and isVictorious() calls it to check for victory. The victoryIndices are still hardcoded; you’d need to set them programmatically to handle variable board sizes.
I wrote isDraw(), which checks to see if there’s no victor, and if the board is full (thus, a draw).
I replaced char constants with public static final constants; among other things, this lets minimax() not have to know about X and O. You could continue to generalize by creating a type-parameterized interface on Board that exposed this information. In fact, if you look up the Java reference code for the textbook, this is the approach that was taken.
I moved minValue() and maxValue() to a standalone Minimax class, and added a minimax() call which decides which of the two to start with.
I modified listSuccessors() to return a List<Board> rather than a Board[]; it depends upon getCurrentPlayer(), which I also made public to allow minimax() to choose the right starting method.
Now, the bug: I originally attempted to use a statement like
to count the number of locations claimed by a particular player (and analogs for the number of plays each player had made). But, it turns out, it always returns zero. Primitive type auto-boxing appears to interfere with its equality checking (see BoardTest.testCount(), and the general contract for frequency()). Oops.
I zeroed in on this error quickly using some of the tests in BoardTest; caveman debugging (System.err.println()) also would work. But my experience is that if you don’t know where to start (and I didn’t: all boards were returning a minimax value of 0), it’s best to check that methods behave the way you expect. Guessing at which variables are causing problems is less principled, and requires adding/removing/commenting-out println()s. Fine for small programs, harder once things get bigger.
As a side bonus, the added unit tests provide further assurance that when you change things (like for alpha-beta pruning or transposition tables) you haven’t broken your code.
Assignment 03 may be resubmitted. The deadline for resubmissions is 1700 on Friday, 17 October.
You can download a set of 3x3 board test cases here: tictactoe-3-test-cases.tar.gz. These correspond to the autograder’s output.
We made a mistake in class on one, by just looking at it and deciding it was a tie:
123
...
.X.
.O.
Not a tie! For example, X plays upper left, O plays in lower right, X plays in lower left, O is unable to block both of X’s win options.
The code as we left it in class is here: TicTacToeInClass.tar.gz, though I plan to either revise it, or continue on with (parts of) it in class Thursday depending upon the input I receive.
We are going to go over Assignment 03 in class in some detail on Tuesday. Then, you will be given a chance to re-submit it if you so desire.
To make time for this, Assignment 04’s due date is going to be moved. We may spend time on it in class Thursday. I am also considering relaxing the problem somewhat.
During office hours yesterday, Patrick worked with a student on alpha/beta pruning and the minimax algorithm. He wrote a short Python2 program to illustrate some of the techniques as described in the book and its examples, and asked me to post it here in case you find it helpful. The program is here: alphabeta.py
On the Edlab, the python command defaults to Python2, so you can use python alphabeta.py -h to see its help text, or, for example, python alphabeta.py -i to watch it run through the minimax algorithm on a randomly-generated tree step by step.
I’ve been meaning to post a sample solution to Assignment 01. A student reminded me yesterday during office hours, so I packaged up the solution I wrote earlier this semester and you can now download it here: assignment01-sample-solution.tar.gz.
Some notes, in no particular order:
There is code for both A* and IDDFS. I wrote the latter first, then added A* support. The Solver class included uses the A* implementation, but it’s a one-line change.
The heuristic that A* uses is inadmissible. That’s fine, as the assignment wanted a solution, not an optimal solution.
A* explores the frontier of the search tree, and keeps a map of the knownCosts to reach nodes it finds. It uses this map for two things: to determine if a node has never been seen before, or to determine if the new path to the node is cheaper than any previously seen path. If either of those conditions holds, it updates its knowledge of the path to the node.
Paths are stored in a map called predecessor; it tracks how to get to a given board by storing its predecessor, which is a pair of the prior board, and the move that transforms that board to the successor. When A* finds the solution, the path to it is recovered by walking back through the relevant entries in this map, adding the moves along the path to a list (then reversing the list, since they’re in reverse order).
The code is set up using Maven, which is the de facto standard Java build and packaging tool. Eclipse has plugins to support it, and IntelliJ supports it out of the box. If you load it up in your IDE, things will go more smoothly if you make sure your IDE recognizes it as a Maven project.
The A* solver depends upon a third-party JAR, available at http://www.javatuples.org/ (or that Maven will download for you).
From the root directory of the project, you can use mvn package to build the assignment01-1.0.jar file, then use java -cp assignment01-1.0.jar Validator ... to run the validator, or java -cp assignment01-1.0.jar:/path/to/javatuples-1.2.jar Solver ... to run the solver. You can also compile the .java files separately using javac, but you’ll need the javatuples jar on your classpath when compiling the Solver.
The project uses JUnit4 to drive test cases, which you can see in src/main/test.
Looking back it at now, there are some obviously kludgy things I did in this project (e.g., should have used an ArrayList and toArray() when reading in the board; not sure why I created an Enum, etc.). Live and learn.
Patrick finished grading Assignment 02 last night. Like the previous assignment, it was mostly done by an autograder on test cases, with manual intervention as needed.
You can download the test cases we used here: assignment02-test-cases.tar.gz. Each test case consists of a problem and a solution as generated by the simple hill climber (SHC). Your program was run on each test case, and its solution was compared with the SHC’s. The SHC did not vary its heuristic when finding solutions, whereas your program was given the evaluation function (coverage or bigger) that it was attempting to optimize.
We placed the output of the autograder into the Moodle comments for the assignment. Assuming your program executed without error, the output you see in Moodle will look something like the following:
The last line is your grade, as described in the assignment. Each other line lists a problem, a heuristic, and your program’s performance relative to the SHC, given as a percentage. For example, in the above output, the submitted program did 7.1% better than the SHC on the 30x30-random.problem with the coverage evaluation function, and 1.4% worse on the bigger evaluation function for that same problem.
If Patrick had to edit your assignment to make it work with the autograder, he deducted 0.1 points (out of 1.0). By far the most common cause of this was programs that didn’t parse command line arguments correctly, ignoring coverage or bigger.
(An aside: All assignments are equally weighted in grade calculations, unless otherwise noted. Assignment 01 isn’t worth ten times as many points as this one!)
Please come see either of us if you have questions or believe we made a mistake in grading your assignment. We want you to get all the credit you’ve earned, and will gladly fix errors on our part.
To test your submission, I ran your validator on each of the validator test cases, and checked to see that its output was correct. I then ran your solver on each of the solver test cases. I took its output and ran it through my own validator. If it was valid, you received credit.
If at any point during a test case your program exited prematurely (with an exception or the like) I considered that test case failed.
If your program took more than twenty seconds (plus some extra wiggle room) on a test case, I considered it failed.
I watched for out-of-memory errors related to the Java heap, but I didn’t see any. There were a few out-of-memory errors related to blowing the stack, but those are in essence programming errors. If you don’t understand why, please come by office hours and Patrick or I will explain.
If you think I mis-graded, please get in touch and explain. I’m happy to grant you credit if I made a mistake!