/* * QuoVadis.java 1.1 97/07/25 */ /******************************************************************************/ /* Written: 17th July 1997 by Chris Heath */ /* Last modified: 25th July 1997 */ /* I give you permission to modify this as much as you like... */ /* ... so long as you keep my name in the credits! */ /* ... and that you email me (cheath@umich.edu) if you've done */ /* something worth looking at. */ /******************************************************************************/ // suggestions for future versions: // animation (how to implement pushing though?) // moving a piece around corners by moving mouse diagonally // better/more humorous happy face & message at the end // Version changes: // 1.1 Added black and white button, because there is no automatic way of detecting this. // Message changes to "Well Done" when puzzle is completed. import java.awt.*; import java.applet.*; /******************************************************************************/ /* CLASS GAMEBOARD */ /******************************************************************************/ class GameBoard extends Panel { static final short borderWidth=3; static final short pieceIsLeft=1; static final short pieceIsRight=2; static final short pieceIsTop=4; static final short pieceIsBottom=8; static final short emptySpace = 0; static final short piece1By1 = 16; static final short piece1By2Top = (short)(32+pieceIsTop); static final short piece1By2Bottom = (short)(32+pieceIsBottom); static final short piece2By1Left = (short)(48+pieceIsLeft); static final short piece2By1Right = (short)(48+pieceIsRight); static final short piece2By2TopLeft = (short)(64+pieceIsTop+pieceIsLeft); static final short piece2By2TopRight = (short)(64+pieceIsTop+pieceIsRight); static final short piece2By2BottomLeft = (short)(64+pieceIsBottom+pieceIsLeft); static final short piece2By2BottomRight = (short)(64+pieceIsBottom+pieceIsRight); QuoVadis theApp; short board[/*5*/][/*4*/]; boolean gameFinished, blackAndWhite = false, pieceMoved; int clickCount; int dragStartX, dragStartY; int dragPieceCol, dragPieceRow; public GameBoard (QuoVadis theApp) { this.theApp = theApp; board = new short[5][4]; clearGame(); } /*--------------------------------------------------------------class GameBoard---*/ public void clearGame () { board[0][0] = piece1By2Top; board[0][1] = piece2By2TopLeft; board[0][2] = piece2By2TopRight; board[0][3] = piece1By2Top; board[1][0] = piece1By2Bottom; board[1][1] = piece2By2BottomLeft; board[1][2] = piece2By2BottomRight; board[1][3] = piece1By2Bottom; board[2][0] = piece1By2Top; board[2][1] = piece2By1Left; board[2][2] = piece2By1Right; board[2][3] = piece1By2Top; board[3][0] = piece1By2Bottom; board[3][1] = piece1By1; board[3][2] = piece1By1; board[3][3] = piece1By2Bottom; board[4][0] = emptySpace; board[4][1] = piece1By1; board[4][2] = piece1By1; board[4][3] = emptySpace; gameFinished = false; clickCount = 0; } /*-------------------------------------------------------------class GameBoard---*/ private Rectangle getBoardRect () { Rectangle result = new Rectangle(); Dimension d = size(); int squareSize = Math.min((d.width-borderWidth*2-2)/4, (d.height-borderWidth*2-3)/5); result.width = squareSize * 4; result.height = squareSize * 5; result.x = (d.width - result.width) / 2; result.y = (d.height - result.height) / 2; return result; } /*-------------------------------------------------------------class GameBoard---*/ private void paintPiece (Graphics g, int x, int y, int shiftPixelsX, int shiftPixelsY, short pieceType) { Rectangle br = getBoardRect(); int width = br.width/4; int height = br.height/5; switch (pieceType) { case emptySpace: g.setColor(Color.white); break; case piece1By1: g.setColor(Color.black); break; case piece2By1Left: g.setColor(Color.red); width *= 2; break; case piece1By2Top: g.setColor(Color.green); height *= 2; break; case piece2By2TopLeft: g.setColor(Color.yellow); width *= 2; height *= 2; break; default: return; } /* draw a rectangle - this way it's less browser-dependent on a Macintosh */ if (pieceType != emptySpace) { if (blackAndWhite) g.setColor(Color.black); g.fillRect(x+1, y+1, width-3, height-3); g.drawLine(x+1, y+height-2, x+width-2, y+height-2); g.drawLine(x+width-2, y+1, x+width-2, y+height-2); } g.setColor(Color.white); if (shiftPixelsX > 0) g.fillRect(x+1-shiftPixelsX, y+1-shiftPixelsY, shiftPixelsX, height-2); if (shiftPixelsX < 0) g.fillRect(x+width-1, y+1-shiftPixelsY, -shiftPixelsX, height-2); if (shiftPixelsY > 0) g.fillRect(x+1-shiftPixelsX, y+1-shiftPixelsY, width-2, shiftPixelsY); if (shiftPixelsY < 0) g.fillRect(x+1-shiftPixelsX, y+height-1, width-2, -shiftPixelsY); if (gameFinished && pieceType == piece2By2TopLeft) { /* draw the happy face */ int thickness = Math.max(1, width/20); if (blackAndWhite) g.setColor(Color.white); else g.setColor(Color.black); for (int i=0; i= 5 || dragPieceCol >= 4 || dragPieceRow < 0 || dragPieceCol < 0 || gameFinished) return true; short pieceType = board[dragPieceRow][dragPieceCol]; if (pieceType == emptySpace) return true; int pieceWidth = br.width/4; int pieceHeight = br.height/5; int moveThreshX = br.width/8 + 1; int moveThreshY = br.height/10 + 1; moveLeft = dragStartX - x >= moveThreshX; moveRight = x - dragStartX >= moveThreshX; moveUp = dragStartY - y >= moveThreshY; moveDown = y - dragStartY >= moveThreshY; if ((moveLeft?1:0) + (moveRight?1:0) + (moveUp?1:0) + (moveDown?1:0) != 1) return true; if (moveLeft && movePieceLeft(dragPieceCol, dragPieceRow, false)) { if ((pieceType & pieceIsLeft) != 0) dragPieceCol++; movePieceLeft(dragPieceCol, dragPieceRow, true); dragStartX -= pieceWidth; dragPieceCol--; notifyPieceMoved(); } if (moveRight && movePieceRight(dragPieceCol, dragPieceRow, false)) { if ((pieceType & pieceIsRight) != 0) dragPieceCol--; movePieceRight(dragPieceCol, dragPieceRow, true); dragStartX += pieceWidth; dragPieceCol++; notifyPieceMoved(); } if (moveUp && movePieceUp(dragPieceCol, dragPieceRow, false)) { if ((pieceType & pieceIsTop) != 0) dragPieceRow++; movePieceUp(dragPieceCol, dragPieceRow, true); dragStartY -= pieceHeight; dragPieceRow--; notifyPieceMoved(); } if (moveDown && movePieceDown(dragPieceCol, dragPieceRow, false)) { if ((pieceType & pieceIsBottom) != 0) dragPieceRow--; movePieceDown(dragPieceCol, dragPieceRow, true); dragStartY += pieceHeight; dragPieceRow++; notifyPieceMoved(); } if (board[3][1] == piece2By2TopLeft) { gameFinished = true; paintPiece(g, br.x+pieceWidth, br.y+pieceHeight*3, 0, 0, piece2By2TopLeft); theApp.updateMessage(); } return true; } /*-------------------------------------------------------------class GameBoard---*/ public void notifyPieceMoved () { if (!pieceMoved) { pieceMoved = true; clickCount++; theApp.updateClicksLabel(); } } /*-------------------------------------------------------------class GameBoard---*/ private boolean movePieceLeft (int col, int row, boolean doIt) { short pieceType = board[row][col]; if (pieceType == emptySpace) return true; if (col == 0) return false; if ((pieceType & pieceIsBottom) != 0) return movePieceLeft(col, row-1, doIt); /* always move by topLeft corner */ if (!movePieceLeft(col-1, row, doIt)) /*recurse to see if pieces can be pushed */ return false; if ((pieceType & pieceIsTop) != 0) { if (!movePieceLeft(col-1, row+1, doIt)) return false; if (doIt) { board[row+1][col-1] = board[row+1][col]; board[row+1][col] = emptySpace; } } if (doIt) { Rectangle br = getBoardRect(); Graphics g = getGraphics(); board[row][col-1] = pieceType; board[row][col] = emptySpace; paintPiece(g, br.x+br.width*(col-1)/4, br.y+br.height*row/5, -br.width/4, 0, pieceType); } return true; } /*-------------------------------------------------------------class GameBoard---*/ private boolean movePieceRight (int col, int row, boolean doIt) { short pieceType = board[row][col]; if (pieceType == emptySpace) return true; if (col == 3) return false; if ((pieceType & pieceIsBottom) != 0) return movePieceRight(col, row-1, doIt); /* always move by topLeft corner */ if (!movePieceRight(col+1, row, doIt)) /*recurse to see if pieces can be pushed */ return false; if ((pieceType & pieceIsTop) != 0) { if (!movePieceRight(col+1, row+1, doIt)) return false; if (doIt) { board[row+1][col+1] = board[row+1][col]; board[row+1][col] = emptySpace; } } if (doIt) { Rectangle br = getBoardRect(); Graphics g = getGraphics(); board[row][col+1] = pieceType; board[row][col] = emptySpace; paintPiece(g, br.x+br.width*(col+1)/4, br.y+br.height*row/5, br.width/4, 0, pieceType); } return true; } /*-------------------------------------------------------------class GameBoard---*/ private boolean movePieceUp (int col, int row, boolean doIt) { short pieceType = board[row][col]; if (pieceType == emptySpace) return true; if (row == 0) return false; if ((pieceType & pieceIsRight) != 0) return movePieceUp(col-1, row, doIt); /* always move by topLeft corner */ if (!movePieceUp(col, row-1, doIt)) /*recurse to see if pieces can be pushed */ return false; if ((pieceType & pieceIsLeft) != 0) { if (!movePieceUp(col+1, row-1, doIt)) return false; if (doIt) { board[row-1][col+1] = board[row][col+1]; board[row][col+1] = emptySpace; } } if (doIt) { Rectangle br = getBoardRect(); Graphics g = getGraphics(); board[row-1][col] = pieceType; board[row][col] = emptySpace; paintPiece(g, br.x+br.width*col/4, br.y+br.height*(row-1)/5, 0, -br.height/5, pieceType); } return true; } /*-------------------------------------------------------------class GameBoard---*/ private boolean movePieceDown (int col, int row, boolean doIt) { short pieceType = board[row][col]; if (pieceType == emptySpace) return true; if (row == 4) return false; if ((pieceType & pieceIsRight) != 0) return movePieceDown(col-1, row, doIt); /* always move by topLeft corner */ if (!movePieceDown(col, row+1, doIt)) /*recurse to see if pieces can be pushed */ return false; if ((pieceType & pieceIsLeft) != 0) { if (!movePieceDown(col+1, row+1, doIt)) return false; if (doIt) { board[row+1][col+1] = board[row][col+1]; board[row][col+1] = emptySpace; } } if (doIt) { Rectangle br = getBoardRect(); Graphics g = getGraphics(); board[row+1][col] = pieceType; board[row][col] = emptySpace; paintPiece(g, br.x+br.width*col/4, br.y+br.height*(row+1)/5, 0, br.height/5, pieceType); } return true; } }; /******************************************************************************/ /* CLASS QUOVADIS */ /* */ /* This is the main applet class - it sets up the board, handles the */ /* button I/O, and that's about it! */ /******************************************************************************/ public class QuoVadis extends Applet { GameBoard gameBoard; Button startButton; Label message1, message2, message3, message4, clicksLabel; /*---------------------------------------------------------------class QuoVadis---*/ /** * Initialize the applet. (load images & sounds?) */ public void init () { setLayout(new BorderLayout(/*hGap*/10, 0)); add("Center", gameBoard = new GameBoard(this)); startButton = new Button("Restart puzzle"); Checkbox BWButton = new Checkbox("Black & White"); /* create instruction panel */ Panel instructionPanel = new Panel(); instructionPanel.setLayout(new GridLayout(0,1,0,0));/* one column, no space between components */ instructionPanel.add(message1 = new Label()); instructionPanel.add(message2 = new Label()); instructionPanel.add(message3 = new Label()); instructionPanel.add(message4 = new Label()); instructionPanel.add(clicksLabel = new Label()); /* put these three in a panel, one below the other */ Panel rightHandPanel = new Panel(); rightHandPanel.setLayout(new BorderLayout(0, /*vGap*/10)); rightHandPanel.add("North", BWButton); rightHandPanel.add("Center", alignPanel(startButton, GridBagConstraints.WEST)); rightHandPanel.add("South", instructionPanel); add("East", alignPanel(rightHandPanel, GridBagConstraints.CENTER)); updateMessage(); updateClicksLabel(); } /*---------------------------------------------------------------class QuoVadis---*/ private Panel alignPanel (Component thePanel, int alignment) { /* if the enclosing rectangle is bigger than thePanel's preferredRect, then it will be shifted (not resized!) so it is aligned inside the enclosing rectangle according to the parameter alignment */ /* alignment should be one of GridBagConstraints.{CENTER, NORTH, NORTHEAST,...} */ Panel newPanel = new Panel(); GridBagLayout layout = new GridBagLayout(); GridBagConstraints constraints = new GridBagConstraints(); newPanel.setLayout(layout); constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.fill = GridBagConstraints.NONE; /* don't resize; will center instead */ constraints.anchor = alignment; constraints.weightx = constraints.weighty = 1; layout.setConstraints(thePanel, constraints); newPanel.add(thePanel); return newPanel; } /*---------------------------------------------------------------class QuoVadis---*/ public void updateMessage () { if (gameBoard.gameFinished) { message1.setText("Well done!"); if (gameBoard.clickCount < 70) { message2.setText("Your're a"); message3.setText("Quo Vadis pro!"); message4.setText(""); } else { message2.setText("Now find a solution"); message3.setText("that uses less than"); message4.setText("70 mouse-clicks!"); } } else { message1.setText("Try to get the"); if (gameBoard.blackAndWhite) message2.setText("largest square"); else message2.setText("large yellow square"); message3.setText("down to the hole"); message4.setText("at the bottom!"); } } /*---------------------------------------------------------------class QuoVadis---*/ public void updateClicksLabel () { clicksLabel.setText("Clicks: ".concat(Integer.toString(gameBoard.clickCount))); } /*---------------------------------------------------------------class QuoVadis---*/ public boolean action (Event e, Object arg) { if (e.target instanceof Button) { gameBoard.clearGame(); updateClicksLabel(); } else if (e.target instanceof Checkbox) gameBoard.blackAndWhite = !gameBoard.blackAndWhite; else return false; gameBoard.repaint(); updateMessage(); return true; } /*---------------------------------------------------------------class QuoVadis---*/ public String getAppletInfo () { return "QuoVadis version 1.1 by Chris Heath"; } }