In this TD, we will learn to write programs that compute winning strategies for games. In the first part, we will implement a backtracking algorithm that solves Sudoku puzzles. In the second part, we will implement a min-max algorithm that computes the best strategy to win a Nim game.
Sudoku is a popular puzzle game where the objective is to fill out an NxN board with numbers such that certain constraints are respected. Typically Sudoku boards have 9 rows and 9 columns. For example, consider the puzzle on the right of the following picture and its solution on the left:
To begin with, we only consider simple Sudoku puzzles with row and column constraints.
Download and unzip the files in TD9.zip in your eclipse src directory within the TD9 project. It should create the following directory structure
(default package) Test.java puzzle0.txt puzzle1.txt puzzle2.txt (package sudoku) sudoku/GameState.java sudoku/SudokuBoard.java sudoku/SudokuInterface.java sudoku/ValidSudokuBoard.java sudoku/SudokuSolver.java sudoku/SudokuRegionSolver.java sudoku/SudokuDrawing.java sudoku/MoveChooser.java (package nim) nim/GameState.java nim/NimBoard.java nim/NimInterface.java nim/NimSolver.java nim/NimDrawing.java nim/MoveChooser.java
Move the puzzle*.txt files to the main directory for the project. Compile and run Test.java. It should show you a Sudoku Board as follows
This main class for this window is SudokuBoard which implements the interface SudokuInterface. The important methods in this interface are as follows:
public interface SudokuInterface { ... public int getBoardSize(); public boolean hasValue(int x, int y); public int getValue(int x, int y); public LinkedList<Integer> getMoves(int x, int y); public void move(int x, int y, int n); public void undo(int x, int y); ... }
The class SudokuBoard does not check for validity of the values written on the board and hence it accepts invalid solutions. To fix this, we define a new class ValidSudokuBoard as follows:
package sudoku; public class ValidSudokuBoard extends SudokuBoard { public ValidSudokuBoard(int size) { super (size); } public boolean isValidMove(int x, int y, int n) { ... } }You are already provided with a skeleton of this class in the sudoku package. You must define the method isValidMove(int x,int y,int n) which returns true if and only if the proposed value n for the location at row x and column y is valid. That is, that n is a number in the range [1 .. getBoardSize()] and that n does not appear in row x or column y.
You can test this code using the method test1() in Test.java. It should produce the following output:
Loading Sudoku puzzle from file puzzle0.txt Invalid value 1 at position (0,1) Invalid value 1 at position (0,2) ... Invalid value 9 at position (8,7) Invalid value 9 at position (8,8) Loading Sudoku puzzle from file puzzle1.txtIt should also produce a new Sudoku board; you can see that this board now offers fewer options in each drop-down list; this is because the getMoves method calls isValidMove to compute the list of valid moves to offer to the user.
Upload ValidSudokuBoard.java.
public class SudokuSolver extends SudokuBoard { Vector<Set<Integer>> rowMoves; Vector<Set<Integer>> columnMoves; public SudokuSolver(int size) { super (size); initializeSets(); } public void initializeSets(){ rowMoves = new Vector<Set<Integer>>(getBoardSize()); columnMoves = new Vector<Set<Integer>>(getBoardSize()); for (int i = 0; i < getBoardSize(); i++) { HashSet<Integer> rs = new HashSet<Integer>(); rs.addAll(getAllMoves()); rowMoves.add(rs); HashSet<Integer> cs = new HashSet<Integer>(); cs.addAll(getAllMoves()); columnMoves.add(cs); } } ... }The constructor calls initializeSets to initialize all the rowMove and columnMove sets to contain all the numbers (e.g 1,...,9) that may be written to a board location. In this exercise, you must write three methods
public boolean isValidMove(int x, int y, int n); public void move(int x, int y, int n); public void undo(int x, int y);
The test method for this exercise in Test.java is test2(). It should produce the exact same result as Exercise 1.
Upload SudokuSolver.java.
public boolean solve(int i, int j){ ... }Then define the method solve as follows (it is already provided in SudokuSolver); this method is called when the "Solve" button is pressed on the Sudoku board:
public boolean solve(){ boolean b = solve(0,0); if (b) setGameState(GameState.Winning); else setGameState(GameState.Losing); return b; }To test these methods, use test3() and click on the "Solve" button. The resulting board should look like the Sudoku solution shown before the first exercise.
Upload SudokuSolver.java.
Sudoku puzzles often have a third constraint. The board is divided into several two-dimensional regions and the numbers that appear in each region must also be distinct. For example, consider the 9x9 puzzle below, divided into 9 regions of 3x3 each. Then the region constraint is written as follows:
public class SudokuRegionSolver extends SudokuSolver { Vector<Set<Integer>> regionMoves; public SudokuRegionSolver(int size, int x, int y) { super (size,x,y); initializeRegions(); } public void initializeRegions(){ regionMoves = new Vector<Set<Integer>>(getBoardSize()); for (int i = 0; i < getBoardSize(); i++) { HashSet<Integer> rs = new HashSet<Integer>(); rs.addAll(getAllMoves()); regionMoves.add(rs); } } }Define a method getRegion that computes the region that a board location falls in:
public int getRegion(int x, int y);For example, the location (3,5) in a 9x9 puzzle falls in region 4. To program this method, you may use the following methods in SudokuInterface:
public boolean hasRegions(); public int getRegionRows(); public int getRegionColumns();
Loading Sudoku puzzle from file puzzle2.txt The region for location (3,5) is 4 The region for location (8,0) is 6 The region for location (7,7) is 8
It should also display a puzzle board that looks like the original board but with 9 regions being displayed.
You can play with this puzzle. You will find that solving it is more difficult than solving the first puzzle.
Upload SudokuRegionSolver.java.
Upload SudokuRegionSolver.java.
The goal of this TD is to write a Nim playing program that computes the best (winning) strategy before each move by using a min-max algorithm. To begin, you are provided with a class NimBoard that implements the interface NimInterface.
public interface NimInterface { public int getRows(); public int getStones(int row); public void move(int row, int stones); public void printMove(int row, int stones); public void undo(int row, int stones); public GameState evaluateGame(); public GameState max(GameState g1, GameState g2); public GameState negate(GameState g); }The interface uses an enumeration GameState to represent the different possible results of the game for the current player:
public enum GameState{ Unknown, Losing, Winning; }The methods in NimInterface are as follows:
package nim; public class NimSolver extends NimBoard{ public NimSolver(int x, int y) { super(x,y); } public GameState bestResult(){ .... } public GameState worstResult(){ .... } }You are provided with a skeleton of this class and you must implement the bestResult and worstResult methods.
Move: Removing 1 from row 0 Projected result for next player: Winning Move: Removing 1 from row 0 Projected result for next player: Losing Projected result: LosingIt should also show you a Nim board with 1 stone remaining in the first row, 4 stones in the second row, and 5 stones in the third row. The program predicts that the user will definitely lose from this position, if the program always makes the best moves.
Upload NimSolver.java.
public void bestMove() { ... }Just like in bestResult, your code will enumerate all possible next moves and compute worst results for the next game. The best move is one whose worst result in the next game is the maximum of all the worst results. After finding the best move, you must execute the move using move and then use printMove to printout this move. The test code for this exercise is in test7(). It should produce the following output
User's Turn: ------------ Move: Removing 1 from row 0 Projected result for next player: Winning Computer's Turn: ---------------- Move: Removing 1 from row 0 Projected result for next player: Losing ------------ New Game ------------ User's Turn: ------------ Move: Removing 2 from row 0 Projected result for next player: Losing Computer's Turn: ---------------- Move: Removing 1 from row 0 Projected result for next player: WinningIt should also show you a Nim board and the program predicts that you can win. But be careful, your program is now a really smart player!
Upload NimSolver.java.