Lab idea from Dave Reed, Creighton University.
In the 3-In-A-Row puzzle you are given an n by n board for some even integer n. The board is partially filled—some cells are occupied by blue squares, and some by white squares. The rest of the board is empty (shown as gray).
Figure 1. Input board
You have to fill the board with blue and white squares in such a way that the following two conditions are satisfied.
- No row or column has 3-In-A-Row (or 3-In-A-Column) squares of the same color, and
- Each row and column has equal number of blue and white squares.
The given puzzle has this solution:
Figure 2. Solution
A human trying to solve this puzzle will have to carefully reason her way through it using logical analysis. See an example of such analysis at 3-In-A-Row help. Nevertheless, we can write a computer program that takes an initial board configuration and searches for an answer. However, due to the exponential growth rate in the size of the search space, a naive brute-force search will take prohibitively long. We need to search intelligently.
Backtracking is an established search method for solving many intractable computational problems, including a problem like the 3-In-A-Row puzzle. The algorithm starts with an empty current partial solution. It keeps attempting to complete the current partial solution to a full solution by incrementally adding to the current partial solution one new item at each step. It will systematically try all possibilities of expanding the current partial solution, so long as it cannot prove that the current partial solution is doomed, i.e., not capable of being expanded to a complete solution. Immediately after trial adding a new item to the current partial solution, it checks whether this results in a full solution or not. If it does, the algorithm halts and declares success. Otherwise, it checks whether the last addition has resulted in a doomed partial solution or not. If the new partial solution is not doomed, it continues the search using the new partial solution as the current partial solution. If the new partial solution is doomed, it retracts the last addition and continues with the next possible way to expand this current partial solution. If all possible ways to expand the current partial solution has been exhausted without finding a solution, it retracts its last addition, and continues the search. The search stops when we have checked all possible ways to expand the current partial solution without finding a solution, and the current partial solution is empty. In such a case, it declares failure.
This basic algorithm can be modified to search for all solutions instead of just one solution.
First, make sure you understand the rules and objectives of the puzzle. Second, study the lecture notes on backtracking. Third, design your backtracking search algorithm for the puzzle. Decide on the data structures to be used, the ordering of the items to be added to the current partial solution, how to check for success condition, how to check whether a partial solution is doomed or not, etc. Fourth, write pseudocode for the search. Last, you can now implement your pseudocode as a Kotlin program. Don’t forget to test as soon as you have written even one function.
Instead of being given a skeleton Kotlin file to start from, you are going to write the code more or less from scratch. However, in order to enforce some uniformity (which will make the grading easier), we request that you do certain things:
You must write at least one file called
TiarSolverText.kt. This file should contain the methods that together perform the backtracking search. The necessary data structures can be kept within this one file, or you may create a separate class for
Board. You can make the
Boardclass disjoint from or include it in
TiarSolverText.kt. It’s all up to you.
mainfunction is top-level and should be in
TiarSolverText.kt. Your program should read in the data for the initial board from a file whose name is given on the command line. It writes a solution (if there is one) to the standard output; it writes a failure message if there is no solution.
The format of the input is like this. The first line contains an even positive integer
n. (This means the dimension of the board is
n.) Then follow
nlines, each line containing exactly
ncharacters. A position has the character
Wif there is a white square there. It has the character
Bif there is a blue square there. The character
-denotes a blank square. For example, the board in Fig 1 is encoded like this
6 --W-B- ------ BB--W- -B---- -----W -BW---
The format of the output is like this. It consists of exactly
nlines, each line of which contains exactly
ncharacters. Each character is either
B, denoting a white or blue square in that position, respectively. For example, the board in Fig 2 is encoded like this
BWWBBW WWBWBB BBWBWW WBBWWB BWBWBW WBWBWB
Your final program should be run like this. Let’s say you write only one Kotlin file
TiarSolverText.kt. It defines all the functions necessary for doing the backtracking search, and also contains the top-level
mainfunction. Suppose further that your data file is named
3inarow.inand contains the input as described in the example above. You should be able to compile and run your program by typing
$ kotlinc TiarSolverText.kt $ kotlin TiarSolverTextKt 3inarow.in
Your program should output the 6 output lines described above.
Suppose we also have a file
impossible.inwith this content
6 B-BB-- -W-W-W ------ B-B-BB ---B-- -W----
Clearly this input file does not have a solution. So running
$ kotlin TiarSolverTextKt impossible.in
should give a failure message like
There is no solution.
You must have completed the program for the regular part described above before attempting this extra credit part. Your attempted extension to the basic program will not be graded unless you also submit a correct program for the regular part. You can make either one or both of the following extensions.
The first entension is to make your program output graphically. Instead of just simple text, make your program display both the input board along side the solution board. This will make it easy for the user to check the correctness of the program’s solution. Here is the result of running my program on the 03-26-easy-6x6.in input file.
Figure 3. Input and solution boards for
The second extension is to animate the backtracking search like this
Figure 4. Input board and search animation for
except that, in contrast to the animated gif, your animation should not loop. Your program should animate how the backtrack search proceeds until it finds a solution or determines that there is none, and then stop.
The third extension is to combine all three versions of the program (text, GUI, and animation) into one program. The user chooses which version to execute by providing the appropriate flag on the command line.
The fourth extension is to write a text-only version of the program that prints all solutions.
We will use this gradesheet when grading your lab.
As usual, you should submit via Moodle. Simply submit
TiarSolverText.kt if you attempt the basic backtracking algorithm only. You should create a new project for each extra credit you attempt. However, when submitting the extra credit work, simply submit the kotlin source code.