initial commit for labryinth project on git_hub.

Signed-off-by: philippe lhardy <philippe@pavilionartlogiciel>
This commit is contained in:
philippe lhardy
2014-12-24 17:28:23 +01:00
commit 850744326a
14 changed files with 2106 additions and 0 deletions

View File

@@ -0,0 +1,808 @@
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<<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.
private 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;
private short [][] t;
private int depth=0;
/** will enforce maxdepth as maxium 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<Position> openList = new LinkedList<Position>();
//list of entries and exits.
private final LinkedList<Position> entryExits = new LinkedList<Position>();
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("<v");
break;
case LEFT|UP:
System.out.print("<^");
break;
case LEFT|RIGHT|UP:
System.out.print("LL");
break;
case LEFT|RIGHT|DOWN:
System.out.print("TT");
break;
case LEFT|DOWN|UP:
System.out.print("<|");
break;
case RIGHT|DOWN|UP:
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<Position>) entryExits.clone());
}
}
public int getWidth(){
return this.width;
}
public int getHeight(){
return this.height;
}
public LinkedList<Position> 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<Position> resolve(int x, int y, MazeResolutionListener rlistener)
{
int newx=x;
int newy=y;
LinkedList<Position> backpath = new LinkedList<Position>();
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<Short> freeDirection = new ArrayList<Short>();
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<Short> freeDirection = new ArrayList<Short>();
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.");
}
}
}