Module 9: Supplemental Material
Tower of Hanoi
Let's build a GUI for the Tower of Hanoi:
- We'll use simple rectangles for the towers and disks.
- When each move occurs, we'll update the GUI to show the towers
and disks.
- After each move, we will pause to give an animation effect.
- The GUI code will be in a separate class.
Here's how we'll rewrite the main Tower-of-Hanoi solution:
(source file)
import java.util.*;
public class TowerOfHanoi4 {
// stacks[0], stacks[1] and stacks[2] are the three stacks.
static Stack<Integer>[] towers;
static HanoiGUI hanoiGUI;
public static void main (String[] argv)
{
// A 4-disk puzzle:
System.out.println ("4-Disk solution: ");
solveHanoi (3, 0, 1);
}
static void solveHanoi (int n, int i, int j)
{
// Create the three stacks and initialize ... this code is the same ...
// GUI. Note: n+1 = # disks. We will pass in the stacks.
hanoiGUI = new HanoiGUI (towers, n+1);
// Now solve recursively as before.
solveHanoiRecursive (n, i, j);
}
static void solveHanoiRecursive (int n, int i, int j)
{
// ...
}
static void move (int n, int i, int j)
{
// Pull out the top disk on stack i and move to j ... same as before ...
// This method will handle re-drawing the towers/disks.
hanoiGUI.updateGUI ();
// Pause execution for animation effect.
try {
Thread.sleep (1000);
}
catch (InterruptedException e) {
}
}
static int other (int i, int j)
{
// ...
}
}
Exercise 1:
Download TowerOfHanoi4.java and
the file HanoiGUI.class. Then
compile and execute TowerOfHanoi4. You should see
an animation of the solution.
Next, download HanoiGUITemplate.java. Save
the latter as HanoiGUI.java. Then compile and execute.
You will notice that the GUI code in the template
only draws the three towers. You are to fill in the code to draw
the disks. All you need to do is call g.fillRect(), the
parameters of which are: the topleft corner (first two parameters)
and the width and height.
N-Queens
Let us examine the class ChessBoard:
(source file)
- Recall, there were methods for adding a queen, removing one,
and testing if a particular square was forbidden.
- Thus the structure of the class is:
public class ChessBoard {
// Constructor.
public ChessBoard (int size)
{
// Build the board ...
}
public int size ()
{
// Return the size ...
}
public void addQueen (int row, int col)
{
// Add a queen to the square [row,col] ...
}
public void removeQueen (int row, int col)
{
// Remove a queen from the square [row,col] ...
}
public boolean isForbidden (int row, int col)
{
// Check whether [row,col] is attacked ...
}
}
- The underlying data structure is not relevant to the user as
long as the methods perform their actions as advertized.
- We will use a 2D array to represent the board:
public class ChessBoard {
// Instance variables.
int size;
char[][] board; // board[i][j] == 'X' if there's a queen on it.
// Constructor.
public ChessBoard (int size)
{
}
// ...
}
- Notice that we have used a char array.
- We could just as easily have used an int array.
- Now let's write the constructor and the size()
method:
public class ChessBoard {
// Instance variables.
int size;
char[][] board; // board[i][j] == 'X' if there's a queen on it.
// Constructor.
public ChessBoard (int size)
{
// Build the board. Initially empty: board[i][j] == 'O'.
this.size = size;
board = new char [size][size];
for (int i=0; i < size; i++) {
for (int j=0; j < size; j++) {
board[i][j] = 'O';
}
}
}
public int size ()
{
return size;
}
// ...
}
Thus, we use the character 'X' to represent the presence
of a queen and 'O' to represent an empty square.
- Next, let's write the add/remove methods:
public class ChessBoard {
// ...
public void addQueen (int row, int col)
{
board[row][col] = 'X';
}
public void removeQueen (int row, int col)
{
board[row][col] = 'O';
}
// ...
}
- The most complicated method to write is
isForbidden():
- Given the current board, and a square [row,col], we need to
decide whether [row,col] can be attacked by any of the queens
already on the board.
- It's easy to check whether the given row or column has a queen.
- The diagonals are a little more complicated.
Here's the code:
public boolean isForbidden (int row, int col)
{
// First, try the row.
for (int c=0; c < size; c++) {
if (board[row][c] == 'X') {
return true;
}
}
// Now try the column
for (int r=0; r < size; r++) {
if (board[r][col] == 'X') {
return true;
}
}
// Now the diagonals.
for (int r=0; r < size; r++) {
for (int c=0; c < size; c++) {
if ( (r != row) && (c != col) && (board[r][c] == 'X') ) {
// See if (r,c) can be attacked by (row,col).
if (Math.abs(r-row) == Math.abs(c-col)) {
return true;
}
}
}
}
// If we reach here, there's no queen attacking [row,col]
return false;
}
For the diagonal:
- We exploit the fact that diagonals are at
45o.
⇒
The row difference and column difference are the same for two
squares on a diagonal.
The class ChessBoard has the code for
displaying the board in a GUI. Let's examine some of this code:
- The general structure of the GUI:
- Create a JFrame (a class in the Java library).
- Place a JPanel inside the frame on which all the
drawing is done.
- To do our own drawing, we need to extend the class
JPanel and override the paintComponent() method.
- The code for creating the frame is in the display()
method of ChessBoard:
public void display ()
{
// Make the frame and set some of its parameters:
JFrame f = new JFrame ();
f.setSize (300,300);
f.setTitle ("N Queens problem");
// Create our extension of the JPanel:
ChessPanel drawPanel = new ChessPanel ();
// We'll need to pass on the data:
drawPanel.size = size;
drawPanel.board = board;
// Add this to the frame. Notice the strange syntax.
f.getContentPane().add (drawPanel);
// Bring up the frame.
f.setVisible (true);
}
- The structure of the class ChessPanel (which we
wrote):
class ChessPanel extends JPanel {
int size;
char[][] board;
public void paintComponent (Graphics g)
{
// ... This is where all the drawing is done ...
}
}
- Drawing commands themselves are methods of the class
Graphics, an instance of which is available inside paintComponent().
Exercise 2:
Download ChessBoard.java and
NQueens.java.
You will also
need ImageTool.java
and queen.jpg.
Then,
replace the char array in ChessBoard with an int
equivalent. What changes do you need to make in the code?
Would it be possible to use a boolean array?
An alternative data structure:
- Instead of using a 2D array, we could easily use a 1D array:
(source file)
public class ChessBoard2 {
int size;
int[] columns; // column[r] = which column in row r has a queen, if any
// column[r] = -1, if this row has no queen
// ...
}
- We can do this because we know that no row can have two
queens:
- Either there's no queen (column[r] == -1) or,
- There is a queen in column c (column[r] == c).
- Then, the methods for adding and removing become:
public void addQueen (int row, int col)
{
columns[row] = col;
}
public void removeQueen (int row, int col)
{
columns[row] = -1;
}
- The method isForbidden() is a little more complex
to re-write:
public boolean isForbidden (int row, int col)
{
// First, try the row.
if (columns[row] >= 0) {
return true;
}
// Now try the columns.
for (int c=0; c < size; c++) {
if (columns[row] == col) {
return true;
}
}
// Now the diagonals.
for (int r=0; r < size; r++) {
if (columns[r] >= 0) {
if (Math.abs(columns[r]-col) == Math.abs(row-r)) {
return true;
}
}
}
return false;
}
Exercise 3:
Download ChessBoard2.java and
NQueens2.java and read through
the implementation of ChessBoard2.
Apart from the space savings, what is the advantage of
using the 1D array?
© 2006, Rahul Simha (revised 2017)