// "LittlePentominosApplet"
//
// 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 solves this puzzle
// using a straightforward recursive backtracking procedure.
//
// This is a simple version of the applet that sets up and solves
// randomly chosen boards.  If there is a mouseclick on the applet,
// a new board is set up.  For a pentominos applet that gives more
// user control, see my Java page at http://math.hws.edu/xJava
//
//
// David Eck (eck@hws.edu)

import java.applet.Applet;
import java.awt.*;

public class LittlePentominosApplet extends Applet implements Runnable {
    
    int board[];          // The 8-by-8 board is actually represented
                          // conceptually as 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
                               
    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
                                // (while the main applet thread handles the user interface)
                                
    boolean done;  // used in play() to test whether the puzzle is solved (or aborted)
    
    boolean clicked;  // set to true when user clicks on the applet

    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 here tells which pentomino
	   { 5, 8,9,10,20 },       // that line 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 rows 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 void init() {
    
        board = new int[100];
        used = new boolean[13];
    
        setLayout(new BorderLayout());
        boardcanvas = new PentominosBoardCanvas(board);  // for displaying the board
        add("Center",boardcanvas);
        
    }
    
    public void start() {
       gameThread = new Thread(this);
       gameThread.start();
    }
    
    public void stop() {
       if (gameThread != null && gameThread.isAlive())
          gameThread.stop();
    }
    
    boolean putPiece(int p, int square) {  // try to place a piece on the board,
                                           // return true if it fits
        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.playPiece(pieces[p],square);  // color in the squares to represent the piece
        return true;
    }
    

    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 (!done && (used[pieces[p][0]] == false) && putPiece(p,square)) {  // try piece p
               used[pieces[p][0]] = true;
               numused++;
               if (getClicked()) {
                  done = true;
                  return;
               }
               else if (numused == 12) {  // puzzle is solved
                  doDelay(5000);
                  done = true;
                  return;
               }
               else {
                  doDelay(50);
                  int nextSquare = square;
                  while (board[nextSquare] != 0)  // find next empty square
                     nextSquare++;
                  play(nextSquare);  // and try to complete the solution
                  if (done)
                     return;
                  boardcanvas.removePiece(pieces[p],square);  // backtrack
                  numused--;
                  used[pieces[p][0]] = false;
               }
           }
    }
    
    void setUpRandomBoard() {
        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;
        int x,y;
        switch ((int)(5*Math.random())) {
           case 0:
              for (int i=0; i < 4; i ++) {
                 do {
                    x = 1 + (int)(8*Math.random());
                    y = 1 + (int)(8*Math.random());
                 } while (board[y*10+x] == -1);
                 board[y*10+x] = -1;
              }
           break;
           case 1:
           case 2:
              do {
                 x = 1 + (int)(8*Math.random());
                 y = 1 + (int)(8*Math.random());
              } while (y == 5 || x == 5);
              board[10*y+x] = -1;
              board[10*y+(9-x)] = -1;
              board[10*(9-y)+x] = -1;
              board[10*(9-y)+(9-x)] = -1;
           break;
           default:
              x = (int)(6*Math.random()) + 1;
              y = (int)((x)*Math.random()) + 1;
              board[y*10+x] = -1;
              board[y*10+x+1] = -1;
              board[y*10+x+10] = -1;
              board[y*10+x+11] = -1;
           break;
        }
        boardcanvas.repaint();
    }
    
    public void run() { 
       while (true) {
          do {
             setClicked(false);
             setUpRandomBoard();
             doDelay(1000);
          } while (getClicked());
          for (int i=1; i<=12; i++)
              used[i] = false;
          numused = 0;
          int square = 11;  // reprsents the upper left corner of the board
          while (board[square] == -1)
             square++;  // move past any filled squares, since Play(square) assumes the square is empty
          done = false;
          setClicked(false);
          play(square);
       }
    }
    
    synchronized void doDelay(int milliseconds) {
       try {
          wait(milliseconds);
       }
       catch (InterruptedException e) {
       }
    }
    
    synchronized void setClicked(boolean clicked) {
       this.clicked = clicked;
       if (clicked)
          notify();
    }
    
    synchronized boolean getClicked() {
       return this.clicked;
    }
    
    public boolean mouseDown(Event evt, int x, int y) {
       setClicked(true);
       return true;
    }
    
    
}   // 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

    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) { // Constructor
       board = theBoard;
       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);
    }
    
    // Note:  The following methods are synchronized because when the
    // board is resized, the main applet thread might try to call paint
    // while the puzzle-solving thread is trying to place squares on the
    // Board.  (I'm not sure this should really happen because the puzzle
    // thread is supposed to be running at a lower priority, but before
    // I added the synchronization, the screen would get corrupted when
    // I resized the board while the puzzle was in the process of being
    // solved.)

    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 playPiece(int[] pieceData, int startSquare) {
       Graphics g = getGraphics();
       putSquare(g,pieceData[0],startSquare);
       for (int i = 1; i < 5; i++)
          putSquare(g,pieceData[0],startSquare+pieceData[i]);
       g.dispose();
    }
    
    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 removePiece(int[] pieceData, int startSquare) {
       Graphics g = getGraphics();
       clearSquare(g,startSquare);
       for (int i = 1; i < 5; i++)
          clearSquare(g,startSquare+pieceData[i]);
       g.dispose();
    }

    synchronized void blackenSquare(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;
       Graphics g = getGraphics();
       g.fillRect(x, y, (w-1)/8, (h-1)/8);
       g.dispose();
       board[square] = -1;
    }
    
    public Dimension minimumSize() {
        return new Dimension(160,160);
    }

    public Dimension preferredSize() {
        return minimumSize();
    }
}

