import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * This applet solves pentominos puzzles. A pentomino consists of 5 connected squares. There are exactly * 12 ways to make a pentomino (counting rotations and reflections of a piece as the same piece). A * pentominos puzzle consists of trying to place the pieces on a board, without overlapping any of * the pieces. This version uses an 8-by-8 board. In this case, the pentominos will fill * 60 of the 64 avaliable squares on the board. The squares that are to be left empty are selected * in advance at random and are colored black. *

You can use the program and the source code free of charge for any purpose in unmodified * form. If you want to make a modified version and distribute it to other people, please contact * me about getting permission to do so. *

David J. Eck, eck@hws.edu, http://math.hws.edu/eck/ *

See http://math.hws.edu/xJava/PentominosSolver for a greatly enhanced version of this applet. */ public class LittlePentominosApplet extends JApplet { private MosaicPanel board; // for displaying the board on the screen private boolean[] used = new boolean[13]; // used[i] tells whether piece # i is already on the board private int numused; // number of pieces currently on the board, from 0 to 12 private GameThread gameThread = null; // a thread to run the puzzle solving procedure volatile private int delay = 100; private static final int rows = 8, cols = 8; // Number of rows and columns in the board. private final static int GO_MESSAGE = 1; // the values for the message variable private final static int PAUSE_MESSAGE = 3; private final static int RESTART_RANDOM_MESSAGE = 6; private final static int TERMINATE_MESSAGE = 7; /** * This data structure represents the pieces. There are 12 pieces, and each piece can be rotated * and flipped over. Some of these motions leave the peice changed because of symmetry. Each distinct * position of each piece has a line in this array. Each line has 9 elements. The first element is * the number of the piece, from 1 to 12. The remaining 8 elements describe the shape of the piece * in the following peculiar way: One square is assumed to be at position (0,0) in a grid; the square is * chosen as the "top-left" square in the piece, in the sense that all the other squares are either to the * right of this square in the same row, or are in lower rows. The remaining 4 squares in the piece are * encoded by 8 numbers that give the row and column of each of the remaining squares. If the eight numbers * that describe the piece are (a,b,c,d,e,f,g,h) then when the piece is placed on the board with the top-left * square at position (r,c), the remaining squares will be at positions (r+a,c+b), (r+c,c+d), (r+e,c+f), and * (r+g,c+h). this representation is used in the putPiece() and removePiece() metthods. */ private static final int[][] pieces = { { 1, 0,1,0,2,0,3,0,4 }, // Describes piece 1 (the "I" pentomino) in its horizontal orientation. { 1, 1,0,2,0,3,0,4,0 }, // Describes piece 1 (the "I" pentomino) in its vertical orientation. { 2, 1,-1,1,0,1,1,2,0 }, // The "X" pentomino, in its only orientation. { 3, 0,1,1,0,2,-1,2,0 }, // etc.... { 3, 1,0,1,1,1,2,2,2 }, { 3, 0,1,1,1,2,1,2,2 }, { 3, 1,-2,1,-1,1,0,2,-2 }, { 4, 1,0,2,0,2,1,2,2 }, { 4, 0,1,0,2,1,0,2,0 }, { 4, 1,0,2,-2,2,-1,2,0 }, { 4, 0,1,0,2,1,2,2,2 }, { 5, 0,1,0,2,1,1,2,1 }, { 5, 1,-2,1,-1,1,0,2,0 }, { 5, 1,0,2,-1,2,0,2,1 }, { 5, 1,0,1,1,1,2,2,0 }, { 6, 1,0,1,1,2,1,2,2 }, { 6, 1,-1,1,0,2,-2,2,-1 }, { 6, 0,1,1,1,1,2,2,2 }, { 6, 0,1,1,-1,1,0,2,-1 }, { 7, 0,1,0,2,1,0,1,2 }, { 7, 0,1,1,1,2,0,2,1 }, { 7, 0,2,1,0,1,1,1,2 }, { 7, 0,1,1,0,2,0,2,1 }, { 8, 1,0,1,1,1,2,1,3 }, { 8, 1,0,2,0,3,-1,3,0 }, { 8, 0,1,0,2,0,3,1,3 }, { 8, 0,1,1,0,2,0,3,0 }, { 8, 0,1,1,1,2,1,3,1 }, { 8, 0,1,0,2,0,3,1,0 }, { 8, 1,0,2,0,3,0,3,1 }, { 8, 1,-3,1,-2,1,-1,1,0 }, { 9, 0,1,1,-2,1,-1,1,0 }, { 9, 1,0,1,1,2,1,3,1 }, { 9, 0,1,0,2,1,-1,1,0 }, { 9, 1,0,2,0,2,1,3,1 }, { 9, 0,1,1,1,1,2,1,3 }, { 9, 1,0,2,-1,2,0,3,-1 }, { 9, 0,1,0,2,1,2,1,3 }, { 9, 1,-1,1,0,2,-1,3,-1 }, { 10, 1,-2,1,-1,1,0,1,1 }, { 10, 1,-1,1,0,2,0,3,0 }, { 10, 0,1,0,2,0,3,1,1 }, { 10, 1,0,2,0,2,1,3,0 }, { 10, 0,1,0,2,0,3,1,2 }, { 10, 1,0,1,1,2,0,3,0 }, { 10, 1,-1,1,0,1,1,1,2 }, { 10, 1,0,2,-1,2,0,3,0 }, { 11, 1,-1,1,0,1,1,2,1 }, { 11, 0,1,1,-1,1,0,2,0 }, { 11, 1,0,1,1,1,2,2,1 }, { 11, 1,0,1,1,2,-1,2,0 }, { 11, 1,-2,1,-1,1,0,2,-1 }, { 11, 0,1,1,1,1,2,2,1 }, { 11, 1,-1,1,0,1,1,2,-1 }, { 11, 1,-1,1,0,2,0,2,1 }, { 12, 0,1,1,0,1,1,2,1 }, { 12, 0,1,0,2,1,0,1,1 }, { 12, 1,0,1,1,2,0,2,1 }, { 12, 0,1,1,-1,1,0,1,1 }, { 12, 0,1,1,0,1,1,1,2 }, { 12, 1,-1,1,0,2,-1,2,0 }, { 12, 0,1,0,2,1,1,1,2 }, { 12, 0,1,1,0,1,1,2,0 } }; private Color pieceColor[] = { // the colors of pieces number 1 through 12; pieceColor[0] is not used. null, new Color(200,0,0), new Color(150,150,255), new Color(0,200,200), new Color(255,150,255), new Color(0,200,0), new Color(150,255,255), new Color(200,200,0), new Color(0,0,200), new Color(255,150,150), new Color(200,0,200), new Color(255,255,150), new Color(150,255,150) }; private final static Color emptyColor = Color.BLACK; // the color of a square that the user has seleted to be left empty. public void init() { board = new MosaicPanel(rows,cols); // for displaying the board board.setAlwaysDrawGrouting(true); board.setDefaultColor(Color.WHITE); board.setGroutingColor(Color.LIGHT_GRAY); board.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY,2)); setContentPane(board); board.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { start(); } }); } public void destroy() { // Stop the thread when the applet is destoryed. terminate(); } public void start() { // Start the thread, if not already done, and set up a random board. if (gameThread == null || !gameThread.isAlive()) { gameThread = new GameThread(); gameThread.start(); } gameThread.setMessage(RESTART_RANDOM_MESSAGE); } public void stop() { // Pause the game thread while the applet is stopped. gameThread.setMessage(PAUSE_MESSAGE); } synchronized private void terminate() { // Stop the game thread -- only used by the destroy() method. gameThread.setMessage(TERMINATE_MESSAGE); notify(); gameThread.doDelay(25); board = null; } private class GameThread extends Thread { // This represents the thread that solves the puzzle. int moveCount; // How many pieces have been placed so far volatile boolean running; // True when the solution process is running (and not when it is paused) boolean aborted; // used in play() to test whether the solution process has been aborted by a "restart" volatile int message = 0; // "message" is used by user-interface thread to send control messages // to the game-playing thread. A value of 0 indicates "no message" volatile boolean checkForBlocks = true; // If true, a check is made for obvious blocking. volatile boolean symmetryCheck; // If true, the symmetry of the board is checked, and if it has any symmetry, // some pieces are removed from the list to avoid redundant solutions. int[][] blockCheck; // Used for checking for blocking. int blockCheckCt; // Number of times block check has been run -- used in controling recursive counting instead of just using a boolean array. int squaresLeftEmpty; // squares actually left empty in the solution so far boolean putPiece(int p, int row, int col) { // try to place a piece on the board, return true if it fits if (board.getColor(row,col) != null) return false; for (int i = 1; i < 8; i += 2) { if (row+pieces[p][i] < 0 || row+pieces[p][i] >= rows || col+pieces[p][i+1] < 0 || col+pieces[p][i+1] >= cols) return false; else if (board.getColor(row+pieces[p][i],col+pieces[p][i+1]) != null) // one of the squares needed is already occupied return false; } board.setColor(row,col,pieceColor[pieces[p][0]]); for (int i = 1; i < 8; i += 2) board.setColor(row + pieces[p][i], col + pieces[p][i+1], pieceColor[pieces[p][0]]); return true; } void removePiece(int p, int row, int col) { // Remove piece p from the board, at position (row,col) board.setColor(row,col,null); for (int i = 1; i < 9; i += 2) { board.setColor(row + pieces[p][i], col + pieces[p][i+1], null); } } void play(int row, int col) { // recursive procedure that tries to solve the puzzle // parameter "square" is the number of the next empty // to be filled. This is only complicated beacuse all // the details of speed/pause/step are handled here. for (int p=0; p 0) { // test for "messages" generated by user actions while (message == PAUSE_MESSAGE) { doDelay(-1); // Wait until a new message is received } if (aborted) return; if (message >= RESTART_RANDOM_MESSAGE) { aborted = true; return; // note: don't setMessage(0), since run() has to handle message } else { // go message running = true; setMessage(0); } } if (numused == 12) { // puzzle is solved setMessage(RESTART_RANDOM_MESSAGE); doDelay(5000); return; } doDelay(delay); int nextRow = row; // find next empty space, going left-to-right then top-to-bottom int nextCol = col; while (board.getColor(nextRow,nextCol) != null) { // find next empty square nextCol++; if (nextCol == cols) { nextCol = 0; nextRow++; if (nextRow == row) // We've gone beyond the end of the board! throw new IllegalStateException("Internal Error -- moved beyond end of board!"); } } play(nextRow, nextCol); // and try to complete the solution if (aborted) return; removePiece(p,row,col); // backtrack numused--; used[pieces[p][0]] = false; } } } boolean obviousBlockExists() { // Check whether the board has a region that can never be filled because of the number of squares it contains. blockCheckCt++; for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) { int blockSize = countEmptyBlock(r,c); if (blockSize % 5 != 0) return true; } return false; } int countEmptyBlock(int r, int c) { // Find the size of one empty region on the board; recursive routine called by obviousBlockExists. if (blockCheck[r][c] == blockCheckCt || board.getColor(r,c) != null) return 0; int c1 = c, c2 = c; while (c1 > 0 && blockCheck[r][c1-1] < blockCheckCt && board.getColor(r,c1-1) == null) c1--; while (c2 < cols-1 && blockCheck[r][c2+1] < blockCheckCt && board.getColor(r,c2+1) == null) c2++; for (int i = c1; i <= c2; i++) blockCheck[r][i] = blockCheckCt; int ct = c2 - c1 + 1; if (r > 0) for (int i = c1; i <= c2; i++) ct += countEmptyBlock(r-1,i); if (r < rows-1) for (int i = c1; i <= c2; i++) ct += countEmptyBlock(r+1,i); return ct; } void setUpRandomBoard() { // Set up a random board, that is, select at random the squares that will be left empty board.clear(); int x,y; int placed = 0; int choice = (int)(3*Math.random()); switch (choice) { case 0: // totally random for (int i=0; i < 4; i ++) { do { x = (int)(cols*Math.random()); y = (int)(rows*Math.random()); } while (board.getColor(y,x) != null); board.setColor(y,x,emptyColor); } break; case 1: // Symmetric random while (placed < 4) { x = (int)(cols*Math.random()); y = (int)(rows*Math.random()); if (board.getColor(y,x) == null) { board.setColor(y,x,emptyColor); placed++; } if (placed < 4 && board.getColor(y,cols-1-x) == null) { board.setColor(y,cols-1-x,emptyColor); placed++; } if (placed < 4 && board.getColor(rows-1-y,x) == null) { board.setColor(rows-1-y,x,emptyColor); placed++; } if (placed < 4 && board.getColor(rows-1-y,cols-1-x) == null) { board.setColor(rows-1-y,cols-1-x,emptyColor); placed++; } } break; default: // random block int blockrows = 2; int blockcols = 2; x = (int)((cols - blockcols+ 1)*Math.random()); y = (int)((rows - blockrows + 1)*Math.random()); for (int r = 0; r < blockrows; r++) for (int c = 0; c < blockcols; c++) { board.setColor(y+r,x+c,emptyColor); placed++; } break; } } synchronized void doDelay(int milliseconds) { // wait for specified time, or until a control message is sent using setMessage() // is generated. For an indefinite wait, milliseconds should be < 0 if (milliseconds < 0) { try { wait(); } catch (InterruptedException e) { } } else { try { wait(milliseconds); } catch (InterruptedException e) { } } } synchronized void setMessage(int message) { // send control message to game thread this.message = message; if (message > 0) notify(); // wake game thread if it is sleeping or waiting for a message (in the doDelay method) } /** * The run method for the thread that runs the game. */ public void run() { while (true) { try { running = false; board.repaint(); while (message != GO_MESSAGE && message != TERMINATE_MESSAGE) { // wait for game setup if (message == RESTART_RANDOM_MESSAGE) { setUpRandomBoard(); setMessage(GO_MESSAGE); doDelay(2000); } else { doDelay(-1); } } if (message == TERMINATE_MESSAGE) break; running = true; board.repaint(); doDelay(25); // begin next game message = 0; for (int i=1; i<=12; i++) used[i] = false; numused = 0; int startRow = 0; // reprsents the upper left corner of the board int startCol = 0; while (board.getColor(startRow,startCol) != null) { startCol++; // move past any filled squares, since Play(square) assumes the square is empty if (startCol == cols) { startCol = 0; startRow++; } } blockCheck = new int[rows][cols]; blockCheckCt = 0; squaresLeftEmpty = 0; aborted = false; if (!obviousBlockExists()) play(startRow,startCol); // run the recursive algorithm that will solve the puzzle if (message == TERMINATE_MESSAGE) break; running = false; board.repaint(); if (!aborted) { doDelay(-1); } if (message == TERMINATE_MESSAGE) break; } catch (Exception e) { System.out.println("An internal error has occurred:\n"+ e + "\nRESTARTING."); e.printStackTrace(); message = RESTART_RANDOM_MESSAGE; } } // end while } } // end nested class GameThread }