add project files
This commit is contained in:
parent
90e24206fb
commit
1377c41734
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* A labyrinth generated using the depth-first algorithm
|
||||
* (www.astrolog.org/labyrnth/algrithm.htm), with a start point and end point
|
||||
* for a search and with a display (unless too large) as ASCII graphics and
|
||||
* Swing graphics.
|
||||
* Source of labyrinth representation and ASCII output generation:
|
||||
* http://rosettacode.org/wiki/Maze#Java
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
public final class Labyrinth implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Serialized state of a labyrinth with size, passages, start and end
|
||||
* (without search state) and with all information defining its graphic
|
||||
* and textual display.
|
||||
*/
|
||||
|
||||
private final int width; // total number of cells in x direction
|
||||
private final int height; // total number of cells in y direction
|
||||
private final Point start; // starting point of the search
|
||||
private final Point end; // end point of the search
|
||||
|
||||
private final byte[][] passages;
|
||||
/*
|
||||
* Each array element represents a cell in the labyrinth with the passages possible from
|
||||
* this cell. Its four least significant bits are interpreted as one flag for each direction
|
||||
* (see enum Direction for which bit means which direction) indicating whether
|
||||
* there is a passage from this cell in that direction (note that passages
|
||||
* and walls are not cells, but represented indirectly by these flags).
|
||||
* Initially all cells are 0, i.e. have no passage from them (i.e. surrounded
|
||||
* by walls on all their four sides). Note that two-way passages appear as opposite
|
||||
* bits in both the source and destination cell; thus, this data structure supports
|
||||
* one-way passages, too, by setting a bit in the source cell only (however, one-way
|
||||
* passages are not used in PAR).
|
||||
*/
|
||||
|
||||
// When generating the labyrinth and considering whether to create a passage to some neighbor cell, create a
|
||||
// passage to a cell that is already accessible on another path (i.e. create a cycle) with this probability:
|
||||
private static final double CYCLE_CREATION_PROBABILITY = 0.01;
|
||||
|
||||
private static final int CELL_PX = 10; // width and length of the labyrinth cells in pixels
|
||||
private static final int HALF_WALL_PX = 2; // thickness/2 of the labyrinth walls in pixels
|
||||
// labyrinths with more pixels than this (in one or both directions) will not be graphically displayed:
|
||||
private static final int MAX_PX_TO_DISPLAY = 1000;
|
||||
|
||||
public Labyrinth(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// Always start in the center of the labyrinth:
|
||||
start = new Point(width/2, height/2);
|
||||
|
||||
// Randomly pick a cell on the boundary as the end point:
|
||||
int endIndex = (int)((2*width + 2*height) * Math.random());
|
||||
int endX;
|
||||
int endY;
|
||||
// Try the four edges of the grid, starting at the upper edge,
|
||||
// proceeding clockwise to the left edge:
|
||||
if (endIndex < width) { // upper edge
|
||||
endX = endIndex;
|
||||
endY = 0;
|
||||
} else {
|
||||
if (endIndex < width + height) { // right edge
|
||||
endX = width-1;
|
||||
endY = endIndex - width;
|
||||
} else {
|
||||
if (endIndex < 2*width + height) { // lower edge
|
||||
endX = endIndex - width - height;
|
||||
endY = height-1;
|
||||
} else { // left edge
|
||||
endX = 0;
|
||||
endY = endIndex - 2*width - height;
|
||||
}
|
||||
}
|
||||
}
|
||||
end = new Point(endX, endY);
|
||||
|
||||
passages = new byte[width][height]; // initially all 0 (see comment at declaration of passages)
|
||||
makePassages();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public Point getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public boolean hasPassage(Point from, Direction directionToNeighbor) {
|
||||
return contains(from) && (passages[from.getX()][from.getY()] & directionToNeighbor.bit) != 0;
|
||||
}
|
||||
|
||||
public boolean hasPassage(Point from, Point to) {
|
||||
if (!contains(from) || !contains(to)) {
|
||||
return false;
|
||||
}
|
||||
if (from.getNeighbor(Direction.N).equals(to))
|
||||
return (passages[from.getX()][from.getY()] & Direction.N.bit) != 0;
|
||||
if (from.getNeighbor(Direction.S).equals(to))
|
||||
return (passages[from.getX()][from.getY()] & Direction.S.bit) != 0;
|
||||
if (from.getNeighbor(Direction.E).equals(to))
|
||||
return (passages[from.getX()][from.getY()] & Direction.E.bit) != 0;
|
||||
if (from.getNeighbor(Direction.W).equals(to))
|
||||
return (passages[from.getX()][from.getY()] & Direction.W.bit) != 0;
|
||||
return false; // To suppress warning about undefined return value
|
||||
}
|
||||
|
||||
public boolean contains(Point p) {
|
||||
return 0 <= p.getX() && p.getX() < width &&
|
||||
0 <= p.getY() && p.getY() < height;
|
||||
}
|
||||
|
||||
public boolean isDestination(Point p) {
|
||||
return p.equals(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether <code>p</code>, when coming from <code>fromDir</code>, is a blind alley.
|
||||
*/
|
||||
public boolean isBlindAlley(Point p, Direction fromDir) {
|
||||
int directionBitsExceptFromDir = Direction.allDirectionBits & ~fromDir.bit;
|
||||
return (passages[p.getX()][p.getY()] & directionBitsExceptFromDir) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a labyrinth (with or without cycles, depending on CYCLE_CREATION_PROBABILITY)
|
||||
* using the depth-first algorithm (www.astrolog.org/labyrnth/algrithm.htm (sic!))
|
||||
*/
|
||||
|
||||
private void makePassages() {
|
||||
ArrayDeque<Point> pointsToDo = new ArrayDeque<Point>();
|
||||
Point current;
|
||||
pointsToDo.push(getStart());
|
||||
while (!pointsToDo.isEmpty()) {
|
||||
current = pointsToDo.pop();
|
||||
int cx = current.getX();
|
||||
int cy = current.getY();
|
||||
Direction[] dirs = Direction.values();
|
||||
Collections.shuffle(Arrays.asList(dirs));
|
||||
// For all unvisited neighboring cells in random order:
|
||||
// Make a passage from the current cell to that neighbor
|
||||
for (Direction dir : dirs) {
|
||||
// Pick random neighbor of current cell as new cell (nx, ny)
|
||||
Point neighbor = current.getNeighbor(dir);
|
||||
int nx = neighbor.getX();
|
||||
int ny = neighbor.getY();
|
||||
|
||||
if (contains(neighbor) // If neighbor is still in the labyrinth ...
|
||||
&& ( passages[nx][ny] == 0 // ... and has no passage yet, i.e. has not been visited yet during generation
|
||||
|| Math.random() < CYCLE_CREATION_PROBABILITY )) { // ... or creating a cycle is OK
|
||||
|
||||
// Make a two-way passage, i.e. from current to neighbor and from neighbor to current:
|
||||
passages[cx][cy] |= dir.bit;
|
||||
passages[nx][ny] |= dir.opposite.bit;
|
||||
|
||||
// Remember to continue from this neighbor later on
|
||||
pointsToDo.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void print() {
|
||||
System.out.println("Labyrinth with start " + start + " and end " + end);
|
||||
for (int i = 0; i < height; i++) {
|
||||
// draw the north edges
|
||||
for (int j = 0; j < width; j++) {
|
||||
System.out.print((passages[j][i] & Direction.N.bit) == 0 ? "+---" : "+ ");
|
||||
}
|
||||
System.out.println("+");
|
||||
// draw the west edges
|
||||
for (int j = 0; j < width; j++) {
|
||||
System.out.print((passages[j][i] & Direction.W.bit) == 0 ? "| " : " ");
|
||||
}
|
||||
// draw the far east edge
|
||||
System.out.println("|");
|
||||
}
|
||||
// draw the bottom line
|
||||
for (int j = 0; j < width; j++) {
|
||||
System.out.print("+---");
|
||||
}
|
||||
System.out.println("+");
|
||||
}
|
||||
|
||||
public int cell_size_pixels() {
|
||||
return CELL_PX;
|
||||
}
|
||||
|
||||
public boolean smallEnoughToDisplay() {
|
||||
return width*CELL_PX <= MAX_PX_TO_DISPLAY && height*CELL_PX <= MAX_PX_TO_DISPLAY;
|
||||
}
|
||||
|
||||
public void display(Graphics graphics) {
|
||||
// draw start and end cell in special colors (covering start and end cell of the solution path)
|
||||
graphics.setColor(Color.RED);
|
||||
graphics.fillRect(start.getX()*CELL_PX, start.getY()*CELL_PX, CELL_PX, CELL_PX);
|
||||
graphics.setColor(Color.GREEN);
|
||||
graphics.fillRect(end.getX()*CELL_PX, end.getY()*CELL_PX, CELL_PX, CELL_PX);
|
||||
|
||||
// draw black walls (covering part of the solution path)
|
||||
graphics.setColor(Color.BLACK);
|
||||
for(int x = 0; x < width; ++x) {
|
||||
for(int y = 0; y < height; ++y) {
|
||||
// draw north edge of each cell (together with south edge of cell above)
|
||||
if ((passages[x][y] & Direction.N.bit) == 0)
|
||||
// y-HALF_WALL_PX will be half out of labyrinth for x==0 row,
|
||||
// but that does not hurt the picture thanks to automatic cropping
|
||||
graphics.fillRect(x*CELL_PX, y*CELL_PX-HALF_WALL_PX, CELL_PX, 2*HALF_WALL_PX);
|
||||
// draw west edge of each cell (together with east edge of cell to the left)
|
||||
if ((passages[x][y] & Direction.W.bit) == 0)
|
||||
// x-HALF_WALL_PX will be half out of labyrinth for y==0 column,
|
||||
// but that does not hurt the picture thanks to automatic cropping
|
||||
graphics.fillRect(x*CELL_PX-HALF_WALL_PX, y*CELL_PX, 2*HALF_WALL_PX, CELL_PX);
|
||||
}
|
||||
}
|
||||
// draw east edge of labyrinth
|
||||
graphics.fillRect(width*CELL_PX, 0, HALF_WALL_PX, height*CELL_PX);
|
||||
// draw south edge of labyrinth
|
||||
graphics.fillRect(0, height*CELL_PX-HALF_WALL_PX, width*CELL_PX, HALF_WALL_PX);
|
||||
}
|
||||
|
||||
|
||||
public boolean checkSolution(Point solution[]) {
|
||||
Point from = solution[0];
|
||||
if (!from.equals(start)) {
|
||||
System.out.println("checkSolution fails because the first cell is" + from + ", but not " + start);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < solution.length; ++i) {
|
||||
Point to = solution[i];
|
||||
if (!hasPassage(from, to)) {
|
||||
System.out.println("checkSolution fails because there is no passage from " + from + " to " + to);
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
if (!from.equals(end)) {
|
||||
System.out.println("checkSolution fails because the last cell is" + from + ", but not " + end);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* An immutable class for a 2D point that can safely be shared among threads.
|
||||
*
|
||||
* Author: Holger.Peine@hs-hannover.de
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public final class Point implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
final int x, y;
|
||||
Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
int getX() { return x; }
|
||||
int getY() { return y; }
|
||||
|
||||
final Point getNeighbor(Direction dir) {
|
||||
return new Point(x+dir.dx, y+dir.dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "("+x+", "+y+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other.getClass() != this.getClass())
|
||||
return false;
|
||||
Point p = (Point)other;
|
||||
return x == p.x && y == p.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 3001*x+y; // 3001 is prime
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* An enum for 2D directions (north, west, south, east) represented as a bit vector,
|
||||
* and an immutable class that packages a Point with such a direction.
|
||||
*
|
||||
* Author: Holger.Peine@hs-hannover.de
|
||||
* Source of enum Direction: http://rosettacode.org/wiki/Maze#Java
|
||||
*/
|
||||
|
||||
|
||||
|
||||
enum Direction {
|
||||
N(1, 0, -1), S(2, 0, 1), E(4, 1, 0), W(8, -1, 0);
|
||||
static final int allDirectionBits = N.bit | S.bit | E.bit | W.bit;
|
||||
final int bit;
|
||||
final int dx;
|
||||
final int dy;
|
||||
Direction opposite;
|
||||
|
||||
// use the static initializer to resolve forward references
|
||||
static {
|
||||
N.opposite = S;
|
||||
S.opposite = N;
|
||||
E.opposite = W;
|
||||
W.opposite = E;
|
||||
}
|
||||
|
||||
private Direction(int bit, int dx, int dy) {
|
||||
this.bit = bit;
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch(this) {
|
||||
case N: return "N";
|
||||
case S: return "S";
|
||||
case W: return "W";
|
||||
case E: return "E";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PointAndDirection {
|
||||
final private Point point;
|
||||
public Point getPoint() {
|
||||
return point;
|
||||
}
|
||||
final private Direction directionToBranchingPoint;
|
||||
public Direction getDirectionToBranchingPoint() {
|
||||
return directionToBranchingPoint;
|
||||
}
|
||||
PointAndDirection(Point p, Direction direction) {
|
||||
this.point = p;
|
||||
directionToBranchingPoint = direction;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GridLayout;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
final public class Solver extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// The default size of the labyrinth (i.e. unless program is invoked with size arguments):
|
||||
private static final int DEFAULT_WIDTH_IN_CELLS = 100;
|
||||
private static final int DEFAULT_HEIGHT_IN_CELLS = 100;
|
||||
|
||||
private static final int N_RUNS_HALF = 5; // #runs will be 2*N_RUNS_HALF + 1
|
||||
|
||||
// The grid defining the structure of the labyrinth
|
||||
private final Labyrinth labyrinth;
|
||||
|
||||
// For each cell in the labyrinth: Has solve() visited it yet?
|
||||
private boolean[][] visited; // initialized in solve()
|
||||
|
||||
private Point[] solution = null; // set to solution path once that has been computed
|
||||
|
||||
public Solver(Labyrinth labyrinth) {
|
||||
this.labyrinth = labyrinth;
|
||||
}
|
||||
|
||||
public Solver(int width, int height) {
|
||||
this(new Labyrinth(width, height));
|
||||
}
|
||||
|
||||
private boolean visitedBefore(Point p) {
|
||||
return visited[p.getX()][p.getY()];
|
||||
}
|
||||
|
||||
private void visit(Point p) {
|
||||
visited[p.getX()][p.getY()] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a path through the labyrinth from start to end as an array, or null if no solution exists
|
||||
*/
|
||||
public Point[] solve() {
|
||||
|
||||
// Initialize the search state: This must be done here to be part of the timing measurement
|
||||
|
||||
Point current = labyrinth.getStart();
|
||||
ArrayDeque<Point> pathSoFar = new ArrayDeque<Point>(); // Path from start to just before current
|
||||
visited = new boolean[labyrinth.getWidth()][labyrinth.getHeight()]; // initially all false
|
||||
ArrayDeque<PointAndDirection> backtrackStack = new ArrayDeque<PointAndDirection>();
|
||||
// Used as a stack: Branches not yet taken; solver will backtrack to these branching points later
|
||||
// TODO: Is it faster to allocate backtrackStack with width*height elements right away?
|
||||
|
||||
// Search:
|
||||
|
||||
while (!labyrinth.isDestination(current)) {
|
||||
Point next = null;
|
||||
visit(current);
|
||||
|
||||
// Use first random unvisited neighbor as next cell, push others on the backtrack stack:
|
||||
Direction[] dirs = Direction.values();
|
||||
for (Direction directionToNeighbor: dirs) {
|
||||
Point neighbor = current.getNeighbor(directionToNeighbor);
|
||||
if ( labyrinth.hasPassage(current, directionToNeighbor)
|
||||
&& !visitedBefore(neighbor)
|
||||
&& ( !labyrinth.isBlindAlley(neighbor, directionToNeighbor.opposite)
|
||||
|| labyrinth.isDestination(neighbor))) {
|
||||
if (next == null) // 1st unvisited neighbor
|
||||
next = neighbor;
|
||||
else {
|
||||
// 2nd or higher unvisited neighbor: Save neighbor as starting cell for a later backtracking
|
||||
backtrackStack.push(new PointAndDirection(neighbor, directionToNeighbor.opposite));
|
||||
// System.out.println("Pushing " + neighbor + " to the backtracking stack.");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Advance to next cell, if any:
|
||||
if (next != null) {
|
||||
// System.out.println("Advancing from " + current + " to " + next);
|
||||
pathSoFar.addLast(current);
|
||||
current = next;
|
||||
} else {
|
||||
// current has no unvisited neighbor: Backtrack, if possible
|
||||
if (backtrackStack.isEmpty())
|
||||
return null; // No more backtracking avaible: No solution exists
|
||||
|
||||
// Backtrack: Continue with cell saved at latest branching point:
|
||||
PointAndDirection pd = backtrackStack.pop();
|
||||
current = pd.getPoint();
|
||||
Point branchingPoint = current.getNeighbor(pd.getDirectionToBranchingPoint());
|
||||
// System.out.println("Backtracking to " + branchingPoint);
|
||||
// Remove the dead end from the top of pathSoFar, i.e. all cells after branchingPoint:
|
||||
while (!pathSoFar.peekLast().equals(branchingPoint)) {
|
||||
// System.out.println(" Going back before " + pathSoFar.peekLast());
|
||||
pathSoFar.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
pathSoFar.addLast(current);
|
||||
// Point[0] is only for making the return value have type Point[] (and not Object[]):
|
||||
return pathSoFar.toArray(new Point[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics graphics) {
|
||||
super.paintComponent(graphics);
|
||||
// draw white background
|
||||
graphics.setColor(Color.WHITE);
|
||||
graphics.fillRect(0, 0, labyrinth.getWidth()*labyrinth.cell_size_pixels(), labyrinth.getHeight()*labyrinth.cell_size_pixels());
|
||||
|
||||
// draw solution path, if available
|
||||
if (solution != null) {
|
||||
graphics.setColor(Color.YELLOW);
|
||||
for (Point p: solution)
|
||||
/* // fill only white area between the walls instead of whole cell:
|
||||
graphics.fillRect(p.getX()*CELL_PX+HALF_WALL_PX, p.getY()*CELL_PX+HALF_WALL_PX,
|
||||
CELL_PX-2*HALF_WALL_PX, CELL_PX-2*HALF_WALL_PX);
|
||||
*/
|
||||
graphics.fillRect(p.getX()*labyrinth.cell_size_pixels(), p.getY()*labyrinth.cell_size_pixels(),
|
||||
labyrinth.cell_size_pixels(), labyrinth.cell_size_pixels());
|
||||
}
|
||||
// draw walls
|
||||
labyrinth.display(graphics);
|
||||
}
|
||||
|
||||
public void printSolution() {
|
||||
System.out.print("Solution: ");
|
||||
for (Point p: solution)
|
||||
System.out.print(p);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void displaySolution() {
|
||||
repaint();
|
||||
}
|
||||
|
||||
private static Solver makeAndSaveSolver(String[] args) {
|
||||
|
||||
// Construct solver: Either read it from a file, or create a new one
|
||||
if (args.length >= 1 && args[0].endsWith(".ser")) {
|
||||
|
||||
// 1st argument is name of file with serialized labyrinth: Ignore other arguments
|
||||
// and create a solver for the labyrinth from that file:
|
||||
ObjectInputStream ois;
|
||||
try {
|
||||
ois = new ObjectInputStream(new FileInputStream(args[0]));
|
||||
Labyrinth labyrinth = (Labyrinth)ois.readObject();
|
||||
ois.close();
|
||||
return new Solver(labyrinth);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Create solver for new, random labyrinth:
|
||||
|
||||
int width = args.length >= 1 ? (Integer.parseInt(args[0])) : DEFAULT_WIDTH_IN_CELLS;
|
||||
int height = args.length >= 2 ? (Integer.parseInt(args[1])) : DEFAULT_HEIGHT_IN_CELLS;
|
||||
|
||||
Solver solver = new Solver(width, height);
|
||||
|
||||
// Save labyrinth to file (may be reused in future program executions):
|
||||
try {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("labyrinth.ser"));
|
||||
oos.writeObject(solver.labyrinth);
|
||||
oos.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
return solver;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void displayLabyrinth(Solver solver) {
|
||||
JFrame frame = new JFrame("Sequential labyrinth solver");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
// TODO: Window is initially displayed somewhat smaller than
|
||||
// the indicated frame size, therefore use width+5 and height+5:
|
||||
frame.setSize((solver.labyrinth.getWidth()+5) * solver.labyrinth.cell_size_pixels(),
|
||||
(solver.labyrinth.getHeight()+5) * solver.labyrinth.cell_size_pixels());
|
||||
|
||||
// Put a scroll pane around the labyrinth frame if the latter is too large
|
||||
// (by Joern Lenselink)
|
||||
Dimension displayDimens = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize();
|
||||
Dimension labyrinthDimens = frame.getSize();
|
||||
if(labyrinthDimens.height > displayDimens.height) {
|
||||
JScrollPane scroll = new JScrollPane();
|
||||
solver.setBackground(Color.LIGHT_GRAY);
|
||||
frame.getContentPane().add(scroll);
|
||||
JPanel borderlayoutpanel = new JPanel();
|
||||
borderlayoutpanel.setBackground(Color.darkGray);
|
||||
scroll.setViewportView(borderlayoutpanel);
|
||||
borderlayoutpanel.setLayout(new BorderLayout(0, 0));
|
||||
|
||||
JPanel columnpanel = new JPanel();
|
||||
borderlayoutpanel.add(columnpanel, BorderLayout.NORTH);
|
||||
columnpanel.setLayout(new GridLayout(0, 1, 0, 1));
|
||||
columnpanel.setOpaque(false);
|
||||
columnpanel.setBackground(Color.darkGray);
|
||||
|
||||
columnpanel.setSize(labyrinthDimens.getSize());
|
||||
columnpanel.setPreferredSize(labyrinthDimens.getSize());
|
||||
columnpanel.add(solver);
|
||||
} else {
|
||||
// No scroll pane needed:
|
||||
frame.getContentPane().add(solver);
|
||||
}
|
||||
|
||||
frame.setVisible(true); // will draw the labyrinth (without solution)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args If the first argument is a file name ending in .ser, the serialized labyrinth in that file
|
||||
* is used; else the first two arguments are optional numbers giving the width and height of a new
|
||||
* labyrinth to be constructed. Then the labyrinth is solved and displayed (unless too large).
|
||||
* This is run a certain number of times and then the median run time is printed.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
long[] runTimes = new long[2*N_RUNS_HALF + 1];
|
||||
|
||||
for (int run = 0; run < 2*N_RUNS_HALF + 1; ++run) {
|
||||
|
||||
Solver solver = makeAndSaveSolver(args);
|
||||
if (solver.labyrinth.smallEnoughToDisplay()) {
|
||||
displayLabyrinth(solver);
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
solver.solution = solver.solve();
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
if (solver.solution == null)
|
||||
System.out.println("No solution exists.");
|
||||
else {
|
||||
System.out.println("Computed sequential solution of length " + solver.solution.length + " to labyrinth of size " +
|
||||
solver.labyrinth.getWidth() + "x" + solver.labyrinth.getHeight() + " in " + (endTime - startTime) + "ms.");
|
||||
|
||||
runTimes[run] = endTime - startTime;
|
||||
|
||||
if (solver.labyrinth.smallEnoughToDisplay()) {
|
||||
solver.displaySolution();
|
||||
solver.printSolution();
|
||||
}
|
||||
|
||||
if (solver.labyrinth.checkSolution(solver.solution))
|
||||
System.out.println("Solution correct :-)");
|
||||
else
|
||||
System.out.println("Solution incorrect :-(");
|
||||
}
|
||||
}
|
||||
Arrays.sort(runTimes);
|
||||
System.out.println("Median run time was " + runTimes[N_RUNS_HALF] + " ms.");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue