Files
artloglaby/java/org/artisanlogiciel/games/LabyModel.java
2017-12-01 22:20:33 +01:00

980 lines
28 KiB
Java

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<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(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;
}
}
// 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++)
{
// resetCell(x,y);
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;
}
public final static boolean isFlagSet(short check, short flag)
{
return ((check & flag) == flag);
}
/**
* resolve this labrynth using internal representation
* initial (x,y) is exit, will return list of positions from start to end.
**/
public LinkedList<DirectionPosition> resolve(int x, int y, MazeResolutionListener rlistener)
{
long safeguard = width * height;
short selectedDirection = 0;
int newx = x;
int newy = y;
// list of positions from start to end
LinkedList<DirectionPosition> backpath = new LinkedList<DirectionPosition>();
// 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.
{
ArrayList<Short> freeDirection = new ArrayList<Short>();
// 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 = AllDirections[(didx + 2) % 4];
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.
if (rlistener != null)
{
rlistener.notifySearchError("Model Error : two parents " + found.toString() + " "
+ new Position(newx, newy).toString());
}
return backpath;
}
selectedDirection=reversedirection;
found = new DirectionPosition(selectedDirection,new Position(newx, newy));
if (rlistener != null)
{
rlistener.notifySearch(found);
}
// support multiple pathes
// break;
}
}
}
}
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<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;
}
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 ((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.");
}
}
}