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 to 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. public final static short HORIZONTAL = 1; public final static short VERTICAL = 2; public final static short DIRECTION = 4; // could we get rid of that to // free one bit for other purpose // ? public final static short POSITIVE = 8; public 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 << FLAGLENGTH | DIRECTION | HORIZONTAL | NEGATIVE; private final static short DOWN = Brick.DOWN << FLAGLENGTH | DIRECTION | VERTICAL | POSITIVE; private final static short RIGHT = Brick.RIGHT << FLAGLENGTH | DIRECTION | HORIZONTAL | POSITIVE; private final static short UP = Brick.UP << FLAGLENGTH | DIRECTION | VERTICAL | NEGATIVE; private final static short ENTRY = Brick.ENTRY << FLAGLENGTH; // flag when a // wall should // be open to // access // this. private final static short GOAL = Brick.GOAL << FLAGLENGTH; // flag when a // wall should // be open to // access this. public final static short SOLVED = 64 << FLAGLENGTH; // flag when solution // is on this path. private final static short FREE = 128 << FLAGLENGTH; // free flag // remains 2 free bits ( keep one out for sign ) private static final short[] AllDirections = { LEFT, DOWN, RIGHT, UP }; private int width; private int height; // wall flags + status flags for each position (x,y) private short[][] t; private int depth = 0; /** * will enforce maxdepth as maximum length of path in depth first. It means * that after every maxdepth moves there should be crossing paths. I think * that to shorter maxdepth is the harder the labyrinth is ... **/ private int maxdepth = 0; private int deepest = 0; // longest path found private int linearwork = 0; private Position deepestEnd = null; boolean maxreached = false; java.util.Random random; private final LinkedList 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; /** TODO private static MyLinkedList

{ MyLinkedList

next; P content MyLinkedList

( P object) { content=object; next=null; } MyLinkedList

addLast(P object) { next = new MyLinkedList(object); return next; } } */ 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(MazeParams params, Random random) { this.width = params.getWidth(); this.height = params.getHeight(); this.maxdepth = params.getMaxDepth(); 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; } // FIXME FULLY BREAK RESOLVING... public void noWalls(int x, int y) { if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { // t[x][y] |= DIRECTION | VERTICAL | POSITIVE | HORIZONTAL | NEGATIVE | LEFT | RIGHT | UP | DOWN; t[x][y] |= DIRECTION | VERTICAL | POSITIVE | HORIZONTAL | NEGATIVE; t[x][y] |= LEFT | RIGHT | UP | DOWN; } } /* set direction existing onee are lost */ // FIXME FULLY BREAK RESOLVING... public void setDirection(int x, int y, short path) { if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { t[x][y]= (short) (path | OPEN); } } /** add a new direction, exiting ones are kept */ // FIXME FULLY BREAK RESOLVING... public void addDirection(int x, int y, short path) { if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { t[x][y]|= (short) (path | OPEN); } } // 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(); // don't keep solved path for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { t[x][y] &= ~(SOLVED); } } } 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++) { // resetCell(x,y); t[x][y] &= ~( OPEN | SOLVED); } } } /** * 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 (isFlagSet(t[x][y],OPEN)) { check = false; t[x][y] |= OPEN; } if (isFlagSet(t[x][y],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 (isFlagSet(t[x][y],CLOSED)) { t[x][y] &= ~OPEN; } if (isFlagSet(t[x][y],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; } public final static boolean isFlagSet(short check, short flag) { return ((check & flag) == flag); } public final static short getReverseDirection(int index) { return AllDirections[(index + 2) % 4]; } /** return direction to use to be closer to 'to' from 'from' */ public final static short getDirection(Position from, Position to) { short pointingdirection = DIRECTION; if ( from.equals(to) ) { return pointingdirection; } if ( from.getX() < to.getX() ) { pointingdirection |= RIGHT; } else if ( from.getX() > to.getX() ) { pointingdirection |= LEFT; } if ( from.getY() < to.getY() ) { pointingdirection |= DOWN; } else if ( from.getY() > to.getY() ) { pointingdirection |= UP; } return pointingdirection; } /** * resolve this labrynth using internal representation * initial (x,y) is exit, will return list of positions from start to end. **/ public LinkedList resolve(int x, int y, MazeResolutionListener rlistener) { long safeguard = width * height; short selectedDirection = 0; int newx = x; int newy = y; // list of alternate paths LinkedList> altpath = new LinkedList<>(); // list of positions from start to end LinkedList backpath = new LinkedList(); // position that point to (x,y). DirectionPosition found = new DirectionPosition(selectedDirection,new Position(x, y)); while (!((newx == 0) && (newy == 0))) { if (( x == newx ) && ( y == newy )) { System.out.println("[ERROR] path contains same consecutive point" + new Position(newx, newy).toString()); } x = newx; y = newy; backpath.addFirst(found); found = null; // should find from all adjacent cells (directions) one that point to this. { // didx is index of four cell adjacent to this (x,y) for (int didx = 0; didx < 4; didx++) { int delta = 0; short direction = AllDirections[didx]; short reversedirection = getReverseDirection(didx); short pointingdirection = DIRECTION; if (isFlagSet(direction,POSITIVE)) { delta = 1; pointingdirection |= NEGATIVE; } else { delta = -1; pointingdirection |= POSITIVE; } if (isFlagSet(direction,HORIZONTAL)) { newx = x + delta; newy = y; pointingdirection |= HORIZONTAL; } else { newy = y + delta; newx = x; pointingdirection |= VERTICAL; } // internal GUARD. if (! isFlagSet(reversedirection,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 (isFlagSet(t[newx][newy],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. { int ax = found.getPosition().getX(); int ay = found.getPosition().getY(); if (! isFlagSet(t[newx][newy], SOLVED )) { LinkedList cp = (LinkedList) backpath.clone(); DirectionPosition altfound = new DirectionPosition(selectedDirection,new Position(newx, newy)); cp.addFirst(altfound); altpath.addLast(cp); } else { // this was already solved, might be a loop. if (rlistener != null) { rlistener.notifySearchError("Loop : two parents " + found.toString() + " " + new Position(newx, newy).toString()); } continue; } } } selectedDirection=reversedirection; if (isFlagSet(t[newx][newy], SOLVED )) { // was already solved. found = null; } else { found = new DirectionPosition(selectedDirection,new Position(newx, newy)); if (rlistener != null) { rlistener.notifySearch(found); } } // support multiple pathes // break; } } } if (found == null) { if ( ! altpath.isEmpty() ) { // new possible backpath backpath = altpath.removeFirst(); found = backpath.removeFirst(); if (rlistener != null) { rlistener.notifySearchError("try alternate path " + found.toString()); rlistener.notifySearch(found); } } } } if (found == null) { rlistener.notifySearchError("No path found !"); break; } // System.out.println(found); newx = found.getPosition().getX(); newy = found.getPosition().getY(); if (isFlagSet(t[newx][newy], SOLVED )) { System.out.println("[INFO] position already solved" + found.toString() + " *length:" + backpath.size()); } else { t[newx][newy] |= SOLVED; } safeguard --; if ( safeguard < 0 ) { rlistener.notifySearchError("Path too long ( or long overflow ) for width*height:" + (width*height) + " length:" + backpath.size()); break; } } 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; } public short getPath(int x, int y) { short path = 0; if (( x < width ) && ( y < height )) { path = t[x][y]; } return path; } /** * 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 !isFlagSet(t[newx][newy],(short)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."); } } }