/* Puzzle.java */ /* Probably needed */ import java.applet.*; import java.awt.*; import java.util.Enumeration; import java.net.URL; /* extend req'd so graphics, etc., will be known */ class Piece extends java.applet.Applet { /* My dimensions, and how many are one each side of the puzzle */ int width,side; /* Sequential number in finished puzzle */ int khome; /* Am I the hidden piece? */ boolean hidden=false; /* Location in puzzle coordinates (k for linear)*/ int i,j,know; /* Location in pixel coordinates */ int x,y,xold,yold; /* The image */ Image image; boolean needpaint=true; boolean firsttime=true; Piece (int k, Image image) { khome = k; know = k; side = Puzzle.PUZZ_SIDE; width = Puzzle.PIECE_WIDTH; ktoAll(k); /* xold, yold define where the piece WAS, so we can blank that area when it moves */ xold=x; yold=y; this.image = image; } void hideimage(boolean hideme) { hidden = hideme; } void ktoAll(int k) { i = k % side; j = k / side; x = Puzzle.MARGIN + i * width; y = Puzzle.MARGIN + j * width; } /* Needs to be public, because we're overriding a public method */ public void paint (Graphics g) { if (!needpaint) return; if (firsttime) { /* Draw, rather than moving, don't erase anything. */ if (hidden) { g.setColor(Color.black); g.fillRect(x,y,width,width); } else { g.drawImage(image,x,y,this); } xold = x; yold = y; firsttime = false; needpaint = false; return; } /* If pieces are moving, hidden area will be erased by the other moves */ if (hidden) return; System.out.println("Copying " + khome + " to " + know); int dx = x - xold; int dy = y - yold; g.copyArea(xold,yold,width,width,dx,dy); /* blank any area uncovered - allow for partial moves! */ int x1,x2,xm,y1,y2,ym; xm = x + width-1; ym = y + width-1; x1 = xold; x2 = xold + width-1; y1 = yold; y2 = yold + width-1; if (x1 == x && y1 != y) { /* No change in x, blank existing x region, y changes */ if (y <= y1) { y1 = Math.max(y1,ym+1); } else if (y > y1) { y2 = Math.min(y2,y-1); } } else if(y1 == y && x1 != x) { /* No change in y, blank existing y region, x changes */ if (x <= x1) { x1 = Math.max(x1,xm+1); } else if (x > x1) { x2 = Math.min(x2,x-1); } } else if (y1 == y && x1 == x) { /* No actual move - don't erase anything */ x1 = x2 + 1; y1 = y2 + 1; } else { /* x and y changed */ System.out.println("Diagonal move - should not happen!"); } if (x1 <= x2 && y1 <= y2) { g.setColor(Color.black); g.fillRect(x1,y1,width,width); } xold = x; yold = y; needpaint = false; } /* So update, thus repaint, doesn't clear the background... */ public void update (Graphics g) { paint(g); } void move (int know, boolean dummy) { /* move version with dummy boolean tells the program this is the first time for this piece, i.e., draw, don't copy */ firsttime = true; this.know = know; ktoAll(know); needpaint = true; } void move (int know) { this.know = know; ktoAll(know); needpaint = true; } } /* The applet. */ public class Puzzle extends java.applet.Applet { /* Document location */ /* Puzzle pieces. */ static final int PUZZ_SIDE = 4; static final int PIECE_WIDTH = 80; private final int PUZZ_COUNT = PUZZ_SIDE * PUZZ_SIDE; /* Appearance preferences */ static final int MARGIN = 25; private final int TEXT_MARGIN = 30; private Color textBoxColor; /* Puzzle area. */ private final int PUZZ_WIDTH = PIECE_WIDTH * PUZZ_SIDE; private final int PUZZ_HEIGHT = PIECE_WIDTH * PUZZ_SIDE; /* Puzzle location within screen. */ private final int PUZZ_XOFF = MARGIN; private final int PUZZ_YOFF = MARGIN; private final int PUZZ_BOT = PUZZ_HEIGHT + MARGIN; /* Screen size. */ private final int SCREEN_WIDTH = PUZZ_WIDTH + MARGIN * 2; private final int SCREEN_HEIGHT = PUZZ_HEIGHT + MARGIN*3 + TEXT_MARGIN; /* Keys (vi convention) */ private int key_right = 'h'; private int key_left = 'l'; private int key_up = 'k'; private int key_down = 'j'; /* Fonts */ private Font font; private FontMetrics fontMetrics; /* Game parameters */ private int moves = 0; private int slides = 0; private int clicks = 0; private boolean won = false; private Piece piece[]; private int state[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, }; private static int hide = 15; private boolean redraw = true; /* The main applet's thread. */ private Thread thread = null; /* Strings */ private final String scoreString = "Moves: "; /* Initialize the applet. */ public void init () { textBoxColor = new Color(0,96,0); resize (SCREEN_WIDTH, SCREEN_HEIGHT); font = new Font ("TimesRoman", Font.BOLD, 14); fontMetrics = getFontMetrics (font); setFont (font); setBackground (Color.black); Image image; MediaTracker tracker; char joe; tracker = new MediaTracker(this); /* Make puzzle pieces. */ /* Note that new is used twice, once just below to size the array, and then in the loop to actually instantiate. */ piece = new Piece[PUZZ_COUNT]; for (int k = 0; k < PUZZ_COUNT; k++) { if (k < 10) { joe = (char)((int)'0' + k); } else { joe = (char)((int)'A' + k-10); } image = getImage (getDocumentBase(), "images/" + joe + ".gif"); tracker.addImage (image, 0); piece[k] = new Piece(k,image); if (k == hide) piece[k].hideimage(true); } System.out.println("Made all pieces"); /* Wait for the images to load. */ try { showStatus ("Loading images..."); tracker.waitForAll (); } catch (InterruptedException e) { /* return; */ } showStatus (""); showPieces(); repaint(); } /* The game engine. */ public void start () { mywait(); randomize(); /* reset due to moves made by randomizer */ moves = 0; slides = 0; clicks = 0; showPieces(); repaint(); } public void randomize () { /* Randomize - generate 40 random clicks */ /* Note that only (2N-1)/(N*N) of the clicks */ /* will do anything, if rotations are not implemented, */ /* where N is the number of pieces on each side */ int kclick; int istop = 40; for (int i = 0; i < istop; i++) { kclick = (int)(Math.random()*PUZZ_COUNT); moveClick(kclick); } System.out.println("Randomized."); } public void showPieces() { System.out.println("In showPieces."); /* Draw puzzle pieces */ for (int i = 0; i < PUZZ_COUNT; i++) { piece[state[i]].move(i,true); } } public void paint (Graphics g) { /* Clear just the text area */ g.setColor(textBoxColor); g.fillRect(MARGIN,PUZZ_BOT+MARGIN,PUZZ_WIDTH,TEXT_MARGIN); /* Write move count */ StringBuffer s; s = new StringBuffer("Single Moves: "); s.append(Integer.toString(moves)); s.append(" Slides: "); s.append(Integer.toString(slides)); s.append(" Active Clicks: "); s.append(Integer.toString(clicks)); g.setColor(Color.white); g.drawString(s.toString(),MARGIN+5,MARGIN+TEXT_MARGIN+PUZZ_BOT-10); /* Normally redraw the images, but not if redraw has been set false because this is a single move */ if (redraw || won) { showPieces(); } for (int i = 0; i < PUZZ_COUNT; i++) { piece[i].paint(g); } redraw = true; } /* Don't clear on updates, just paint. */ public void update (Graphics g) { paint(g); } void moveClick(int kclick) { /* Separate this from mouseDown, so we can call it for randomizing */ /* Case where click is in the blank image */ if (kclick < 0 || kclick > PUZZ_COUNT) { System.out.println("Bad k! Should never happen."); } if (state[kclick] == hide) { return; } /* Need to find the blank */ boolean found = false; int k=0; int iblank=0,jblank=0,kblank=0; while (!found) { if (state[k] == hide) { kblank = k; iblank = k % PUZZ_SIDE; jblank = k / PUZZ_SIDE; found = true; } if (k >= PUZZ_COUNT) { System.out.println("Error - did not find blank!"); } k++; } /* If game was won, but user continues, need to re-hide the blank piece */ if (won) { System.out.println("No longer a won game."); piece[hide].hideimage(true); won = false; } boolean first = true; int iclick = kclick % PUZZ_SIDE; int jclick = kclick / PUZZ_SIDE; int i,j; /* Case where click is in the same row as the blank */ if (jclick == jblank) { /* Move everything between by one, and replace the blank */ if (iclick > iblank) { for (i=iblank;iiclick;i=i-1) { int kold = i + jblank*PUZZ_SIDE - 1; int knew = i + jblank*PUZZ_SIDE; state[knew] = state[kold]; piece[state[knew]].move(knew); /* Don't really want a redraw, but the program seems to run i increasing, even though I wrote it with i decreasing. Can I trick it? */ redraw = true; repaint(10); first = false; } moves += iblank-iclick; } /* Move the blank */ state[kclick] = hide; piece[hide].move(kclick); repaint(10); slides++; clicks++; } /* Case where click is in the same column as the blank */ else if (iclick == iblank) { /* Move everything between by one, and replace the blank */ if (jclick > jblank) { for (j=jblank;jjclick;j--) { int knew = iblank + j *PUZZ_SIDE; int kold = iblank + (j-1)*PUZZ_SIDE; state[knew] = state[kold]; piece[state[knew]].move(knew); /* Don't really want a redraw, but the program seems to run j increasing, even though I wrote it with j decreasing. Can I trick it? */ redraw = true; repaint(10); first = false; } moves += jblank-jclick; } /* Move the blank */ state[kclick] = hide; piece[hide].move(kclick); repaint(10); slides++; clicks++; } else { /* Now we need rotations... */ } /* See if game is won */ won = true; for (i = 0; i < PUZZ_COUNT; i++) { if (state[i] != i) won = false; } if (won) piece[hide].hideimage(false); if (won) System.out.println("You won!"); /* Need a more flexible stop decision, but do this so Netscape will run it... */ if (won) { showPieces(); repaint(); /* Wait, then stop the applet. This doesn't actually seem to do anything, but Netscape won't run the code without some reference to stop... */ try { /* 5ms, since the sleep only slows down the redraw */ Thread.sleep (5); } catch (InterruptedException e) { System.out.println("Stopping"); stop(); } System.out.println("Stopping"); stop(); } } public boolean mouseDown(java.awt.Event evt, int x, int y) { int i,j; int iblank=0,jblank=0,kblank=0; /* x and y are available - interpret and move pix. */ /* Convert x,y to iclick,jclick for pictures. */ /* Warning - check this for off-picture clicks, etc */ int iclick = (x-PUZZ_XOFF) / (PUZZ_WIDTH / PUZZ_SIDE); int jclick = (y-PUZZ_YOFF) / (PUZZ_HEIGHT / PUZZ_SIDE); iclick = Math.min(iclick,PUZZ_SIDE-1); jclick = Math.min(jclick,PUZZ_SIDE-1); /* k is the linear index */ int kclick = iclick + (jclick * PUZZ_SIDE); /* Do the work of moving the pieces. */ moveClick(kclick); return true; } private void mywait() { try { Thread.sleep (2500); } catch (InterruptedException e) { } } }