/* * 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.util.*; /** * A maze. * Holds the maze layout, the finish location and the current player location. * The maze is three dimensions, but acts like a two dimensional maze if * the Z dimension has a size of 1. * Each cell contains a bitmap representing which exits are available * from that cell. The exits are known as XPL,XMI,YPL,YMI,ZPL,ZMI * representing a plus and minus direction exit in each of the 3 dimensions. * The maze is created such that adjoining cells share openings so that * the "doorway" is two directional. There are no exits around the outside * of the maze. The finish is just a designated cell. */ public class Maze implements MazeModel { private int xdim,ydim,zdim,max; //private static int[] bias = { 0, 5, 2, 3, 1, 1, 1 }; private static int[] bias = { 0, 100, 100, 100, 100, 1, 1 }; private byte[] maze; private Coordinate3D finish,current; private Vector mazeListeners; /** * Contruct a new maze with the given dimensions. * @param xdim The x dimension of the maze. * @param ydim The y dimension of the maze. * @param zdim The z dimension of the maze. Use 1 for a 2D maze. */ public Maze(int xdim, int ydim, int zdim) { this.xdim = xdim; this.ydim = ydim; this.zdim = zdim; max = xdim*ydim*zdim; maze = new byte[max]; finish = null; current = null; mazeListeners = new Vector(); create(); } /** * Contruct a new maze with the given dimensions. * @param size The 3D size of the maze. */ public Maze(Coordinate3D size) { this(size.x, size.y, size.z); } /** * Get the size of this maze. * @return The 3D size of the maze. */ public Coordinate3D getSize() { return new Coordinate3D(xdim,ydim,zdim); } /** * Put or remove a "I've been here" marker in a cell. * @param position The cell position. * @param mark True to place a mark, False to remove a mark. */ public void setMark(Coordinate3D position, boolean mark) { if ( mark ) put(position,MazeModel.MARK); else maze[position.z*ydim*xdim + position.y*xdim + position.x] &= ~MazeModel.MARK; } /** * Clear the maze layout. * Fills in all the walls. */ private void clearGrid() { for ( int i=0 ; i<max ; i++ ) maze[i] = MazeModel.WALLS; } /** * Get the given cell walls and status. * @param x The x co-ordinate. * @param y The y co-ordinate. * @param z The z co-ordinate. * @return The cell status, MARK if an invalid cell co-ordinate. */ public byte grid(int x, int y, int z) { if ( x>=xdim || x<0 || y>=ydim || y<0 || z>=zdim || z<0 ) return(MazeModel.MARK); return maze[z*ydim*xdim + y*xdim + x]; } /** * Get the given cell walls and status. * @param c The 3D co-ordinate of the desired cell. * @return The cell status, MARK if an invalid cell co-ordinate. */ public byte grid(Coordinate3D c) { return grid(c.x, c.y, c.z); } /** * Set where the finish cell is. * @param finish The 3D co-ordinate of the end of the maze. */ public void setFinish(Coordinate3D finish) { this.finish = finish; fireChange(); } /** * Make a random finish location. */ public void setRandomFinish() { Random random = new Random(); finish = new Coordinate3D( Math.abs(random.nextInt()%xdim), Math.abs(random.nextInt()%ydim), Math.abs(random.nextInt()%zdim)); fireChange(); } /** * Get the maze finish cell location. * May be null if one hasn't been set. */ public Coordinate3D getFinish() { return finish; } /** * Get the current player co-ordinate. * @return The 3D co-ordinate of the player in the maze. */ public Coordinate3D getCurrent() { return current; } /** * Set the current player co-ordinate. * @param current The 3D co-ordinate of the player in the maze. */ public void setCurrent(Coordinate3D current) { setMark(current,true); this.current = current; fireChange(); } /** * Check if the given cell has an "I've been here" marker. * @param position The 3D co-ordinate of the cell to check. * @return True if a marker has been placed. */ public boolean isMarked(Coordinate3D position) { return ( (grid(position) & MazeModel.MARK) != 0 ); } /** * Get a String representation of the given direction. * @param direction One of MazeModel.XMI,XPL,YMI,YPL,ZMI,ZPL. * @return A String representation of one of the above. */ public static String directionString(byte direction) { if ( (direction&MazeModel.XMI) != 0 ) return "XMI"; if ( (direction&MazeModel.XPL) != 0 ) return "XPL"; if ( (direction&MazeModel.YMI) != 0 ) return "YMI"; if ( (direction&MazeModel.YPL) != 0 ) return "YPL"; if ( (direction&MazeModel.ZMI) != 0 ) return "ZMI"; if ( (direction&MazeModel.ZPL) != 0 ) return "ZPL"; return "???"; } /** * Get a representation of the walls of the current cell. * @param x The x co-ordinate. * @param y The x co-ordinate. * @param z The x co-ordinate. * @return A String made up of X+,X-,Y+,Y- etc. */ private String gridString(int x, int y, int z) { byte val = grid(x,y,z); StringBuffer out = new StringBuffer(); if ( (val & MazeModel.XMI) != 0 ) out.append("X-"); if ( (val & MazeModel.XPL) != 0 ) out.append("X+"); if ( (val & MazeModel.YMI) != 0 ) out.append("Y-"); if ( (val & MazeModel.YPL) != 0 ) out.append("Y+"); if ( (val & MazeModel.ZMI) != 0 ) out.append("Z-"); if ( (val & MazeModel.ZPL) != 0 ) out.append("Z+"); if ( (val & MazeModel.MARK) != 0 ) out.append("MARK"); return out.toString(); } /** * Store the value (MazeModel.XPL etc.) into the cell. * @param x The x co-ordinate. * @param y The x co-ordinate. * @param z The x co-ordinate. * @param value The bitmap value: XPL, MARK, etc. */ private final void put(int x,int y,int z,byte value) { maze[z*ydim*xdim + y*xdim + x] |= value; } /** * Store the value (XPL etc.) into the cell. * @param pos The 3D co-ordinate. * @param value The bitmap value: XPL, MARK, etc. */ private final void put(Coordinate3D pos,byte value) { maze[pos.z*ydim*xdim + pos.y*xdim + pos.x] |= value; } /** * Count how many open walls there are in the cell value. * @param val The internal value representing the cell. * @return The number of open walls (0 thru 6). */ public static final int count(int val) { int cnt; cnt = 0; val &= 077; cnt += val&1; cnt += (val>>=1)&1; cnt += (val>>=1)&1; cnt += (val>>=1)&1; cnt += (val>>=1)&1; cnt += (val>>=1)&1; //System.out.println("count: val " + // String.valueOf(val) + // "=" + String.valueOf(cnt)); return cnt; } /** * Create a random maze. */ public void create() { int[] listx,listy,listz; int cells = 1; int top = 0; int x,y,z; int tmp,pickcount,cnt,choice; int[] pick = new int[6]; Random random = new Random(); listx = new int[max]; listy = new int[max]; listz = new int[max]; listx[0] = (xdim-1)/2; listy[0] = (ydim-1)/2; listz[0] = (zdim-1)/2; clearGrid(); while ( top >= 0 ) { x = listx[top]; y = listy[top]; z = listz[top]; //System.out.println("Create at " + // String.valueOf(x) + // "," + String.valueOf(y) + // "," + String.valueOf(z) + // " level " + String.valueOf(top) + // " grid " + gridString(x,y,z)); if ( grid(x,y,z) != 0 ) { top--; continue; } if ( cells > 1 ) { pickcount = cnt = 0; if ( (tmp = bias[count(grid(x+1,y,z))]) != 0) cnt++; pick[0] = (pickcount += tmp); if ( (tmp = bias[count(grid(x-1,y,z))]) != 0) cnt++; pick[1] = (pickcount += tmp); if ( (tmp = bias[count(grid(x,y+1,z))]) != 0) cnt++; pick[2] = (pickcount += tmp); if ( (tmp = bias[count(grid(x,y-1,z))]) != 0) cnt++; pick[3] = (pickcount += tmp); if ( (tmp = bias[count(grid(x,y,z+1))]) != 0) cnt++; pick[4] = (pickcount += tmp); if ( (tmp = bias[count(grid(x,y,z-1))]) != 0) cnt++; pick[5] = (pickcount += tmp); } else { for ( int i=0 ; i<4 ; i++ ) pick[i] = i+1; pickcount = cnt = 4; } if ( cnt == 0 ) { top--; continue; } choice = Math.abs(random.nextInt()%pickcount); //System.out.println("choice=" + String.valueOf(choice)); //System.out.println("pick 0=" + String.valueOf(pick[0]) + // " 1=" + String.valueOf(pick[1]) + // " 2=" + String.valueOf(pick[2]) + // " 3=" + String.valueOf(pick[3]) + // " 4=" + String.valueOf(pick[4]) + // " 5=" + String.valueOf(pick[5])); if ( pick[0] > choice ) { put(x,y,z,MazeModel.XPL); put(x+1,y,z,MazeModel.XMI); } else if ( pick[1] > choice ) { put(x,y,z,MazeModel.XMI); put(x-1,y,z,MazeModel.XPL); } else if ( pick[2] > choice ) { put(x,y,z,MazeModel.YPL); put(x,y+1,z,MazeModel.YMI); } else if ( pick[3] > choice ) { put(x,y,z,MazeModel.YMI); put(x,y-1,z,MazeModel.YPL); } else if ( pick[4] > choice ) { put(x,y,z,MazeModel.ZPL); put(x,y,z+1,MazeModel.ZMI); } else if ( pick[5] > choice ) { put(x,y,z,MazeModel.ZMI); put(x,y,z-1,MazeModel.ZPL); } /* printf("new val %o\n",grid(x,y,z)); */ if ( grid(x-1,y,z) == MazeModel.WALLS ) { listx[top] = x-1; listy[top] = y; listz[top] = z; top++; } if ( grid(x+1,y,z) == MazeModel.WALLS ) { listx[top] = x+1; listy[top] = y; listz[top] = z; top++; } if ( grid(x,y-1,z) == MazeModel.WALLS ) { listx[top] = x; listy[top] = y-1; listz[top] = z; top++; } if ( grid(x,y+1,z) == MazeModel.WALLS ) { listx[top] = x; listy[top] = y+1; listz[top] = z; top++; } if ( grid(x,y,z-1) == MazeModel.WALLS ) { listx[top] = x; listy[top] = y; listz[top] = z-1; top++; } if ( grid(x,y,z+1) == MazeModel.WALLS ) { listx[top] = x; listy[top] = y; listz[top] = z+1; top++; } //System.out.println("top=" + String.valueOf(top)); top--; cells++; } fireChange(); } /** * Return the opposite direction. * @param value One of XPL,XMI,YPL,YMI,ZPL,ZMI. * @return One of XMI,XPL,YMI,YPL,ZMI,ZPL. */ public static byte invert(byte value) { switch (value) { case MazeModel.XPL: return(MazeModel.XMI); case MazeModel.XMI: return(MazeModel.XPL); case MazeModel.YPL: return(MazeModel.YMI); case MazeModel.YMI: return(MazeModel.YPL); case MazeModel.ZPL: return(MazeModel.ZMI); case MazeModel.ZMI: return(MazeModel.ZPL); } return 0; } /** * Move between cells in a maze. * Does not check for walls or maze edges. * @param c The 3D co-ordinate to start from. * @param direction The direction to go, one of XPL,XMI etc. * @return The ending 3D co-ordinate. */ public static Coordinate3D forward(Coordinate3D c, byte direction) { switch (direction) { case MazeModel.XPL: c.x++; break; case MazeModel.XMI: c.x--; break; case MazeModel.YPL: c.y++; break; case MazeModel.YMI: c.y--; break; case MazeModel.ZPL: c.z++; break; case MazeModel.ZMI: c.z--; break; } return c; } /** * Check and see if movement is possible. * @param current The 3D co-ordinate to start from. * @param direction The direction to go, one of XPL,XMI etc. * @return True if movement possible, false if blocked by a wall. */ public boolean isOpen(Coordinate3D current,byte direction) { return (grid(current) & direction) != 0; } /** * Solve the maze from a given start position. * @param start The 3D co-ordinate to start from. * @return A Stack of 3D co-ordinates giving the path from * the given start to the known finish. */ public Stack solve(Coordinate3D start) { Stack stack = new Stack(); Coordinate3D current = start; solveCell(stack,current,(byte)0); for ( int i=0 ; i<stack.size() ; i++ ) { current = (Coordinate3D)stack.elementAt(i); System.out.println(String.valueOf(i) + " " + current.toString()); } return stack; } /** * A recursive routine to find the finish. * @param stack The current Stack of moves from the start. * @param position The current cell position in the search. * @param dir The direction we used to move into the cell * to prevent overlapping the path. * @return True if a path to the finish was found. */ private boolean solveCell(Stack stack,Coordinate3D position,byte dir) { System.out.println("searching " + position.toString() + " " + directionString(dir)); stack.push(position); setMark(position,true); if ( position == finish ) return true; byte from = invert(dir); if ( isOpen(position,MazeModel.XPL) && from != MazeModel.XPL ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.XPL),MazeModel.XPL) ) return true; if ( isOpen(position,MazeModel.XMI) && from != MazeModel.XMI ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.XPL),MazeModel.XMI) ) return true; if ( isOpen(position,MazeModel.YPL) && from != MazeModel.YPL ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.YPL),MazeModel.YPL) ) return true; if ( isOpen(position,MazeModel.YMI) && from != MazeModel.YMI ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.YMI),MazeModel.YMI) ) return true; if ( isOpen(position,MazeModel.ZPL) && from != MazeModel.XPL ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.ZPL),MazeModel.ZPL) ) return true; if ( isOpen(position,MazeModel.ZMI) && from != MazeModel.ZMI ) if ( solveCell(stack,forward(new Coordinate3D(position),MazeModel.ZMI),MazeModel.ZMI) ) return true; stack.pop(); setMark(position,false); return false; } /** * Add a MazeListener. * An event is fired when the maze is created or when the * finish or current positions change. * @param l A MazeListener to listen for maze changes. */ public void addMazeListener(MazeListener l) { mazeListeners.addElement(l); } /** * Remove a MazeListener. * @param l The MazeListener to remove. */ public void removeMazeListener(MazeListener l) { mazeListeners.removeElement(l); } /** * Fire a change in the maze. */ protected void fireChange() { for ( int i=mazeListeners.size()-1 ; i>=0 ; i-- ) { MazeListener l = (MazeListener)mazeListeners.elementAt(i); l.mazeChanged(new EventObject(this)); } } }