add Save Text support and rework package
- move maze within package maze
This commit is contained in:
119
java/org/artisanlogiciel/games/maze/Brick.java
Normal file
119
java/org/artisanlogiciel/games/maze/Brick.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
/*
|
||||
|
||||
2x2 Tile to represent a labyrinth position with some walls
|
||||
|
||||
this is 2x2 downright most part of 3x3 centered tile.
|
||||
|
||||
center right
|
||||
down downright
|
||||
|
||||
ab
|
||||
cd
|
||||
|
||||
a is 'H' or '.' or ' '
|
||||
bcd is within 'H' or ' '
|
||||
|
||||
*/
|
||||
public class Brick
|
||||
{
|
||||
|
||||
public final static short LEFT = 1;
|
||||
public final static short DOWN = 2;
|
||||
public final static short RIGHT = 4;
|
||||
public final static short UP = 8;
|
||||
public final static short ENTRY = 16;
|
||||
public final static short GOAL = 32;
|
||||
|
||||
char a;
|
||||
char b;
|
||||
char c;
|
||||
char d;
|
||||
short walls; // according to LabyModel.getWalls();
|
||||
|
||||
public Brick(char center, char right, char down, char downright, short walls)
|
||||
{
|
||||
a = center;
|
||||
b = right;
|
||||
c = down;
|
||||
d = downright;
|
||||
this.walls = walls;
|
||||
}
|
||||
|
||||
public Brick(String up, String low, short walls)
|
||||
{
|
||||
a = up.charAt(0);
|
||||
b = up.charAt(1);
|
||||
c = low.charAt(0);
|
||||
d = low.charAt(1);
|
||||
this.walls = walls;
|
||||
}
|
||||
|
||||
private boolean check()
|
||||
{
|
||||
// Not Yet Implemented
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getUpString()
|
||||
{
|
||||
return "" + a + b;
|
||||
}
|
||||
|
||||
public String getLowString()
|
||||
{
|
||||
return "" + c + d;
|
||||
}
|
||||
|
||||
public static String getDirLine()
|
||||
{
|
||||
char dir[] = new char[16];
|
||||
String s = "";
|
||||
|
||||
/*
|
||||
* dir[LEFT | DOWN | RIGHT | UP]='O'; dir[LEFT | DOWN | RIGHT]='U';
|
||||
* dir[LEFT | UP | RIGHT]='M'; dir[LEFT | UP | DOWN]='['; dir[RIGHT | UP
|
||||
* | DOWN]=']'; dir[UP | DOWN]='='; dir[LEFT | RIGHT]='|'; dir[RIGHT |
|
||||
* DOWN]='J'; dir[LEFT | DOWN]='L'; dir [LEFT | UP]='T'; dir[UP |
|
||||
* RIGHT]='7'; dir[LEFT] = '!'; dir[RIGHT] ='|'; dir[DOWN]= '_';
|
||||
* dir[UP]= '¨'; dir[0]=' ';
|
||||
*/
|
||||
|
||||
dir[LEFT | DOWN | RIGHT | UP] = 'O';
|
||||
dir[LEFT | DOWN | RIGHT] = 'U';
|
||||
dir[LEFT | UP | RIGHT] = 'M';
|
||||
dir[LEFT | UP | DOWN] = '[';
|
||||
dir[RIGHT | UP | DOWN] = ']';
|
||||
dir[UP | DOWN] = '=';
|
||||
dir[LEFT | RIGHT] = 226;
|
||||
dir[RIGHT | DOWN] = 'J';
|
||||
dir[LEFT | DOWN] = 'L';
|
||||
dir[LEFT | UP] = 169;
|
||||
dir[UP | RIGHT] = 170;
|
||||
dir[LEFT] = 173;
|
||||
dir[RIGHT] = '|';
|
||||
dir[DOWN] = '_';
|
||||
dir[UP] = '¨';
|
||||
dir[0] = ' ';
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
s = s + dir[i];
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
}
|
||||
|
||||
public char getChar()
|
||||
{
|
||||
// return getDirLine().charAt(walls & 0xFFF0);
|
||||
return getDirLine().charAt(walls);
|
||||
}
|
||||
|
||||
public short getWalls()
|
||||
{
|
||||
return walls;
|
||||
}
|
||||
}
|
||||
120
java/org/artisanlogiciel/games/maze/LabyMap.java
Normal file
120
java/org/artisanlogiciel/games/maze/LabyMap.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* model with Bricks, based on walls
|
||||
*/
|
||||
public class LabyMap implements WallsProvider
|
||||
{
|
||||
|
||||
Brick[][] tileMap;
|
||||
LinkedList<Position> exits;
|
||||
|
||||
public LabyMap(Brick[][] tileMap, LinkedList<Position> exits)
|
||||
{
|
||||
this.tileMap = tileMap;
|
||||
this.exits = exits;
|
||||
}
|
||||
|
||||
Brick getAt(int x, int y)
|
||||
{
|
||||
return tileMap[x][y];
|
||||
}
|
||||
|
||||
public short getWalls(int x, int y)
|
||||
{
|
||||
return getAt(x, y).getWalls();
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return tileMap[0].length;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return tileMap.length;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
int entryX = -1;
|
||||
int entryY = -1;
|
||||
for (Position exit : exits)
|
||||
{
|
||||
if (exit.getX() == -1)
|
||||
{
|
||||
entryY = exit.getY();
|
||||
}
|
||||
if (exit.getY() == -1)
|
||||
{
|
||||
entryX = exit.getX();
|
||||
}
|
||||
}
|
||||
String laby = "H";
|
||||
for (int x = 0; x < tileMap.length; x++)
|
||||
{
|
||||
if (entryX == x)
|
||||
{
|
||||
laby += " H";
|
||||
}
|
||||
else
|
||||
{
|
||||
laby += "HH";
|
||||
}
|
||||
}
|
||||
laby += "\n";
|
||||
for (int y = 0; y < tileMap[0].length; y++)
|
||||
{
|
||||
if (entryY == y)
|
||||
{
|
||||
laby += ">";
|
||||
}
|
||||
else
|
||||
{
|
||||
laby += "H";
|
||||
}
|
||||
for (int x = 0; x < tileMap.length; x++)
|
||||
{
|
||||
laby += tileMap[x][y].getUpString();
|
||||
}
|
||||
laby += "\n";
|
||||
laby += "H";
|
||||
for (int x = 0; x < tileMap.length; x++)
|
||||
{
|
||||
laby += tileMap[x][y].getLowString();
|
||||
}
|
||||
laby += "\n";
|
||||
}
|
||||
return laby;
|
||||
}
|
||||
|
||||
public String toShortString()
|
||||
{
|
||||
int entryX = -1;
|
||||
int entryY = -1;
|
||||
for (Position exit : exits)
|
||||
{
|
||||
if (exit.getX() == -1)
|
||||
{
|
||||
entryY = exit.getY();
|
||||
}
|
||||
if (exit.getY() == -1)
|
||||
{
|
||||
entryX = exit.getX();
|
||||
}
|
||||
}
|
||||
String laby = "";
|
||||
for (int y = 0; y < tileMap[0].length; y++)
|
||||
{
|
||||
for (int x = 0; x < tileMap.length; x++)
|
||||
{
|
||||
laby += "" + tileMap[x][y].getChar();
|
||||
}
|
||||
laby += "\n";
|
||||
}
|
||||
return laby;
|
||||
}
|
||||
|
||||
}
|
||||
752
java/org/artisanlogiciel/games/maze/LabyModel.java
Normal file
752
java/org/artisanlogiciel/games/maze/LabyModel.java
Normal file
@@ -0,0 +1,752 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
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 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
|
||||
// ?
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
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 heigh, 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) {
|
||||
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, 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 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)) {
|
||||
if ((t[newx][newy]) == CLEAR) {
|
||||
freeDirection.add(Short.valueOf(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;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* is there no wall in that direction ?
|
||||
**/
|
||||
public boolean canMoveInDirection(int x, int y, short direction) {
|
||||
// tried to replace by but does not work ( can't go back ...).
|
||||
// return ! hasWallInDirection(x,y, (short) (direction << FLAGLENGTH));
|
||||
return ((getWalls(x, y) & direction ) == 0);
|
||||
}
|
||||
|
||||
}
|
||||
45
java/org/artisanlogiciel/games/maze/Main.java
Normal file
45
java/org/artisanlogiciel/games/maze/Main.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
import org.artisanlogiciel.games.maze.solve.SolvingModel;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
public MazeParamEditor editor() {
|
||||
MazeParamEditor editor = new MazeParamEditor(null);
|
||||
System.out.println("enter width height and maxdepth");
|
||||
Scanner console = new Scanner(System.in);
|
||||
editor.read(console);
|
||||
return editor;
|
||||
}
|
||||
|
||||
public LabyMap generate2(MazeParamEditor params) {
|
||||
params.setSeed(1024L);
|
||||
SolvingModel model = new SolvingModel(params);
|
||||
model.generateWithEntry(0, 0);
|
||||
|
||||
final int width = params.getWidth();
|
||||
final int height = params.getHeight();
|
||||
LinkedList<Position> exits = new LinkedList<Position>();
|
||||
model.addEntryOrExit(-1, 0);
|
||||
model.addEntryOrExit(width, height - 1);
|
||||
System.out.println(model.toLabyMap().toString());
|
||||
if (!model.check()) {
|
||||
System.out.println("Check failed");
|
||||
}
|
||||
model.debugOut();
|
||||
model.resolve(width - 1, height - 1, null);
|
||||
LabyMap labyMap = model.toLabyMap();
|
||||
return labyMap;
|
||||
}
|
||||
|
||||
public static void main(String pArgs[]) {
|
||||
Main m = new Main();
|
||||
MazeParamEditor editor = m.editor();
|
||||
LabyMap map = m.generate2(editor);
|
||||
System.out.println(map.toShortString());
|
||||
System.out.println(map.toString());
|
||||
System.out.println(Brick.getDirLine());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
/**
|
||||
* MazeCreationListener
|
||||
**/
|
||||
public interface MazeCreationListener
|
||||
{
|
||||
void changed(Position cornerleft, Position cornerright, WallsProvider provider);
|
||||
}
|
||||
70
java/org/artisanlogiciel/games/maze/MazeParamEditor.java
Normal file
70
java/org/artisanlogiciel/games/maze/MazeParamEditor.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
MazeParamEditor to edit Maze Parameters ( current impl in console / Scanner )
|
||||
**/
|
||||
class MazeParamEditor implements MazeParams
|
||||
{
|
||||
long seed;
|
||||
int width;
|
||||
int height;
|
||||
int maxdepth;
|
||||
File labdir;
|
||||
String name;
|
||||
|
||||
public MazeParamEditor(File saveDir)
|
||||
{
|
||||
name = null;
|
||||
labdir = saveDir;
|
||||
}
|
||||
|
||||
public void read(Scanner console)
|
||||
{
|
||||
width = console.nextInt();
|
||||
height = console.nextInt();
|
||||
maxdepth = console.nextInt();
|
||||
}
|
||||
|
||||
public long getSeed()
|
||||
{
|
||||
return seed;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getMaxDepth()
|
||||
{
|
||||
return maxdepth;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
name = "lab" + width + "x" + height;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setSeed(long seed)
|
||||
{
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public File getSaveDir()
|
||||
{
|
||||
return labdir;
|
||||
}
|
||||
}
|
||||
|
||||
29
java/org/artisanlogiciel/games/maze/MazeParams.java
Normal file
29
java/org/artisanlogiciel/games/maze/MazeParams.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* MazeParams contains parameters for a Maze generation
|
||||
**/
|
||||
public interface MazeParams
|
||||
{
|
||||
/** currently seed of java.util.random **/
|
||||
public long getSeed();
|
||||
|
||||
public int getWidth();
|
||||
|
||||
public int getHeight();
|
||||
|
||||
public int getMaxDepth();
|
||||
|
||||
/**
|
||||
* @return name of Maze, used for creating filename at saving time
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* @return directory owning where to save a maze
|
||||
*/
|
||||
public File getSaveDir();
|
||||
|
||||
}
|
||||
63
java/org/artisanlogiciel/games/maze/MazeParamsFixed.java
Normal file
63
java/org/artisanlogiciel/games/maze/MazeParamsFixed.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MazeParamsFixed implements MazeParams {
|
||||
long seed;
|
||||
int width;
|
||||
int height;
|
||||
int maxdepth;
|
||||
File labdir;
|
||||
String name;
|
||||
|
||||
public MazeParamsFixed() {
|
||||
}
|
||||
|
||||
public MazeParamsFixed(MazeParams params) {
|
||||
setParams(params.getSaveDir(), params.getWidth(), params.getHeight(), params.getMaxDepth());
|
||||
}
|
||||
|
||||
public void setParams(File saveDir, int W, int H, int MD) {
|
||||
labdir = saveDir;
|
||||
width = W;
|
||||
height = H;
|
||||
maxdepth = MD;
|
||||
}
|
||||
|
||||
public MazeParamsFixed(File saveDir, int W, int H, int MD, long seed) {
|
||||
name = null;
|
||||
setParams(saveDir, W, H, MD);
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getMaxDepth() {
|
||||
return maxdepth;
|
||||
}
|
||||
|
||||
public void setName(String n) {
|
||||
name = n;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
name = "lab" + width + "x" + height;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public File getSaveDir() {
|
||||
return labdir;
|
||||
}
|
||||
}
|
||||
58
java/org/artisanlogiciel/games/maze/Position.java
Normal file
58
java/org/artisanlogiciel/games/maze/Position.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
/** position of a cell with maze */
|
||||
public class Position
|
||||
{
|
||||
private int x, y;
|
||||
private int depth;
|
||||
|
||||
public Position(int x, int y, int depth)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.depth = depth;
|
||||
}
|
||||
|
||||
public Position(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.depth = 0;
|
||||
}
|
||||
|
||||
public int getX()
|
||||
{
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public int getY()
|
||||
{
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public int getDepth()
|
||||
{
|
||||
return depth;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "(" + x + "," + y + ")" + "/" + depth;
|
||||
}
|
||||
|
||||
public boolean equals(Object other)
|
||||
{
|
||||
// disregards depth
|
||||
if (other instanceof Position )
|
||||
{
|
||||
Position p = (Position) other;
|
||||
return (p.getX() == x) && (p.getY() == y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
27
java/org/artisanlogiciel/games/maze/WallsProvider.java
Normal file
27
java/org/artisanlogiciel/games/maze/WallsProvider.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.artisanlogiciel.games.maze;
|
||||
|
||||
/**
|
||||
* WallsProvider provide a Walls representation
|
||||
**/
|
||||
public interface WallsProvider
|
||||
{
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
/**
|
||||
* See Brick
|
||||
*
|
||||
* Will set bits :
|
||||
* 3 2 1 0
|
||||
* (8)(4)(2)(1)
|
||||
* ^ > v <
|
||||
* U R D L
|
||||
* p i o e
|
||||
* g w f
|
||||
* h n t
|
||||
* t
|
||||
**/
|
||||
short getWalls(int x, int y);
|
||||
}
|
||||
@@ -27,9 +27,12 @@ import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import org.artisanlogiciel.games.*;
|
||||
import org.artisanlogiciel.games.maze.*;
|
||||
import org.artisanlogiciel.games.maze.persist.MazePersistRaw;
|
||||
import org.artisanlogiciel.games.maze.solve.DirectionPosition;
|
||||
import org.artisanlogiciel.games.maze.solve.MazeResolutionListener;
|
||||
import org.artisanlogiciel.games.maze.solve.SolvingModel;
|
||||
import org.artisanlogiciel.games.stl.Maze3dParams;
|
||||
import org.artisanlogiciel.games.stl.Wall3d;
|
||||
import org.artisanlogiciel.games.stl.Wall3dStream;
|
||||
import org.artisanlogiciel.util.UTF8Control;
|
||||
|
||||
@@ -143,8 +146,12 @@ public class Display extends JFrame {
|
||||
}
|
||||
|
||||
void resolve() {
|
||||
|
||||
SolvingModel solving = new SolvingModel(model);
|
||||
// should transform current model to be a SolvingModel
|
||||
model = solving;
|
||||
model.reset();
|
||||
model.resolve(model.getWidth() - 1, model.getHeight() - 1, maze);
|
||||
solving.resolve(solving.getWidth() - 1, solving.getHeight() - 1, maze);
|
||||
}
|
||||
|
||||
void goNorth() {
|
||||
@@ -311,6 +318,21 @@ public class Display extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
void saveText() {
|
||||
File outfile = getFileForExtension("txt");
|
||||
writeSentence("Saving to " + outfile + " ...");
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(new FileOutputStream(outfile));
|
||||
out.write(model.toLabyMap().toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
writeSentence("... Done.");
|
||||
} catch (IOException io) {
|
||||
io.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addStatus(String pStatus)
|
||||
{
|
||||
statusField.setText(pStatus);
|
||||
@@ -414,6 +436,16 @@ public class Display extends JFrame {
|
||||
}
|
||||
};
|
||||
saveStlButton.addActionListener(saveStlAction);
|
||||
final JButton saveTextButton = new JButton(labels.getString("save") + " txt");
|
||||
Action saveTextAction = new AbstractAction() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
//
|
||||
addStatus("save txt");
|
||||
setMazeName(saveName.getText());
|
||||
saveText();
|
||||
}
|
||||
};
|
||||
saveTextButton.addActionListener(saveTextAction);
|
||||
|
||||
stlsettings = new Maze3dSettings(new Maze3dParams());
|
||||
|
||||
@@ -425,6 +457,7 @@ public class Display extends JFrame {
|
||||
saveMenu.add(stlsettings);
|
||||
saveMenu.add(saveStlButton);
|
||||
saveMenu.add(saveImcButton);
|
||||
saveMenu.add(saveTextButton);
|
||||
|
||||
return saveMenu;
|
||||
|
||||
@@ -601,7 +634,7 @@ public class Display extends JFrame {
|
||||
FileInputStream inputStream = null;
|
||||
try {
|
||||
inputStream = new FileInputStream(infile);
|
||||
model = new LabyModel("raw", inputStream);
|
||||
model = new MazePersistRaw().parseInputStream("raw",inputStream);
|
||||
} catch (IOException io) {
|
||||
io.printStackTrace(System.err);
|
||||
statusField.setText("[ERROR] Can't load " + infile.getAbsolutePath());
|
||||
@@ -851,55 +884,43 @@ public class Display extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void proceed()
|
||||
{
|
||||
// should redraw ...
|
||||
invalidate();
|
||||
repaint();
|
||||
checkExit();
|
||||
}
|
||||
void goNorth() {
|
||||
resetResolution();
|
||||
if ((map.getWalls(sX, sY) & Brick.UP) == 0) {
|
||||
if (map.canMoveInDirection(sX, sY,Brick.UP)) {
|
||||
sY = sY - 1;
|
||||
// should redraw ...
|
||||
invalidate();
|
||||
repaint();
|
||||
|
||||
checkExit();
|
||||
proceed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void goSouth() {
|
||||
resetResolution();
|
||||
if ((map.getWalls(sX, sY) & Brick.DOWN) == 0) {
|
||||
if (map.canMoveInDirection(sX, sY,Brick.DOWN)) {
|
||||
sY = sY + 1;
|
||||
// should redraw ...
|
||||
invalidate();
|
||||
repaint();
|
||||
|
||||
checkExit();
|
||||
proceed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void goEast() {
|
||||
resetResolution();
|
||||
if ((map.getWalls(sX, sY) & Brick.RIGHT) == 0) {
|
||||
if (map.canMoveInDirection(sX, sY, Brick.RIGHT)) {
|
||||
sX = sX + 1;
|
||||
// should redraw ...
|
||||
invalidate();
|
||||
repaint();
|
||||
|
||||
checkExit();
|
||||
proceed();
|
||||
}
|
||||
}
|
||||
|
||||
void goWest() {
|
||||
resetResolution();
|
||||
if ((map.getWalls(sX, sY) & Brick.LEFT) == 0) {
|
||||
if (map.canMoveInDirection(sX, sY, Brick.LEFT)) {
|
||||
sX = sX - 1;
|
||||
// should redraw ...
|
||||
invalidate();
|
||||
repaint();
|
||||
|
||||
checkExit();
|
||||
proceed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1113,7 +1134,8 @@ public class Display extends JFrame {
|
||||
addStatus("Saving to " + outfile + " ...");
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(outfile);
|
||||
model.streamOut("raw", out);
|
||||
MazePersistRaw persist = new MazePersistRaw(model);
|
||||
persist.streamOut("raw", out);
|
||||
out.flush();
|
||||
out.close();
|
||||
addStatus("... Done.");
|
||||
@@ -1135,7 +1157,7 @@ public class Display extends JFrame {
|
||||
|
||||
if ((pArgs.length > 0) && (pArgs[0].length() > 0)) {
|
||||
try {
|
||||
model = new LabyModel("raw", new FileInputStream(pArgs[0]));
|
||||
model = new MazePersistRaw().parseInputStream("raw",new FileInputStream(pArgs[0]));
|
||||
} catch (IOException io) {
|
||||
io.printStackTrace(System.err);
|
||||
System.exit(1);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.artisanlogiciel.games.maze.gui;
|
||||
|
||||
import org.artisanlogiciel.games.MazeParams;
|
||||
import org.artisanlogiciel.games.stl.Maze3dParams;
|
||||
import org.artisanlogiciel.games.stl.Wall3d;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.artisanlogiciel.games.maze.gui;
|
||||
|
||||
import org.artisanlogiciel.games.MazeParams;
|
||||
import org.artisanlogiciel.games.MazeParamsFixed;
|
||||
import org.artisanlogiciel.games.maze.MazeParams;
|
||||
import org.artisanlogiciel.games.maze.MazeParamsFixed;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.artisanlogiciel.games.maze.persist;
|
||||
|
||||
import org.artisanlogiciel.games.maze.LabyModel;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class MazePersistRaw
|
||||
{
|
||||
private LabyModel model;
|
||||
|
||||
public MazePersistRaw(LabyModel model)
|
||||
{
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public MazePersistRaw()
|
||||
{
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
|
||||
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(model.getWidth());
|
||||
dataOut.writeInt(model.getHeight());
|
||||
dataOut.flush();
|
||||
for (int y = 0; y < model.getHeight(); y++) {
|
||||
for (int x = 0; x < model.getWidth(); x++) {
|
||||
dataOut.writeShort(model.getPath(x,y));
|
||||
}
|
||||
}
|
||||
dataOut.flush();
|
||||
} else {
|
||||
throw new IOException("Format " + pFormat + " Not yet implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public LabyModel parseInputStream(String pFormat, InputStream pIn) throws IOException {
|
||||
if ((pFormat == null) || (pFormat.equals("raw"))) {
|
||||
// maxdepth is unset then unmodified
|
||||
byte[] header = new byte[4];
|
||||
DataInputStream in = new DataInputStream(pIn);
|
||||
in.read(header);
|
||||
int rwidth = in.readInt();
|
||||
int rheight = in.readInt();
|
||||
if ((rwidth > 0) && (rheight > 0)) {
|
||||
int width = rwidth;
|
||||
int height = rheight;
|
||||
// SHOULD CHECK max width and max height...
|
||||
// CLEAR == 0 and array is initialized with 0s
|
||||
short[][] t = new short[width][height];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
t[x][y] = in.readShort();
|
||||
}
|
||||
}
|
||||
model= new LabyModel(width, height, t);
|
||||
return model;
|
||||
} else {
|
||||
throw new IOException("Invalid header for width and height");
|
||||
}
|
||||
// should be at end of stream ? Not necessary can stream multiple
|
||||
// labs ( or tiling ).
|
||||
} else {
|
||||
throw new IOException("Format " + pFormat + " Not yet implemented.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.artisanlogiciel.games.maze.solve;
|
||||
|
||||
import org.artisanlogiciel.games.maze.LabyModel;
|
||||
import org.artisanlogiciel.games.maze.Position;
|
||||
|
||||
/**
|
||||
* DirectionPosition
|
||||
* <p>
|
||||
* record direction and position ( direction as defined in LabyModel ).
|
||||
**/
|
||||
public class DirectionPosition {
|
||||
private short direction;
|
||||
private Position position;
|
||||
|
||||
// (direction,position)
|
||||
public DirectionPosition(short d, Position p) {
|
||||
direction = d;
|
||||
position = p;
|
||||
}
|
||||
|
||||
public short getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public void setDirection(short d) {
|
||||
direction = d;
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (position != null) {
|
||||
return position.toString();
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
// create a new DirectionPosition from this using one possible direction.
|
||||
public DirectionPosition moveToAdjacentDirection() {
|
||||
short pointingdirection = 0;
|
||||
Position p = null;
|
||||
if (LabyModel.isFlagSet(direction, LabyModel.RIGHT)) {
|
||||
p = new Position(position.getX() + 1, position.getY());
|
||||
pointingdirection |= LabyModel.LEFT;
|
||||
} else if (LabyModel.isFlagSet(direction, LabyModel.LEFT)) {
|
||||
p = new Position(position.getX() - 1, position.getY());
|
||||
pointingdirection |= LabyModel.RIGHT;
|
||||
} else if (LabyModel.isFlagSet(direction, LabyModel.UP)) {
|
||||
p = new Position(position.getX(), position.getY() - 1);
|
||||
pointingdirection |= LabyModel.DOWN;
|
||||
} else if (LabyModel.isFlagSet(direction, LabyModel.DOWN)) {
|
||||
p = new Position(position.getX(), position.getY() + 1);
|
||||
pointingdirection |= LabyModel.UP;
|
||||
} else {
|
||||
p = position;
|
||||
}
|
||||
|
||||
return new DirectionPosition((short) pointingdirection, p);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.artisanlogiciel.games.maze.solve;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* MazeResolutionListener used as interface between resolver and (mostly) GUI
|
||||
**/
|
||||
public interface MazeResolutionListener
|
||||
{
|
||||
|
||||
boolean notifySearch(DirectionPosition pPosition);
|
||||
|
||||
void notifySearchError(String error);
|
||||
|
||||
void notifyCompletion(LinkedList<DirectionPosition> solvedPath);
|
||||
|
||||
}
|
||||
171
java/org/artisanlogiciel/games/maze/solve/SolvingModel.java
Normal file
171
java/org/artisanlogiciel/games/maze/solve/SolvingModel.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package org.artisanlogiciel.games.maze.solve;
|
||||
|
||||
import org.artisanlogiciel.games.maze.LabyModel;
|
||||
import org.artisanlogiciel.games.maze.MazeParams;
|
||||
import org.artisanlogiciel.games.maze.Position;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class SolvingModel
|
||||
extends LabyModel
|
||||
{
|
||||
|
||||
public SolvingModel(MazeParams params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
public SolvingModel(LabyModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve this labrynth using internal representation
|
||||
* initial (x,y) is exit, will return list of positions from start (0,0) to end (x,y)
|
||||
**/
|
||||
public LinkedList<DirectionPosition> resolve(int x, int y, MazeResolutionListener rlistener) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
long safeguard = width * height;
|
||||
int newx = 0;
|
||||
int newy = 0;
|
||||
|
||||
resetResolving();
|
||||
|
||||
// list of alternate paths
|
||||
LinkedList<LinkedList<DirectionPosition>> altpath = new LinkedList<>();
|
||||
|
||||
// list of positions from start to end
|
||||
LinkedList<DirectionPosition> backpath = new LinkedList<DirectionPosition>();
|
||||
// position that point to (x,y).
|
||||
DirectionPosition found = new DirectionPosition((short) 0, new Position(x, y));
|
||||
// entry
|
||||
Position entry = new Position(0, 0);
|
||||
|
||||
while (!found.getPosition().equals(entry)) {
|
||||
Position last = found.getPosition();
|
||||
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 = last.getX() + delta;
|
||||
newy = last.getY();
|
||||
pointingdirection |= HORIZONTAL;
|
||||
} else {
|
||||
newy = last.getY() + delta;
|
||||
newx = last.getX();
|
||||
pointingdirection |= VERTICAL;
|
||||
}
|
||||
|
||||
// internal GUARD.
|
||||
if (!isFlagSet(reversedirection, pointingdirection)) {
|
||||
System.out.println("[FATAL] Internal ERROR. Please check AllDirections order "
|
||||
+ (reversedirection & pointingdirection) + " " + pointingdirection);
|
||||
return backpath;
|
||||
}
|
||||
|
||||
Position p = new Position(newx, newy);
|
||||
if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height)) {
|
||||
if (isFlagSet(getCell(p), reversedirection)) {
|
||||
if (found != null) {
|
||||
// there is already a potential solution in adjacent cell of last.
|
||||
System.out.println("alternate " + p + " from " + last + "/" + safeguard);
|
||||
// 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 (!isFlagSet(getCell(p), SOLVED)) {
|
||||
LinkedList<DirectionPosition> cp = new LinkedList<DirectionPosition>(backpath);
|
||||
DirectionPosition altfound = new DirectionPosition(reversedirection, p);
|
||||
cp.addFirst(altfound);
|
||||
altpath.addLast(cp);
|
||||
rlistener.notifySearchError("record alternate path " + p.toString());
|
||||
} else {
|
||||
// this was already solved, might be a loop.
|
||||
if (rlistener != null) {
|
||||
rlistener.notifySearchError("Loop " + last.toString() + " has two parents " + found.toString() + " "
|
||||
+ p.toString());
|
||||
}
|
||||
// continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isFlagSet(getCell(p), SOLVED)) {
|
||||
// this is first potential solution in adjacent cell of last.
|
||||
System.out.println("check " + p + " from " + last + "/" + safeguard);
|
||||
|
||||
found = new DirectionPosition(reversedirection, p);
|
||||
if (rlistener != null) {
|
||||
rlistener.notifySearch(found);
|
||||
}
|
||||
} else {
|
||||
// was already solved.
|
||||
System.out.println("already solved " + p + " from " + last + "/" + safeguard);
|
||||
}
|
||||
}
|
||||
// support multiple pathes
|
||||
} else {
|
||||
System.out.println("not reachable " + p + " from " + last + "/" + safeguard);
|
||||
}
|
||||
} else {
|
||||
System.out.println("p outofbounds " + p + "/" + safeguard);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (!altpath.isEmpty()) {
|
||||
rlistener.notifySearchError("No path found BUT ALTPATH !");
|
||||
}
|
||||
rlistener.notifySearchError("No path found !");
|
||||
break;
|
||||
}
|
||||
// System.out.println(found);
|
||||
if (isFlagSet(getCell(found.getPosition()), SOLVED)) {
|
||||
System.out.println("[INFO] position already solved" + found.toString() + " *length:" + backpath.size());
|
||||
} else {
|
||||
updateCell(found.getPosition(), 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user