import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.applet.*;

public class puzzle extends Applet implements ActionListener, Runnable {
  Thread engine = null;
  Image baseImage;
  piece pieces[][];
  MediaTracker tracker;
  int cols = 4, rows = 4;     // number of columns and rows
  int totalh, totalw;         // dimensions of image
  int h, w;                   // dimensions of each piece
  int scramble;               // number of scramble moves
  int moveCounter = 0;        // number of moves so far
  Button rstrt = new Button("Try again"); // the restart button
  boolean donep = false;
  boolean full_repaint = true;
  Image offImage = null;
  Graphics offGraphics = null;

public void init() {
  setLayout(new BorderLayout());
  rstrt.addActionListener(this);
  tracker = new MediaTracker(this);
  Dimension d = this.size();
  offImage = createImage(d.width,d.height);
  offGraphics = offImage.getGraphics();
  // get params with defaults
  String s = getParameter("img");
  if (s == null) 
    s = "waterbill.jpg";
  baseImage = getImage(getCodeBase(), s);
  tracker.addImage(baseImage,0);
  s = getParameter("rows");
  if (s != null)
    rows = Integer.valueOf(s).intValue();
  s = getParameter("cols");
  if (s != null)
    cols = Integer.valueOf(s).intValue();
  s = getParameter("scramble");
  if (s != null)
    scramble = Integer.valueOf(s).intValue();
  else
    scramble = 2*cols*rows;
  this.init_board();
  moveCounter = 0;
  Panel pnl = new Panel();
  pnl.add(rstrt);
  add("South", pnl);
}

public void init_board() {
  // wait for baseImage to fully load
  try {
    tracker.waitForID(0); } 
  catch (InterruptedException e) {
    return; }
  totalh = baseImage.getHeight(this);
  totalw = baseImage.getWidth(this);
  pieces = new piece[cols][rows];
  // return if baseImage not available yet
  totalh = totalh - (totalh % rows);
  totalw = totalw - (totalw % cols);  
  h = totalh / rows;
  w = totalw / cols;
  // construct pieces
  ImageFilter cropfilter;
  ImageProducer prod;
  Image new_img;
  for (int r = 0; r < rows; r++)
    for (int c = 0; c < cols; c++) {
      cropfilter = new CropImageFilter(c*w, r*h, w, h);
      prod = new FilteredImageSource(baseImage.getSource(),cropfilter);
      new_img = createImage(prod);
      tracker.addImage(new_img,1);
      pieces[c][r] = new piece(new_img,c,r);
    }
  // make empty piece
  int b_col = cols-1;
  int b_row = 0;
  pieces[b_col][b_row] = null;
  this.scrambler();

  // make sure pieces are loaded
  try {
    tracker.waitForID(1); } 
  catch (InterruptedException e) {
    return; }
  donep = false;
  full_repaint = true;
}

public void scrambler() {
  int b_row = 0;
  int b_col = cols-1;
  int poss[][] = new int[cols][rows];
  int c;
  int b_row2 = b_row, b_col2 = b_col;  // avoid repeats
  for(int i = 0; i < scramble; i++) {
    c = 0;
    if ((b_col+1 < cols) && !((b_col+1 == b_col2) && (b_row == b_row2))) {
      poss[c][0] = b_col+1;
      poss[c][1] = b_row;
      c++; }
    if ((b_col-1 >= 0) && !((b_col-1 == b_col2) && (b_row == b_row2))) {
      poss[c][0] = b_col-1;
      poss[c][1] = b_row;
      c++; }
    if ((b_row+1 < rows) && !((b_col == b_col2) && (b_row+1 == b_row2))) {
      poss[c][0] = b_col;
      poss[c][1] = b_row+1;
      c++; }  
    if ((b_row-1 >= 0) && !((b_col == b_col2) && (b_row-1 == b_row2))) {
      poss[c][0] = b_col;
      poss[c][1] = b_row-1;
      c++; }
    c = (int)Math.round((c-1)*Math.random());
    pieces[b_col][b_row] = pieces[poss[c][0]][poss[c][1]];
    b_col2 = b_col;
    b_row2 = b_row;
    b_col = poss[c][0];
    b_row = poss[c][1];
    pieces[b_col][b_row] = null;
  }
  donep = false;
  full_repaint = true;
}

public void paint(Graphics g) {
  full_repaint = false;
  if (baseImage == null) 
    return;
  if (donep) {
    g.drawImage(baseImage,0,0,this);
    return;
  }
  // draw pieces
  for (int r = 0; r < rows; r++)
    for (int c = 0; c < cols; c++)
      if (pieces[c][r] == null) {
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(c*w, r*h, w, h); }
      else
	offGraphics.drawImage(pieces[c][r].pict,c*w,r*h,this);
  // draw grid
  for(int i = 0; i <= cols; i++)
    offGraphics.drawLine(i*w,0,i*w,totalh);
  for(int i = 0; i <= rows; i++)
    offGraphics.drawLine(0,i*h,totalw,i*h);
  g.drawImage(offImage,0,0,this);

  String moveDisplay = "Moves: " + moveCounter;
  g.drawString(moveDisplay, (totalw / 2) - 20, totalh + 15); 
}

public void update(Graphics g) {
  paint(g);
}

public boolean mouseDown(java.awt.Event evt, int x, int y) {
  int c = x / w;
  int r = y / h;
  if ((c > cols-1) || (r > rows-1))
    return true;
  if (donep) {
    moveCounter = 0;
    this.init_board();
    full_repaint = true;
    return true;
  }
  // check for move
  boolean movep = false;
  if (pieces[c][r] == null) return true;
  if ((c < cols-1) && (pieces[c+1][r] == null)) {
    pieces[c+1][r] = pieces[c][r];
    movep = true; }
  if ((c > 0) && (pieces[c-1][r] == null)) {
    pieces[c-1][r] = pieces[c][r];
    movep = true; }
  if ((r < rows-1) && (pieces[c][r+1] == null)) {
    pieces[c][r+1] = pieces[c][r];
    movep = true; }  
  if ((r > 0) && (pieces[c][r-1] == null)) {
    pieces[c][r-1] = pieces[c][r];
    movep = true; }
  if (movep) {
    pieces[c][r] = null;
    full_repaint = true;
    moveCounter++;
    // check for done
    donep = true;
    piece p;
    for (r = 0; r < rows; r++)
      for (c = 0; c < cols; c++) {
	p = pieces[c][r];
	if ((p != null) && ((p.col != c) || (p.row != r)))
	  donep = false;
      }
  }
  return true;
}
  
public void start() {
  if (engine == null) {
    engine = new Thread(this);
    engine.start(); }
}

public void stop() {
  if (engine != null && engine.isAlive()) {
    engine.stop(); }
  engine = null;
}

public void run() {
  Thread me = Thread.currentThread();
  while (engine == me) {
    try {Thread.currentThread().sleep(100);} catch (InterruptedException e){}
    if (full_repaint) {
      repaint(); }
  }
}
  
public void actionPerformed(ActionEvent e) {
  if("Try again".equals(e.getActionCommand())) {
    donep = false;
    moveCounter = 0;
    this.init_board();
    full_repaint = true;
  }
}

}

class piece {
  public Image pict;
  public int col,row;
  
  piece(Image p, int c, int r) {
    pict = p;
    col = c;
    row = r;
  }
}