/* * Maze * Copyright (C) 2000 Paul Davis, pdavis@lpccomp.bc.ca * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ import java.awt.*; import java.awt.event.*; /** * Display a 3D maze using a 2D projection as an AWT component. * Line drawings are used to represent walls and corners. * The orientation of the view is variable: the player can stand on the * walls or ceiling. * The player can click on the left or right side to pivot left or right, * near the top or bottom to pivot backward or forward or near the * center to move forward. * The following keystrokes are also accepted: * <DL> * <LI> SPACE - move forward * <LI> L - pivot left * <LI> R - pivot right * <LI> B - roll back * <LI> F - roll forward * <LI> G - roll right * <LI> H - roll left * <LI> S - spin around * <LI> U - slide up * <LI> D - slide down * <LI> I - forward until a choice * <LI> W - forward until a wall * <LI> Z - follow until a choice * <LI> ? - show 2D maze * </DL> */ public class Maze3D extends Component { /** * The maze model. * @see Maze */ private MazeModel model; private Coordinate3D size,current; private byte front,right,up; private boolean showMarks; private int myWidth,myHeight; private Frame mapFrame; /** * Create a 3D maze display. Defaults to a size of 300x300. * @param model The MazeModel to use for the maze contents. */ public Maze3D(MazeModel model) { this(model,300,300); } /** * Create a 3D maze display. * @param model The MazeModel to use for the maze contents. * @param width The display width of the Component. * @param height The display width of the Component. */ public Maze3D(MazeModel model, int width, int height) { this.model = model; myWidth = width; myHeight = height; size = model.getSize(); current = new Coordinate3D(0,size.y-1,0); model.setCurrent(current); front = MazeModel.YMI; right = MazeModel.XPL; up = MazeModel.ZPL; showMarks = false; setSize(width,height); addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { //System.out.println("Mouse at " + // String.valueOf(e.getX()) + // "," + String.valueOf(e.getY())); handleClick(e.getX(),e.getY()); } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } } ); addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { //System.out.println("key pressed"); } public void keyReleased(KeyEvent e) { handleKeyCode(e.getKeyCode()); //System.out.println("key released"); } public void keyTyped(KeyEvent e) { //System.out.println("key typed"); } } ); } /** * From Component, this component can get keyboard input. */ public boolean isFocusTraversable() { return true; } /** * Handle a mouse click. * @param x The component x co-ordinate of the click. * @param y The component y co-ordinate of the click. */ private void handleClick(int x,int y) { Dimension s = getSize(); if ( x < s.width/3 ) { // Left turnLeft(); repaint(); } else if ( x > s.width*2/3 ) { // Right turnRight(); repaint(); } else if ( y < s.height/3 ) { // Up if ( goUp() ) repaint(); } else if ( y > s.height*2/3 ) { // Down if ( goDown() ) repaint(); } else { // Forward if ( goForward() ) repaint(); } //System.out.println("Now at " + String.valueOf(current.x) + // "," + String.valueOf(current.y) + // "," + String.valueOf(current.z) + // " " + Maze.directionString(front)); } /** * Handle a keyboard key. * @param keyCode The keycode from the keyboard. */ private void handleKeyCode(int keyCode) { //System.out.println("key code " + String.valueOf(keyCode)); switch(keyCode) { case KeyEvent.VK_SPACE: // SPACE - move forward if( goForward() ) repaint(); break; case KeyEvent.VK_L: // L - pivot left turnLeft(); repaint(); break; case KeyEvent.VK_R: // R - pivot right turnRight(); repaint(); break; case KeyEvent.VK_B: // B - roll back rollBack(); repaint(); break; case KeyEvent.VK_F: // F - roll forward rollForward(); repaint(); break; case KeyEvent.VK_G: // G - roll right rollRight(); repaint(); break; case KeyEvent.VK_H: // H - roll left rollLeft(); repaint(); break; case KeyEvent.VK_S: // S - spin around front = Maze.invert(front); right = Maze.invert(right); repaint(); break; case KeyEvent.VK_U: // U - slide up if ( goUp() ) repaint(); break; case KeyEvent.VK_D: // D - slide down if ( goDown() ) repaint(); break; case KeyEvent.VK_I: // I - forward until a choice if ( ! goForward() ) break; while ( !isOpen(right) && !isOpen(Maze.invert(right)) && !isOpen(up) && !isOpen(Maze.invert(up)) && isOpen(front) ) Maze.forward(current,front); model.setCurrent(current); repaint(); break; case KeyEvent.VK_W: // W - forward until a wall while ( isOpen(front) ) Maze.forward(current,front); model.setCurrent(current); repaint(); break; case KeyEvent.VK_Z: // Z - follow until a choice if ( ! goForward() ) break; while ( Maze.count(model.grid(current)) == 2 ) { if ( isOpen(front) ) Maze.forward(current,front); else if ( isOpen(right) ) { turnRight(); Maze.forward(current,front); } else if ( isOpen(Maze.invert(right)) ) { turnLeft(); Maze.forward(current,front); } else if ( isOpen(up) ) { rollBack(); Maze.forward(current,front); } else if ( isOpen(Maze.invert(up)) ) { rollBack(); Maze.forward(current,front); } } model.setCurrent(current); repaint(); break; case KeyEvent.VK_SLASH: // ? - show 2D maze mapFrame = new Frame(); Dialog d = new Dialog(mapFrame,"Map"); int w = 600 / size.x; if ( w > 20 ) w = 20; int h = 450 / size.y; if ( h > 20 ) h = 20; if ( w>h ) d.add(new Maze2D(model,w,15,15)); else d.add(new Maze2D(model,h,15,15)); d.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { //System.out.println("close: " + // e.getSource().toString()); Window win = (Window)e.getSource(); win.dispose(); } }); d.pack(); d.show(); break; } } /** * From Component.getPreferredSize. */ public Dimension getPreferredSize() { return new Dimension(myWidth,myHeight); } /* public Dimension getSize() { return new Dimension(myWidth,myHeight); } */ /* public void setPosition(Coordinate3D current) { this.current = current; } */ /** * Set the orientation of the view. * The parameters determine which direction is viewed and * which "floor" or "wall" the player is standing on. * @param front The dimension to the front of the player: XPL etc. * @param right The dimension to the right of the player. * @param up The dimension above the player. */ public void setOrientation(byte front, byte right, byte up) { this.front = front; this.right = right; this.up = up; } /* public Coordinate3D getCurrent() { return current; } */ /** * Change the view orientation to pivot to the left. */ private void turnLeft() { byte temp = front; front = Maze.invert(right); right = temp; } /** * Change the view orientation to pivot to the right. */ private void turnRight() { byte temp = front; front = right; right = Maze.invert(temp); } /** * Change the view orientation to pivot backwards. */ private void rollBack() { byte temp = front; front = up; up = Maze.invert(temp); } /** * Change the view orientation to pivot frontwards. */ private void rollForward() { byte temp = front; front = Maze.invert(up); up = temp; } /** * Change the view orientation to roll onto your right side. */ private void rollRight() { byte temp = up; up = right; right = Maze.invert(temp); } /** * Change the view orientation to roll onto your left side. */ private void rollLeft() { byte temp = up; up = Maze.invert(right); right = temp; } /** * Move forward in the maze. Keep the same orientation. * @return True if movement possible, false if blocked by a wall. */ private boolean goForward() { if ( isOpen(model.grid(current),front) ) { Maze.forward(current,front); model.setCurrent(current); return true; } return false; } /** * Move backwards in maze. Keep the same orientation. * @return True if movement possible, false if blocked by a wall. */ private boolean goBackward() { if ( isOpen(model.grid(current),Maze.invert(front)) ) { Maze.forward(current,Maze.invert(front)); model.setCurrent(current); return true; } return false; } /** * Move up a level in the maze. Keep the same orientation. * @return True if movement possible, false if blocked by a wall. */ private boolean goUp() { if ( isOpen(model.grid(current),up) ) { Maze.forward(current,up); model.setCurrent(current); return true; } return false; } /** * Move down a level in the maze. Keep the same orientation. * @return True if movement possible, false if blocked by a wall. */ private boolean goDown() { if ( isOpen(model.grid(current),Maze.invert(up)) ) { Maze.forward(current,Maze.invert(up)); model.setCurrent(current); return true; } return false; } /** * Check if the given direction is open from the given cell value. * @param value The cell value to use. * @param direction The direction: XPL etc. * @return True if there is no wall in that direction. */ private boolean isOpen(byte value,byte direction) { return (value & direction) != 0; } /** * Check if the given direction is open from the current cell. * @param direction The direction: XPL etc. * @return True if there is no wall in that direction. */ private boolean isOpen(byte direction) { return isOpen(model.grid(current),direction); } /** * From Component, paint the 2D projection of the maze based * on the current position and view orientation. */ public void paint(Graphics g) { Dimension s = getSize(); //System.out.println("Maze3D size: " + s.toString()); int x,y; g.setColor(Color.white); g.fillRect(0,0,s.width,s.height); int depth=0; Coordinate3D cr = new Coordinate3D(current.x,current.y,current.z); g.setColor(Color.black); while (true) { drawLayer(g,cr,depth++); if ( !isOpen(model.grid(cr),front) || depth>50 ) break; Maze.forward(cr,front); } requestFocus(); } /** * Draw one layer of the projection. * Each layer is one cell further away from the current view point. * @param g The Graphics context to use. * @param cr The position of the cell to draw. * @param depth The number of cells away from the viewpoint. */ private void drawLayer(Graphics g, Coordinate3D cr, int depth) { Dimension s = getSize(); byte value; int x1,x2,y1,y2,x15,y15; byte left,down; int limx=s.width-1,limy=s.height-1; double factor = 0.7; double fdepth = (double)depth-factor; double denom1=(fdepth+1.71)*2; double denom2=(fdepth+2.71)*2; /* if ( depth>3 ) return; */ left = Maze.invert(right); down = Maze.invert(up); value = model.grid(cr); x1 = (int)Math.floor(fdepth*s.width/denom1+0.5); x2 = (int)Math.floor((fdepth+1)*s.width/denom2+0.5); y1 = (int)Math.floor(fdepth*s.height/denom1+0.5); y2 = (int)Math.floor((fdepth+1)*s.height/denom2+0.5); x15 = (x1+x2)/2; y15 = (y1+y2)/2; //fprintf(fp,"depth: %d at %d,%d,%d : %d,%d %d,%d\n", // depth,cr.x,cr.y,cr.z,y1,x1,y2,x2); /* xpos1 = ax[depth]; xpos2 = ax[depth+1]; ypos1 = ay[depth]; ypos2 = ay[depth+1]; len0 = 0; len1 = len[depth]; len2 = len[depth+1]; len3 = (len1-len2)/2; */ if ( isOpen(value,up) ) { g.drawLine(x1,y1,limx-x1,y1); g.drawLine(x2,y1,x2,y2); g.drawLine(limx-x2,y1,limx-x2,y2); } if ( isOpen(value,right) ) { g.drawLine(limx-x1,y1,limx-x1,limy-y1); g.drawLine(limx-x1,limy-y2,limx-x2,limy-y2); g.drawLine(limx-x1,y2,limx-x2,y2); } if ( isOpen(value,down) ) { g.drawLine(x1,limy-y1,limx-x1,limy-y1); g.drawLine(x2,limy-y1,x2,limy-y2); g.drawLine(limx-x2,limy-y1,limx-x2,limy-y2); } if ( isOpen(value,left) ) { g.drawLine(x1,y1,x1,limy-y1); g.drawLine(x1,limy-y2,x2,limy-y2); g.drawLine(x1,y2,x2,y2); } if ( !( isOpen(value,up) ^ isOpen(value,front)) ) g.drawLine(x2,y2,limx-x2,y2); if ( !( isOpen(value,right) ^ isOpen(value,front)) ) g.drawLine(limx-x2,y2,limx-x2,limy-y2); if ( !( isOpen(value,down) ^ isOpen(value,front)) ) g.drawLine(x2,limy-y2,limx-x2,limy-y2); if ( !( isOpen(value,left) ^ isOpen(value,front)) ) g.drawLine(x2,y2,x2,limy-y2); if ( !( isOpen(value,up) ^ isOpen(value,left))) g.drawLine(x1,y1,x2,y2); if ( !( isOpen(value,up) ^ isOpen(value,right))) g.drawLine(limx-x1,y1,limx-x2,y2); if ( !( isOpen(value,right) ^ isOpen(value,down))) g.drawLine(limx-x1,limy-y1,limx-x2,limy-y2); if ( !( isOpen(value,down) ^ isOpen(value,left))) g.drawLine(x1,limy-y1,x2,limy-y2); /* Floor markers if ( up==ZMI && !(value&up)) g.drawLine(limx/2,y15,limx/2,y15); if ( right==ZMI && !(value&right)) g.drawLine(limx-x15,limy/2,limx-x15,limy/2); if ( down==ZMI && !(value&down)) g.drawLine(limx/2,limy-y15,limx/2,limy-y15); if ( left==ZMI && !(value&left)) g.drawLine(x15,limy/2,x15,limy/2); if ( front==ZMI && !(value&front)) g.drawLine(limx/2,limy/2,limx/2,limy/2); */ Coordinate3D finish = model.getFinish(); if ( finish!=null && cr.x==finish.x && cr.y==finish.y && cr.z==finish.z ) chest(g,x1,y1,x2,y2); if ( depth>0 && model.isMarked(cr) ) { if ( up==MazeModel.ZMI ) block(g,limx/2,y15,limx/2,y15,x2-x1); if ( right==MazeModel.ZMI ) block(g,limx-x15,limy/2,limx-x15,limy/2,x2-x1); if ( down==MazeModel.ZMI ) block(g,limx/2,limy-y15,limx/2,limy-y15,x2-x1); if ( left==MazeModel.ZMI ) block(g,x15,limy/2,x15,limy/2,x2-x1); if ( front==MazeModel.ZMI ) block(g,limx/2,limy/2,limx/2,limy/2,x2-x1); } } /** * Draw the "treasure chest", a floating box at the maze finish. * @param g The Graphics context. */ private void chest(Graphics g,int x1,int y1,int x2,int y2) { Dimension s = getSize(); int sy1=(s.height/2-y1)/5; int sx1=(s.width/2-x1)/5; int sy2=(s.height/2-y2)/5; int sx2=(s.width/2-x2)/5; int x=s.width/2; int y=s.height/2; g.drawLine(x-sx1,y-sy1,x+sx1,y-sy1); g.drawLine(x+sx1,y-sy1,x+sx1,y+sy1); g.drawLine(x+sx1,y+sy1,x-sx1,y+sy1); g.drawLine(x-sx1,y+sy1,x-sx1,y-sy1); g.drawLine(x-sx1,y-sy1,x-sx2,y-sy2); g.drawLine(x+sx1,y-sy1,x+sx2,y-sy2); g.drawLine(x+sx1,y+sy1,x+sx2,y+sy2); g.drawLine(x-sx1,y+sy1,x-sx2,y+sy2); g.drawLine(x-sx2,y-sy2,x+sx2,y-sy2); g.drawLine(x+sx2,y-sy2,x+sx2,y+sy2); g.drawLine(x+sx2,y+sy2,x-sx2,y+sy2); g.drawLine(x-sx2,y+sy2,x-sx2,y-sy2); } /** * Draw a marker on the floor for the cells that have been visited. */ private void block(Graphics g, int x1, int y1, int x2, int y2, int d) { d /= 10; if ( d < 1 ) d = 1; int e = 0; g.drawLine(x1-d+e,y1-d,x1-d,y1+d); // Left g.drawLine(x1-d,y1+d,x1+d,y1+d); // Bottom g.drawLine(x1+d,y1+d,x1+d-e,y1-d); // Right g.drawLine(x1+d-e,y1-d,x1-d+e,y1-d); // Top } }