diff --git a/projekt/Labyrinth.java b/projekt/Labyrinth.java
new file mode 100644
index 0000000..d56feb2
--- /dev/null
+++ b/projekt/Labyrinth.java
@@ -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 p
, when coming from fromDir
, 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 pointsToDo = new ArrayDeque();
+ 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;
+ }
+}
diff --git a/projekt/Point.java b/projekt/Point.java
new file mode 100644
index 0000000..d9a0cf8
--- /dev/null
+++ b/projekt/Point.java
@@ -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
+ }
+}
diff --git a/projekt/PointAndDirection.java b/projekt/PointAndDirection.java
new file mode 100644
index 0000000..30e1c45
--- /dev/null
+++ b/projekt/PointAndDirection.java
@@ -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;
+ }
+}
+
diff --git a/projekt/Solver.java b/projekt/Solver.java
new file mode 100644
index 0000000..b3eb119
--- /dev/null
+++ b/projekt/Solver.java
@@ -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 pathSoFar = new ArrayDeque(); // Path from start to just before current
+ visited = new boolean[labyrinth.getWidth()][labyrinth.getHeight()]; // initially all false
+ ArrayDeque backtrackStack = new ArrayDeque();
+ // 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.");
+ }
+}
\ No newline at end of file