package org.artisanlogiciel.games; import java.util.ArrayList; import java.util.LinkedList; import java.util.Collections; import java.util.Random; import java.io.OutputStream; import java.io.InputStream; import java.io.IOException; import java.io.DataOutputStream; import java.io.DataInputStream; /** Model of labyrinth storing only paths not walls. wall are regenerated later on based on adjacent paths. each position (x,y) stores what move can be done from here to go do deeper path. a node is tagged OPEN and contained in openList if all moves from its position have not been resolved a node is tagged CLOSED when fully processed **/ public class LabyModel implements WallsProvider { /** WARNING don't change those values, they are used as it is for optimisation */ private final static short FLAGLENGTH=7; private final static short CLEAR=0; // mandatory 0 since array creation is initialized with 0. private final static short HORIZONTAL=1; private final static short VERTICAL=2; private final static short DIRECTION=4; // could we get rid of that to free one bit for other purpose ? private final static short POSITIVE=8; private final static short NEGATIVE=16; private final static short OPEN=32; // can be reused once generation is completed private final static short CLOSED=64; // can be reused once generation is completed private final static short LEFT=Brick.LEFT< openList = new LinkedList(); //list of entries and exits. private final LinkedList entryExits = new LinkedList(); private final Object coherentLock = new Object(); // before getting the lock and after lock release all is coherent ( ie check() is ok ), MazeCreationListener listener = null; public LabyModel(int width, int height,int maxdepth, Random random){ this.width=width; this.height=height; this.maxdepth=maxdepth; this.random = random; // CLEAR == 0 and array is initialized with 0s t= new short[width][height]; } public LabyModel(String pFormat, InputStream pIn) throws IOException { parseInputStream(pFormat, pIn); } public void setMazeListener(MazeCreationListener listener) { this.listener=listener; } // entry and exit can be outside the model boundaries a one x or one y out. public boolean addEntryOrExit(int x, int y) { entryExits.add(new Position(x,y)); if (( x > 0 ) && ( x < width ) && ( y > 0 ) && ( y < height )) { t[x][y] |= ENTRY; } return true; } public void debugOut() { for (int y=0; y < height; y++) { for (int x=0; x < width; x++) { out4XY(x,y); } System.out.print('\n'); } } public void out4XY(int x,int y) { int direction=t[x][y]; if ( ( direction & OPEN ) == OPEN ) { System.out.print("?"); } else if ( ( direction & CLOSED ) == CLOSED ) { System.out.print("."); } else { System.out.print(" "); } // don't display information about short. direction &= ~OPEN; direction &= ~GOAL; direction &= ~CLOSED; switch(direction){ case LEFT: // left System.out.print("<-"); break; case DOWN: // down System.out.print("vv"); break; case RIGHT: // right System.out.print("->"); break; case UP: // up System.out.print("^^"); break; case HORIZONTAL: // - System.out.print("--"); break; case VERTICAL: // | System.out.print("||"); break; case CLEAR: System.out.print(" "); break; case CLOSED: System.out.print("00"); break; case RIGHT|LEFT: System.out.print("--"); break; case UP|DOWN: System.out.print("||"); break; case RIGHT|UP: System.out.print("^>"); break; case RIGHT|DOWN: System.out.print("v>"); break; case LEFT|DOWN: System.out.print(""); break; case LEFT|RIGHT|UP|DOWN: System.out.print("++"); break; case OPEN: System.out.print("??"); break; case GOAL: System.out.print("**"); break; default: System.out.print(".."); } if ( ( (t[x][y] & RIGHT) == RIGHT ) || ( ( x+ 1 < width) && ( ( t[x+1][y] & LEFT ) == LEFT ) )) { System.out.print("-"); } else { System.out.print("H"); } } public String outHorizWall2XY(int x, int y) { String freeway= " "; if ( (t[x][y] & SOLVED) == SOLVED) { freeway="*"; } if ( ( x < 0 ) || ( x >width ) ) { return " H"; } if ( ( y < 0 ) || ( y >= height ) ) { return "HH"; } if ( ( (t[x][y] & DOWN) == DOWN ) || (( y+ 1 < height) && ( ( t[x][y+1] & UP ) == UP ))) { return freeway + "H"; } else { return "HH"; } } public String out2XY(int x,int y) { // can check for entry exits. if ( ( y < 0 ) || ( y >= height ) || ( x < 0 ) || ( x >width ) ) { return " H"; } String low=""; int direction=t[x][y]; String freeway= " "; if ( (t[x][y] & SOLVED) == SOLVED) { freeway="*"; } // don't display information about short. direction &= ~OPEN; direction &= ~GOAL; direction &= ~CLOSED; direction &= ~SOLVED; switch(direction){ case LEFT: case DOWN: case RIGHT: case UP: low=freeway; break; case HORIZONTAL: // - low=" "; break; case VERTICAL: // | low=" "; break; case CLEAR: low=" "; break; case CLOSED: low="0"; break; case RIGHT|LEFT: case UP|DOWN: case RIGHT|UP: case RIGHT|DOWN: case LEFT|DOWN: case LEFT|UP: case LEFT|RIGHT|UP: case LEFT|RIGHT|DOWN: case LEFT|DOWN|UP: case RIGHT|DOWN|UP: case LEFT|RIGHT|UP|DOWN: low="."; break; case OPEN: low="?"; break; case GOAL: low="*"; break; default: low="."; } for ( Position exit: entryExits ) { if ( ( exit.getX() == x + 1 ) && ( exit.getY() == y )) { low=low+">"; return low; } } if ( ( (t[x][y] & RIGHT) == RIGHT ) || ( ( x+ 1 < width) && ( ( t[x+1][y] & LEFT ) == LEFT ) )) { low=low+freeway; } else { low=low+"H"; } return low; } public LabyMap toLabyMap() { synchronized (coherentLock) { Brick[][] brickMap = new Brick[width][height]; for (int y=0; y< height; y++) { for ( int x=0; x < width; x++) { brickMap[x][y]=new Brick(out2XY(x,y), outHorizWall2XY(x, y),getWalls(x,y)); } } return new LabyMap(brickMap, (LinkedList) entryExits.clone()); } } public int getWidth(){ return this.width; } public int getHeight(){ return this.height; } public LinkedList getOpenList() { return openList; } public void reset() { depth = 0; computeOpenList(); } public void fullReset() { depth = 0; openList.clear(); maxreached = false; deepest=0; deepestEnd=null; // clear open for (int y=0; y < height; y++) { for (int x=0; x < width; x++) { t[x][y] &= ~OPEN; } } } /** check model coherency and fix if model needed a fix it means that algorithm has a problem @return true if model is ok and no fix was applied. **/ public boolean check() { boolean check = true; synchronized(coherentLock) { // node in OPEN should be tagged OPEN and not CLOSED. for ( Position p : openList) { int x = p.getX(); int y = p.getY(); if ( (t[x][y] & OPEN ) != OPEN ) { check = false; t[x][y] |= OPEN; } if ( (t[x][y] & CLOSED ) == CLOSED ) { check = false; t[x][y] &= ~CLOSED; } } // should do reverse : every node tagged OPEN should be in open list. // a little more cpu consuming... } return check; } public void computeOpenList() { openList.clear(); for (int y=0; y < height; y++) { for (int x=0; x < width; x++) { if ( (t[x][y] & CLOSED ) == CLOSED ) { t[x][y] &= ~OPEN; } if ( ( (t[x][y] & OPEN) == OPEN ) || ( t[x][y] == CLEAR )) { openList.add(new Position(x,y)); } } } } public void generateWithEntry(int x, int y){ openList.add(new Position(x,y)); while ( ! openList.isEmpty() ) { step(0); // fixme depth not set synchronized(coherentLock) { linearwork ++; if ( linearwork % maxdepth == 0 ) { coherentLock.notifyAll(); if (listener != null) { listener.changed(null,null,this); } // should not continue in depth first... if ( ! openList.isEmpty() ) { // insert head as next position to pick up. Position current = openList.removeFirst(); openList.add(current); } } } } if ( deepestEnd != null ) { t[deepestEnd.getX()][deepestEnd.getY()]|=GOAL; } } public int getDepth() { return depth; } public Position getDeepestEnd() { return deepestEnd; } public int getDeepestPath() { return deepest; } public boolean maxReached() { return maxreached; } /** @param p Position @return true if newly added , false if already open. **/ private boolean open(boolean first, Position p) { int x = p.getX(); int y = p.getY(); if ( ( t[x][y] & OPEN ) != OPEN ) { t[x][y]|=OPEN; openList.addLast(p); return true; } return false; } /** resolve this labrynth using internal representation **/ public LinkedList resolve(int x, int y, MazeResolutionListener rlistener) { int newx=x; int newy=y; LinkedList backpath = new LinkedList(); while ( ! (( newx == 0 ) && ( newy == 0 ))) { Position found=null; x = newx; y = newy; backpath.addFirst( new Position(x,y)); // should find from all direction one that point to this. ArrayList freeDirection = new ArrayList(); for (int didx=0; didx < 4; didx ++ ) { int delta=0; short direction=AllDirections[didx]; short reversedirection=AllDirections[(didx+2)%4]; short pointingdirection=DIRECTION; if ( ( direction & POSITIVE ) == POSITIVE ) { delta = 1; pointingdirection |= NEGATIVE ; } else { delta = -1; pointingdirection |= POSITIVE ;} if ( ( direction & HORIZONTAL ) == HORIZONTAL ) { newx = x + delta; newy = y; pointingdirection |= HORIZONTAL; } else { newy = y + delta; newx = x; pointingdirection |= VERTICAL ;} // internal GUARD. if ( (reversedirection & pointingdirection) != pointingdirection ) { System.out.println("Internal ERROR. Please check AllDirections order " + ( reversedirection & pointingdirection ) + " " + pointingdirection ); return backpath; } if ( (newx >= 0) && ( newy >= 0 ) && (newx < width) && (newy < height) ) { if ( ( t[newx][newy] & reversedirection ) == reversedirection){ if ( found != null ) { // could be a unique parent of two paths... // but search from other entry/exits than generated from // ie entry(0,0) exit(width-1,height-1) not yet implemented. if (rlistener != null) { rlistener.notifySearchError("Model Error : two parents " + found.toString() + " " + new Position(newx,newy).toString()); } return backpath; } found = new Position(newx,newy); if (rlistener != null) { rlistener.notifySearch(found); } break; } } } if ( found == null ) { rlistener.notifySearchError("No path found !"); break; } // System.out.println(found); newx=found.getX(); newy=found.getY(); t[newx][newy] |= SOLVED; } if (rlistener != null) { rlistener.notifyCompletion(backpath); } return backpath; } /** @returns wheter process closed current node. **/ public boolean step(int depth) { boolean complete = false; Position current = null; synchronized(coherentLock) { if ( ! openList.isEmpty() ) { // last : in depth before. current = openList.getLast(); } else { return true; } if ( current != null ) { int x = current.getX(); int y = current.getY(); // should find all free positions... ArrayList freeDirection = new ArrayList(); for (short direction : AllDirections ) { int delta=0; int newx = -1; int newy = -1; if ( ( direction & POSITIVE ) == POSITIVE ) { delta = 1; } else { delta = -1; } if ( ( direction & HORIZONTAL ) == HORIZONTAL ) { newx = x + delta; newy = y; } else { newy = y + delta; newx = x; } if ( (newx >= 0) && ( newy >= 0 ) && (newx < width) && (newy < height) ) { if( (t[newx][newy] ) == CLEAR){ freeDirection.add(new Short(direction)); } } } if ( ! freeDirection.isEmpty()) { // control random using our own pseudorandom short direction = 0; if ( freeDirection.size() > 1 ) { direction=freeDirection.get(random.nextInt(freeDirection.size())); } else { direction=freeDirection.get(0); Position last=openList.removeLast(); if ( last != current ) { // GUARD, should not happen ( or multi-thread access to model error ) System.err.println("INTERNAL ERROR 3"); return false; } } int delta=0; int newx = -1; int newy = -1; if ( ( direction & POSITIVE ) == POSITIVE ) { delta = 1; } else { delta = -1; } if ( ( direction & HORIZONTAL ) == HORIZONTAL ) { newx = x + delta; newy = y; } else { newy = y + delta; newx = x; } Position target = new Position(newx,newy,current.getDepth()+1); open(false,target); if (( t[x][y] & DIRECTION ) == DIRECTION ) { t[x][y] |= direction; } else { t[x][y] = direction; } // not a 'direction' ... is it necessary to check ? if ( freeDirection.size() > 1 ) { // keep it open at the very same place, previous open did add another node to inspect. return false; } // else this was the unique direction , object already removed ( since if we try to remove it now it is not the last ) // can proceed to close } else { // no free direction remaining => closing Position last=openList.removeLast(); if ( last != current ) { // GUARD, should not happen. System.err.println("INTERNAL ERROR 3"); return false; } } complete = true; t[x][y]&=~OPEN; t[x][y]|=CLOSED; if ( current.getDepth() > deepest ) { deepest = depth; deepestEnd = current; } } // current != null; } // synchronized return complete; } public short getWalls(int x, int y) { short walls=0; for (short direction: AllDirections) { if (hasWallInDirection(x,y,direction)) { walls |= (direction>>FLAGLENGTH); } } return walls; } /** is there a wall in that direction ? **/ public boolean hasWallInDirection(int x, int y, short direction) { int newx=0; int newy=0; int delta=0; // best way to find reversedirection ? int reversedirection=0; // is this direction on the path ? yes => no wall if ( ( t[x][y] & direction ) == direction ) { return false; } // is adjacent tile in direction pointing in reverse direction ? yes => no wall if ( ( direction & POSITIVE ) == POSITIVE ) { delta = 1; } else { delta = -1; } if ( ( direction & HORIZONTAL ) == HORIZONTAL ) { newx = x + delta; newy = y; reversedirection = (direction == RIGHT) ? LEFT : RIGHT; } else { newy = y + delta; newx = x; reversedirection = (direction == UP) ? DOWN : UP; } if ( (newx >= 0) && ( newy >= 0 ) && (newx < width) && (newy < height) ) { return ( (t[newx][newy]&reversedirection) != reversedirection ); } else { // outside boundaries. // TODO CHECK exits. return true; } } public void streamOut( String pFormat, OutputStream pOut) throws IOException { if ((pFormat == null ) || (pFormat.equals("raw"))) { // first raw format, not smart. DataOutputStream dataOut = new DataOutputStream(pOut); // should dump this to stream. dataOut.write( new byte[] { (byte) 'L', (byte) 'A', (byte) 'B', (byte) '0' }); dataOut.writeInt(getWidth()); dataOut.writeInt(getHeight()); dataOut.flush(); for (int y = 0; y < getHeight(); y++) { for (int x = 0 ; x < getWidth() ; x++) { dataOut.writeShort(t[x][y]); } } dataOut.flush(); } else { throw new IOException("Format " + pFormat + " Not yet implemented."); } } private void streamIn( String pFormat, InputStream pIn) throws IOException { throw new IOException("Use correct constructor."); } private void parseInputStream( String pFormat, InputStream pIn) throws IOException { if (( pFormat == null) || ( pFormat.equals("raw") )) { byte[] header = new byte[4]; DataInputStream in = new DataInputStream(pIn); in.read(header); int rwidth = in.readInt(); int rheight = in.readInt(); width=rwidth; height=rheight; maxdepth=maxdepth; random = null; // SHOULD CHECK max width and max height... // CLEAR == 0 and array is initialized with 0s t= new short[width][height]; for (int y = 0; y < height; y++) { for (int x = 0 ; x < width ; x++) { t[x][y] = in.readShort(); } } // should be at end of stream ? Not necessary can stream multiple labs ( or tiling ). } else { throw new IOException("Format " + pFormat + " Not yet implemented."); } } }