- display cell as hexagon is Display.mHexagon if set ( not default ) will need a checkbox to set this - various new models preparation...
938 lines
29 KiB
Java
938 lines
29 KiB
Java
package org.artisanlogiciel.games.maze;
|
|
|
|
import org.artisanlogiciel.games.maze.model.LabyModelProvider;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedList;
|
|
import java.util.Random;
|
|
|
|
/**
|
|
* 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 LabyModelProvider {
|
|
|
|
/**
|
|
* WARNING don't change those va public short getPath(int x, int y) {
|
|
lues, 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
|
|
// ?
|
|
// can be both POSITIVE and NEGATIVE, it means that you can move in positive direction and in negative direction.
|
|
public final static short POSITIVE = 8;
|
|
public final static short NEGATIVE = 16;
|
|
|
|
// completed
|
|
public final static short LEFT = Brick.LEFT << FLAGLENGTH | DIRECTION | HORIZONTAL | NEGATIVE;
|
|
public final static short DOWN = Brick.DOWN << FLAGLENGTH | DIRECTION | VERTICAL | POSITIVE;
|
|
public final static short RIGHT = Brick.RIGHT << FLAGLENGTH | DIRECTION | HORIZONTAL | POSITIVE;
|
|
public final static short UP = Brick.UP << FLAGLENGTH | DIRECTION | VERTICAL | NEGATIVE;
|
|
|
|
public final static short EMPTY = LEFT | DOWN | RIGHT | UP ;
|
|
|
|
// flag when a wall should be open to access this for entry
|
|
public final static short ENTRY = Brick.ENTRY << FLAGLENGTH;
|
|
// flag when a wall should be open to access this for exit
|
|
private final static short GOAL = Brick.GOAL << FLAGLENGTH;
|
|
|
|
// can it be both OPEN and CLOSED ?
|
|
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
|
|
// flag when solution is on this path.
|
|
public final static short SOLVED = 64 << FLAGLENGTH;
|
|
// free flag
|
|
private final static short FREE = 128 << FLAGLENGTH;
|
|
// remains 2 free bits ( keep one out for sign )
|
|
|
|
// orders matters see getReverseDirection
|
|
public static final short[] AllDirections = {LEFT, DOWN, RIGHT, UP};
|
|
|
|
private int width;
|
|
private int height;
|
|
// wall flags Brick.(LEFT,DOWN,RIGHT,UP,ENTRY,GOAL) + 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;
|
|
// longest path found
|
|
private int deepest = 0;
|
|
// each move is a linearwork step
|
|
private int linearwork = 0;
|
|
|
|
private Position deepestEnd = null;
|
|
boolean maxreached = false;
|
|
java.util.Random random;
|
|
// list of positions not fully walked
|
|
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;
|
|
|
|
// do we create wall when only one way is possible ?
|
|
private boolean onewaywall = false;
|
|
|
|
private 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) {
|
|
this(params.getWidth(), params.getHeight(), params.getMaxDepth(), new java.util.Random(params.getSeed()));
|
|
}
|
|
|
|
/**
|
|
* construct LabyModel from an InputStream, yet only "raw" is supported
|
|
**/
|
|
public LabyModel(int width, int height, short [][] t) {
|
|
random = null;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.t = t;
|
|
}
|
|
|
|
public LabyModel(LabyModel other ) {
|
|
random = null;
|
|
this.width = other.width;
|
|
this.height = other.height;
|
|
this.t = other.t;
|
|
}
|
|
|
|
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] |= 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)
|
|
{
|
|
out4XY(x,y,t[x][y],(x < width -1) ?t[x+1][y] : 0);
|
|
}
|
|
|
|
public void out4XY(int x, int y, short txy, short txpy) {
|
|
int direction = txy;
|
|
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 (((txy & RIGHT) == RIGHT) || ((x + 1 < width) && (txpy & LEFT) == LEFT)) {
|
|
System.out.print("-");
|
|
} else {
|
|
System.out.print("H");
|
|
}
|
|
|
|
}
|
|
|
|
public String outHorizWall2XY(int x, int y) {
|
|
return outHorizWall2XY(x,y,t[x][y],(y<height-1) ? t[x][y+1] : 0);
|
|
}
|
|
|
|
public String outHorizWall2XY(int x, int y,short txy, short txyp) {
|
|
String freeway = " ";
|
|
if ((txy & SOLVED) == SOLVED) {
|
|
freeway = "*";
|
|
}
|
|
if ((x < 0) || (x > width)) {
|
|
return " H";
|
|
}
|
|
if ((y < 0) || (y >= height)) {
|
|
return "HH";
|
|
}
|
|
|
|
if (((txy & DOWN) == DOWN) || ((y + 1 < height) && ((txyp & UP) == UP))) {
|
|
return freeway + "H";
|
|
} else {
|
|
return "HH";
|
|
}
|
|
}
|
|
|
|
public String out2XY(int x, int y)
|
|
{
|
|
return out2XY(x,y,t[x][y],(x < width -1) ?t[x+1][y] : 0, width, height);
|
|
}
|
|
|
|
public static String out2XY(int x, int y, short txy, short txpy, int width, int height) {
|
|
// can check for entry exits.
|
|
|
|
if ((y < 0) || (y >= height) || (x < 0) || (x > width)) {
|
|
return " H";
|
|
}
|
|
String low = "";
|
|
int direction = txy;
|
|
String freeway = " ";
|
|
if ((txy & 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 = ".";
|
|
}
|
|
|
|
/* don't display entry or exits
|
|
for (Position exit : entryExits) {
|
|
if ((exit.getX() == x + 1) && (exit.getY() == y)) {
|
|
low = low + ">";
|
|
return low;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (((txy & RIGHT) == RIGHT) || ((x + 1 < width) && ((txpy & 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, new LinkedList<Position>(entryExits));
|
|
}
|
|
}
|
|
|
|
public int getWidth() {
|
|
return this.width;
|
|
}
|
|
|
|
public int getHeight() {
|
|
return this.height;
|
|
}
|
|
|
|
public LinkedList<Position> getOpenList() {
|
|
return openList;
|
|
}
|
|
|
|
public void reset() {
|
|
depth = 0;
|
|
computeOpenList();
|
|
resetResolving();
|
|
}
|
|
|
|
public void resetResolving() {
|
|
// 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;
|
|
}
|
|
|
|
// coherency : if CLOSED then not OPEN
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is core of generator.
|
|
* <p>
|
|
* generate a maze with an entry set at (x,y) with a maxdepth
|
|
**/
|
|
public void generateWithEntry(int x, int y) {
|
|
|
|
openList.add(new Position(x, y));
|
|
while (!openList.isEmpty()) {
|
|
// this is where magic happens
|
|
step();
|
|
|
|
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 short getCell(Position p) {
|
|
return t[p.getX()][p.getY()];
|
|
}
|
|
|
|
public final void updateCell(Position p, short flags) {
|
|
t[p.getX()][p.getY()] |= flags;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
private final void closePosition(int x, int y) {
|
|
t[x][y] &= ~OPEN;
|
|
t[x][y] |= CLOSED;
|
|
}
|
|
|
|
/**
|
|
* One step in Maze generation process
|
|
* get last element in open list and explore one random free direction with it.
|
|
*
|
|
* @returns whether process closed current node.
|
|
**/
|
|
public boolean step() {
|
|
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)) {
|
|
// CLEAR is 0 : no tag at all.
|
|
if ((t[newx][newy]) == CLEAR) {
|
|
freeDirection.add(Short.valueOf(direction));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!freeDirection.isEmpty()) {
|
|
// control random using our own pseudorandom
|
|
short direction = 0;
|
|
// System.out.println("" + freeDirection.size() + "\n" );
|
|
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;
|
|
closePosition(x, y);
|
|
|
|
final int currentdepth = current.getDepth();
|
|
if (currentdepth > deepest) {
|
|
deepest = currentdepth;
|
|
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;
|
|
}
|
|
|
|
public short getMoves(int x, int y)
|
|
{
|
|
return getPath(x,y);
|
|
}
|
|
/**
|
|
* 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 ( isFlagSet(t[x][y], direction)) {
|
|
if (!onewaywall) {
|
|
return false;
|
|
}
|
|
// onewaywall : should check reverse direction is ok too.
|
|
}
|
|
else
|
|
{
|
|
if (onewaywall)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// is adjacent tile in direction pointing in reverse direction ? yes =>
|
|
// no wall
|
|
delta = ((direction & POSITIVE) == POSITIVE) ? 1 : -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 setOnewaywall(boolean onewaywall) {
|
|
this.onewaywall = onewaywall;
|
|
}
|
|
|
|
/**
|
|
* is there no wall in that direction ?
|
|
**/
|
|
public boolean canMoveInDirection(int x, int y, short direction) {
|
|
// one way wall will create walll if it not possible to step back to current position from next position
|
|
return onewaywall ? ( ! hasWallInDirection(x,y, (short) (direction << FLAGLENGTH)) ) : ((getWalls(x, y) & direction ) == 0);
|
|
}
|
|
|
|
public boolean reverse(boolean grow)
|
|
{
|
|
return reversefast(grow);
|
|
}
|
|
|
|
public boolean reversefast(boolean grow)
|
|
{
|
|
// use current moves as walls ... and rebuild new direction from it.
|
|
|
|
int deltasize = grow ? 1 : -1;
|
|
int newwidth = getWidth() + deltasize;
|
|
int newheight = getHeight() + deltasize;
|
|
int delta = grow ? 1 : 0;
|
|
|
|
if ( ( newwidth < 1) || (newheight < 1 ) )
|
|
{
|
|
System.err.println("Can't reverse, too small");
|
|
return false;
|
|
}
|
|
short newt[][] = new short[newwidth][newheight];
|
|
short[] rdmw = { Brick.RIGHT, Brick.DOWN };
|
|
short[] lumw = { Brick.UP, Brick.LEFT };
|
|
short[] ldmw = { Brick.LEFT, Brick.DOWN };
|
|
short[] urmw = { Brick.UP, Brick.RIGHT };
|
|
|
|
short[] allmoves = { LEFT,UP,RIGHT, DOWN };
|
|
for (int x = 0; x < getWidth() -1 ; x ++)
|
|
{
|
|
for (int y = 0; y < getHeight() -1 ; y ++)
|
|
{
|
|
short walls = 0;
|
|
short ulwalls = (short) (t[x][y] >> FLAGLENGTH);
|
|
short rdwalls = (short) (t[x+1][y+1] >> FLAGLENGTH);
|
|
short ldwalls = (short) (t[x][y+1] >> FLAGLENGTH);
|
|
short ruwalls = (short) (t[x+1][y] >> FLAGLENGTH);
|
|
|
|
// invert moves to walls
|
|
for (int i = 0 ; i < 2; i ++)
|
|
{
|
|
if ((ulwalls & rdmw[i] ) != 0) {
|
|
walls |= lumw[i];
|
|
}
|
|
if ((rdwalls & lumw[i] ) != 0 ) {
|
|
walls |= rdmw[i];
|
|
}
|
|
if ((ldwalls & urmw[i] ) != 0) {
|
|
walls |= ldmw[i];
|
|
}
|
|
if ((ruwalls & ldmw[i] ) != 0 ) {
|
|
walls |= urmw[i];
|
|
}
|
|
}
|
|
// convert walls to moves, walls are actualy where there is no path : XOR to invert.
|
|
short moves = (short) ((walls ^ ( Brick.RIGHT | Brick.LEFT | Brick.UP | Brick.DOWN )) << FLAGLENGTH);
|
|
short finalmoves = 0;
|
|
if ( moves == 0 )
|
|
{
|
|
finalmoves = 0;
|
|
}
|
|
else {
|
|
for (short m : allmoves) {
|
|
if ((moves & m) != 0) {
|
|
finalmoves |= m;
|
|
}
|
|
}
|
|
}
|
|
newt[x+delta][y+delta] = finalmoves;
|
|
}
|
|
}
|
|
width = newwidth;
|
|
height = newheight;
|
|
t = newt;
|
|
|
|
return true;
|
|
}
|
|
|
|
public boolean obfuscated_reverse(boolean grow)
|
|
{
|
|
// use current moves as walls ... and rebuild new direction from it.
|
|
|
|
int deltasize = grow ? 1 : -1;
|
|
int newwidth = getWidth() + deltasize;
|
|
int newheight = getHeight() + deltasize;
|
|
int delta = grow ? 1 : 0;
|
|
|
|
if ( ( newwidth < 1) || (newheight < 1 ) )
|
|
{
|
|
System.err.println("Can't reverse, too small");
|
|
return false;
|
|
}
|
|
short newt[][] = new short[newwidth][newheight];
|
|
|
|
short[][] rdul = {
|
|
{ Brick.RIGHT, Brick.DOWN },
|
|
{ Brick.UP, Brick.LEFT }};
|
|
short[] allmoves = { LEFT,UP,RIGHT, DOWN };
|
|
|
|
for (int x = 0; x < getWidth() -1 ; x ++)
|
|
{
|
|
for (int y = 0; y < getHeight() -1 ; y ++)
|
|
{
|
|
short walls = 0;
|
|
// invert moves to walls
|
|
|
|
// OBFUSCATED UNDEBUGGABLE VERSION
|
|
for (int i = 0 ; i < 2; i ++)
|
|
{
|
|
for( int a=0 ; a < 2; a ++) {
|
|
for (int c = 0; c < 2; c ++) {
|
|
int d = a + c;
|
|
int f = a * c;
|
|
int b = d - 2 * f;
|
|
int g = c * i;
|
|
int e = c * b - g;
|
|
if (((short) (t[x + a][y + b] >> FLAGLENGTH) & rdul[d - f - g ][i + f - g]) != 0) {
|
|
walls |= rdul[1 - b + e][i + e];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// convert walls to moves, walls are actualy where there is no path : XOR to invert.
|
|
short moves = (short) ((walls ^ ( Brick.RIGHT | Brick.LEFT | Brick.UP | Brick.DOWN )) << FLAGLENGTH);
|
|
short finalmoves = 0;
|
|
if ( moves == 0 )
|
|
{
|
|
finalmoves = 0;
|
|
}
|
|
else {
|
|
for (short m : allmoves) {
|
|
if ((moves & m) != 0) {
|
|
finalmoves |= m;
|
|
}
|
|
}
|
|
}
|
|
newt[x+delta][y+delta] = finalmoves;
|
|
}
|
|
}
|
|
width = newwidth;
|
|
height = newheight;
|
|
t = newt;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
}
|