PentominosSolver
// A pentomino consists of five squares attached along their edges.
// There are exactly twelve possible pentominos that can be formed
// in this way (not counting reflections and rotations). Someone once
// gave me a Pentominos puzzle where the goal was to place the 12
// pentominos on an 8-by-8 board, leaving 4 blank squares in certain
// previously decided positions. This java applet -- the first
// reasonably serious one I have written -- solves this puzzle using
// a recursive backtracking procedure.
//
// David Eck
// Deparment of Mathematics and Computer Science
// Hobart and William Smith Colleges
// Geneva, NY 14456
// Email: eck@hws.edu
//
// January 6, 1996
import java.applet.Applet;
import java.awt.*;
public class PentominosSolver extends Applet implements Runnable {
Button clearBttn, pauseBttn, goBttn, stepBttn;
Label message;
int board[]; // The 8-by-8 board is actually
// represented by a 10-by-10 data structure
// in which the cells along the border
// are declared permanently "filled"
// This simplifies testing whether a given
// piece fits at a given position on the
// board. Furthermore, this 10-by-10 board
// is represented by a 1-dimensional array
// in which the 10*i+j-th entry represents
// row j and column i on the board.
PentominosBoardCanvas boardcanvas; // for displaying the board on the screen
Graphics g = null; // a graphics context for the boardcanvas
boolean used[]; // used[i] tells whether piece # i is already on the board
int numused; // number of pieces currently on the board, from 0 to 12
Thread GameThread = null; // a thread to run the puzzle solving procedure
// (the main applet thread just does the user interface)
int squaresClicked = 0; // user starts by clicking on four squares; this is the
// number clicked so far. Squares are filled in in black.
// The puzzle is to fill in the remaining 60 squares with
// the 12 pentominos
boolean oneStepOnly = false; // this is set to true by the interface if the
// GameThread is supposed to stop after playing
// the next piece.
long lastYieldTime = 0; // last time Thread.yield() was called
final int pieces[][] = {
{ 1, 1,2,3,4 }, // This array represents everything the program
{ 1, 10,20,30,40 }, // knows about the individual pentominos. Each
{ 2, 9,10,11,20 }, // row in the array represents a particular
{ 3, 1,10,19,20 }, // pentomino in a particular orientation. Different
{ 3, 10,11,12,22 }, // orientations are obtained by rotating or flipping
{ 3, 1,11,21,22 }, // the pentomino over. Note that the program must
{ 3, 8,9,10,18 }, // try each pentomino in each possible orientation,
{ 4, 10,20,21,22 }, // but must be careful not to reuse a piece if
{ 4, 1,2,10,20 }, // it has already been used on the board in a
{ 4, 10,18,19,20 }, // different orientation.
{ 4, 1,2,12,22 }, // The pentominoes are numbered from 1 to 12.
{ 5, 1,2,11,21 }, // The first number on each row tells which pentomino
{ 5, 8,9,10,20 }, // that row represents. Note that there can be
{ 5, 10,19,20,21 }, // up to 8 different rows for each pentomino.
{ 5, 10,11,12,20 }, // some pentominos have fewer rows because they are
{ 6, 10,11,21,22 }, // symmetric. For example, the pentomino that looks
{ 6, 9,10,18,19 }, // like:
{ 6, 1,11,12,22 }, // GGG
{ 6, 1,9,10,19 }, // G G
{ 7, 1,2,10,12 }, //
{ 7, 1,11,20,21 }, // can be rotated into three additional positions,
{ 7, 2,10,11,12 }, // but flipping it over will give nothing new.
{ 7, 1,10,20,21 }, // So, it has only 4 entries in the array.
{ 8, 10,11,12,13 }, // The four remaining entries in the array
{ 8, 10,20,29,30 }, // describe the given piece in the given orientation,
{ 8, 1,2,3,13 }, // in a way convenient for placing the piece into
{ 8, 1,10,20,30 }, // the one-dimensional array that represents the
{ 8, 1,11,21,31 }, // board. As an example, consider the row
{ 8, 1,2,3,10 }, //
{ 8, 10,20,30,31 }, // { 7, 1,2,10,19 }
{ 8, 7,8,9,10 }, //
{ 9, 1,8,9,10 }, // If this piece is placed on the board so that
{ 9, 10,11,21,31 }, // its topmost/leftmost square fills position
{ 9, 1,2,9,10 }, // p in the array, then the other four squares
{ 9, 10,20,21,31 }, // will be at positions p+1, p+2, p+10, and p+19.
{ 9, 1,11,12,13 }, // To see whether the piece can be played at that
{ 9, 10,19,20,29 }, // position, it suffices to check whether any of
{ 9, 1,2,12,13 }, // these five squares are filled.
{ 9, 9,10,19,29 }, // On the board, each piece will be shown
{ 10, 8,9,10,11 }, // in its own color.
{ 10, 9,10,20,30 },
{ 10, 1,2,3,11 },
{ 10, 10,20,21,30 },
{ 10, 1,2,3,12 },
{ 10, 10,11,20,30 },
{ 10, 9,10,11,12 },
{ 10, 10,19,20,30 },
{ 11, 9,10,11,21 },
{ 11, 1,9,10,20 },
{ 11, 10,11,12,21 },
{ 11, 10,11,19,20 },
{ 11, 8,9,10,19},
{ 11, 1,11,12,21 },
{ 11, 9,10,11,19 },
{ 11, 9,10,20,21 },
{ 12, 1,10,11,21 },
{ 12, 1,2,10,11 },
{ 12, 10,11,20,21 },
{ 12, 1,9,10,11 },
{ 12, 1,10,11,12 },
{ 12, 9,10,19,20 },
{ 12, 1,2,11,12 },
{ 12, 1,10,11,20 },
};
public static void main(String args[]) { // more or less from "CardTest demo" example
Frame f = new Frame("Pentominos Solver");
PentominosSolver ps = new PentominosSolver();
ps.init();
ps.start();
f.add("Center",ps);
f.resize(250,300);
f.show();
}
public void init() {
board = new int[100];
used = new boolean[13];
Panel topPanel = new Panel(); // holds an informational message
Panel bottomPanel = new Panel(); // holds four control buttons
setLayout(new BorderLayout());
add("South", bottomPanel);
add("North", topPanel);
clearBttn = new Button("Clear"); // the control buttons
pauseBttn = new Button("Pause");
goBttn = new Button("Go");
stepBttn = new Button("Step");
bottomPanel.add(clearBttn);
bottomPanel.add(pauseBttn);
bottomPanel.add(goBttn);
bottomPanel.add(stepBttn);
message = new Label("Click Four Square"); // the message
topPanel.add(message);
boardcanvas = new PentominosBoardCanvas(board,this); // for displaying the board
add("Center",boardcanvas);
for (int i=0; i<100; i++) // fill in the border with -1's
board[i] = -1;
for (int i=1; i<9; i++) // fill in the rest of the board with empty spaces (0's)
for (int j=1; j<9; j++)
board[j*10+i] = 0;
stepBttn.disable();
goBttn.disable();
pauseBttn.disable();
}
boolean putPiece(int p, int square) { // try to place a piece on the board,
// return true if it fits
int name = pieces[p][0];
if (board[square] != 0)
return false;
for (int i = 1; i <= 4; i ++)
if (board[square + pieces[p][i]] != 0) // one of the squares needed is already occupied
return false;
boardcanvas.putSquare(g,name,square); // color in the squares to represent the piece
for (int i = 1; i <= 4; i++)
boardcanvas.putSquare(g,name,square+pieces[p][i]);
return true;
}
void removePiece(int p, int square) {
boardcanvas.clearSquare(g,square);
for (int i = 1; i <= 4; i++)
boardcanvas.clearSquare(g,square+pieces[p][i]);
}
void play(int square) { // recursive procedure that tries to solve the puzzle
// parameter "square" is the number of the next empty
// to be filled
for (int p=0; p<63; p++)
if ((used[pieces[p][0]] == false) && putPiece(p,square)) { // try piece p
used[pieces[p][0]] = true;
numused++;
if (numused == 12 || oneStepOnly) { // puzzle is solved or should be suspended
if (oneStepOnly)
oneStepOnly = false;
else {
stepBttn.enable();
goBttn.enable();
pauseBttn.disable();
}
GameThread.suspend();
}
else if (java.lang.System.currentTimeMillis() - lastYieldTime >= 100) {
Thread.yield();
lastYieldTime = java.lang.System.currentTimeMillis();
}
if (numused < 12) {
int nextSquare = square;
while (board[nextSquare] != 0) // find next empty square
nextSquare++;
play(nextSquare); // and try to complete the solution
}
removePiece(p,square); // backtrack
numused--;
used[pieces[p][0]] = false;
}
}
public void run() {
g = boardcanvas.getGraphics(); // run() procedure for GameThread;
message.setText("Solving..."); // try to solve the puzzle
for (int i=1; i<=12; i++)
used[i] = false;
numused = 0;
int square = 11;
while (board[square] == -1)
square++;
lastYieldTime = java.lang.System.currentTimeMillis();
play(square);
stepBttn.disable();
goBttn.disable();
pauseBttn.disable();
message.setText("No More Solutions");
}
void doClear() { // responce procedure for "Clear" button: restore initial state
if (GameThread != null) {
GameThread.stop();
GameThread = null;
g = null;
}
if (squaresClicked > 0) {
for (int i=0; i<100; i++) // fill in the border with -1's
board[i] = -1;
for (int i=1; i<9; i++) // fill in the rest of the board with empty spaces (0's)
for (int j=1; j<9; j++)
board[j*10+i] = 0;
boardcanvas.repaint();
squaresClicked = 0;
}
stepBttn.disable();
goBttn.disable();
pauseBttn.disable();
message.setText("Click Four Squares");
}
void doBoardClick(int square) { // respond to click on boardcanvas
// (called from PentominosBoardCanvas::mouseDown)
if (GameThread == null && squaresClicked < 4) {
if (board[square] == 0) { // fill in the clicked sqaure
squaresClicked++;
Graphics g1 = boardcanvas.getGraphics();
boardcanvas.blackenSquare(g1,square);
}
if (squaresClicked == 4) { // if four squares have been filled, start solving the puzzle
GameThread = new Thread(this);
int pri = GameThread.getPriority() - 1;
if (pri >= Thread.MIN_PRIORITY)
GameThread.setPriority(pri);
GameThread.start();
pauseBttn.enable();
}
}
}
void doPause() { // response procedure for Pause button
oneStepOnly = true; // tells GameThread to suspend itself
stepBttn.enable();
goBttn.enable();
pauseBttn.disable();
}
void doStep() { // response to Step button
oneStepOnly = true;
GameThread.resume();
}
void doGo() { // response to Go button
stepBttn.disable();
goBttn.disable();
pauseBttn.enable();
lastYieldTime = java.lang.System.currentTimeMillis();
GameThread.resume();
}
public boolean action(Event evt, Object arg) { // Check for clicks on buttons
if (evt.target instanceof Button) {
if ("Clear".equals(arg))
doClear();
else if ("Pause".equals(arg))
doPause();
else if ("Go".equals(arg))
doGo();
else if ("Step".equals(arg))
doStep();
return true;
}
else
return false;
}
} // end of class PentominosSolver
class PentominosBoardCanvas extends Panel { // implement the visible 8-by-8 playing board
int[] board; // The board data array, passed into the constructor and
// used throughout this class
PentominosSolver owner; // The applet, passed into constructor, and
// used in the mouseDown handler
Color pieceColor[]; // Array of colors to be used in displaying pieces
// pieceColor[0] is the color of an empty square,
// pieceColor[1] is the color for piece number 1, etc.
PentominosBoardCanvas(int[] theBoard, PentominosSolver theOwner) { // Constructor
board = theBoard;
owner = theOwner;
MakeColors(); // create and fill in pieceColor[] array
}
void MakeColors() {
pieceColor = new Color[13];
pieceColor[0] = Color.white;
pieceColor[1] = new Color(200,0,0);
pieceColor[2] = new Color(150,150,255);
pieceColor[3] = new Color(0,200,200);
pieceColor[4] = new Color(255,150,255);
pieceColor[5] = new Color(0,200,0);
pieceColor[6] = new Color(150,255,255);
pieceColor[7] = new Color(200,200,0);
pieceColor[8] = new Color(0,0,200);
pieceColor[9] = new Color(255,150,150);
pieceColor[10] = new Color(200,0,200);
pieceColor[11] = new Color(255,255,150);
pieceColor[12] = new Color(150,255,150);
}
public synchronized void paint(Graphics g) { // redraw the whole board
int w = size().width;
w = 8 * (w/8) + 1;
int h = size().height;
h = 8 * (h/8) + 1;
Color oldColor = g.getColor();
g.setColor(Color.black);
for (int i = 0; i <= 8; i++) {
int x = i * ((w - 1) / 8);
g.drawLine(x,0,x,h-1);
}
for (int i = 0; i <= 8; i++) {
int y = i * ((h - 1) / 8);
g.drawLine(0,y,w-1,y);
}
for (int i = 1; i <= 8; i++) {
int y = (i-1) * ((h-1) / 8);
for (int j = 1; j <= 8; j++) {
int x = (j-1) * ((w-1) / 8);
if (board[10*i + j] == -1)
g.setColor(Color.black);
else
g.setColor(pieceColor[board[10*i + j]]);
g.fillRect(x+1, y+1, (w-1) / 8 - 1, (h-1) / 8 - 1);
}
}
g.setColor(oldColor);
}
synchronized void putSquare(Graphics g, int name, int square) { // "name" gives the piece number
int w = size().width;
w = 8 * (w/8) + 1;
int h = size().height;
h = 8 * (h/8) + 1;
int x = ((square % 10)-1) * ((w-1)) / 8;
int y = ((square / 10)-1) * ((h-1)) / 8;
g.setColor(pieceColor[name]);
g.fillRect(x+1, y+1, (w-1)/8 - 1, (h-1)/8 - 1);
g.setColor(Color.black);
board[square] = name;
}
synchronized void clearSquare(Graphics g, int square) {
int w = size().width;
w = 8 * (w/8) + 1;
int h = size().height;
h = 8 * (h/8) + 1;
int x = ((square % 10)-1) * ((w-1)) / 8;
int y = ((square / 10)-1) * ((h-1)) / 8;
g.setColor(pieceColor[0]);
g.fillRect(x+1, y+1, (w-1)/8 - 1, (h-1)/8 - 1);
g.setColor(Color.black);
board[square] = 0;
}
synchronized void blackenSquare(Graphics g, int square) {
int w = size().width;
w = 8 * (w/8) + 1;
int h = size().height;
h = 8 * (h/8) + 1;
int x = ((square % 10)-1) * ((w-1)) / 8;
int y = ((square / 10)-1) * ((h-1)) / 8;
g.fillRect(x, y, (w-1)/8, (h-1)/8);
board[square] = -1;
}
public boolean mouseDown(Event evt, int x, int y) {
int w = size().width;
w = 8 * (w/8) + 1;
int h = size().height;
h = 8 * (h/8) + 1;
int row = (y / ((h-1)/8)) + 1;
int col = (x / ((w-1)/8)) + 1;
if (row > 0 && row < 9 && col > 0 && col < 9)
owner.doBoardClick(10*row + col);
return true;
}
public Dimension minimumSize() {
return new Dimension(160,160);
}
public Dimension preferredSize() {
return minimumSize();
}
}
New on the Java Boutique:
New Review:
Time Management Made Easy with the Quartz Enterprise Job Scheduler
Why not just use the Java timer API? This open source scheduling
API boasts simplicity, ease-of-integration, a well-rounded feature
set, and it's free!
New Applet:
Reverse Complement
Reverse Complement is a simple applet that converts DNA or RNA
sequences into three useful formats.
Elsewhere on internet.com:
WebDeveloper Java
Lots of Java information on webdeveloper.com
WDVL Java
Thorough Java resource at the Web Developer's Virtual Library.
ScriptSearch Java
Hundreds of free Java code files to download.
jGuru: Your View of the Java Universe
Customizable portal with online training, FAQs, regular news updates, and tutorials.
|