initial commit
This commit is contained in:
commit
43c1a9aa73
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Export-IncludesJava/*
|
BIN
Export-NoJava/breadthFirstSearch.exe
Normal file
BIN
Export-NoJava/breadthFirstSearch.exe
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/breadthFirstSearch.jar
Normal file
BIN
Export-NoJava/lib/breadthFirstSearch.jar
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/core.jar
Normal file
BIN
Export-NoJava/lib/core.jar
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/gluegen-rt-natives-windows-amd64.jar
Normal file
BIN
Export-NoJava/lib/gluegen-rt-natives-windows-amd64.jar
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/gluegen-rt.jar
Normal file
BIN
Export-NoJava/lib/gluegen-rt.jar
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/jogl-all-natives-windows-amd64.jar
Normal file
BIN
Export-NoJava/lib/jogl-all-natives-windows-amd64.jar
Normal file
Binary file not shown.
BIN
Export-NoJava/lib/jogl-all.jar
Normal file
BIN
Export-NoJava/lib/jogl-all.jar
Normal file
Binary file not shown.
369
Export-NoJava/source/breadthFirstSearch.java
Normal file
369
Export-NoJava/source/breadthFirstSearch.java
Normal file
|
@ -0,0 +1,369 @@
|
|||
import processing.core.*;
|
||||
import processing.data.*;
|
||||
import processing.event.*;
|
||||
import processing.opengl.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class breadthFirstSearch extends PApplet {
|
||||
|
||||
// Note #1: This version works using 'intersecting Lines' as a detection for Walls.
|
||||
// Note #2: The ratio of constants 'cellsInWidth' and 'cellsInHeight' must remain the same as that of your screen.
|
||||
|
||||
// final int cells = 60;
|
||||
final int cellsInWidth = 16;
|
||||
final int cellsInHeight = 9;
|
||||
final float verticalWall = .5f;
|
||||
final float horizontalWall = .5f;
|
||||
final int frameRate = 30; // Can't go above 60 or below 0 FPS!
|
||||
final float timeout = 5;
|
||||
|
||||
int cIW = cellsInWidth, cIH = cellsInHeight;
|
||||
float scl = 0, diameter = 0;
|
||||
boolean solvedMaze = false, reachedOrigin = false;
|
||||
ArrayList<Wall> walls = new ArrayList<Wall>();
|
||||
ArrayList<Mover> movers = new ArrayList<Mover>();
|
||||
ArrayList<Mover> visited = new ArrayList<Mover>();
|
||||
ArrayList<Mover> finalPath = new ArrayList<Mover>();
|
||||
Mover origin, pathFinder;
|
||||
|
||||
boolean outputToggle = false;
|
||||
int previousUnsolvableMazes = 0;
|
||||
|
||||
int solvedFrameCountCapture = -1;
|
||||
|
||||
public void setup() {
|
||||
|
||||
|
||||
generateMaze();
|
||||
|
||||
frameRate(frameRate);
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
background(0);
|
||||
|
||||
/*
|
||||
translate(width / 2 - height / 2, 0);
|
||||
fill(0);
|
||||
stroke(0);
|
||||
rect(0, 0, height, height);
|
||||
*/
|
||||
|
||||
// Display Walls
|
||||
noFill();
|
||||
stroke(255);
|
||||
for (Wall w : walls) {
|
||||
w.show();
|
||||
}
|
||||
|
||||
// Display spots that are to be moved to
|
||||
fill(0,255,0);
|
||||
stroke(0,255,0);
|
||||
for (Mover m : movers) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
// Display origin spot
|
||||
fill(255,0,0);
|
||||
stroke(255,0,0);
|
||||
ellipse(origin.position.x, origin.position.y, diameter, diameter);
|
||||
|
||||
if (!solvedMaze) {
|
||||
/*
|
||||
// Display previously visited spots
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : visited) {
|
||||
if (!m.equals(origin)) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// As long as the maze isn't solved, keep stepping the algorithm
|
||||
breadthFirstSearchStep();
|
||||
} else {
|
||||
// When the maze is solved, display the last position and the path to get from the origin to there.
|
||||
fill(0,0,255);
|
||||
stroke(0,0,255);
|
||||
final Mover last = visited.get(visited.size()-1);
|
||||
ellipse(last.position.x, last.position.y, diameter, diameter);
|
||||
|
||||
// Display path from origin to last visited.
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : finalPath) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
if (!outputToggle) {
|
||||
println("Previously genereted '" + previousUnsolvableMazes + "' mazes which were unsolvable.");
|
||||
outputToggle = true;
|
||||
}
|
||||
|
||||
// Trace back the path from last visited to origin.
|
||||
if (!reachedOrigin) {
|
||||
for (Mover v : visited) {
|
||||
if (pathFinder.isNextOf(v)) {
|
||||
if (v.steps == 0) {
|
||||
reachedOrigin = true;
|
||||
break;
|
||||
}
|
||||
finalPath.add(v.copy());
|
||||
pathFinder = v.copy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (solvedFrameCountCapture == -1) {
|
||||
solvedFrameCountCapture = frameCount;
|
||||
} else if (frameCount-solvedFrameCountCapture == frameRate*timeout) {
|
||||
// Possible Error: 'frameCount-solvedFrameCountCapture' could be a negative number or similar.
|
||||
// Note: frameCount, as the name implies, counts how many frames has passed. At one point, it has to reset to 0 (?).
|
||||
// -> when resetting, no matter the result, the difference will never equal the wanted window or take too long to get there.
|
||||
|
||||
outputToggle = false;
|
||||
solvedFrameCountCapture = -1;
|
||||
resetMaze(false);
|
||||
previousUnsolvableMazes = 0;
|
||||
generateMaze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void breadthFirstSearchStep() {
|
||||
// Idea: Move to every accessable (no walls inbetween the two) neighbor, when reaching the bottom, you have a path.
|
||||
// Note: I don't actually know if this is the same working principle as Breadth-First Search.
|
||||
|
||||
ArrayList<Mover> nextGen = new ArrayList<Mover>();
|
||||
for (Mover m : movers) {
|
||||
visited.add(m.copy());
|
||||
|
||||
if (m.position.y == height-scl/2) {
|
||||
solvedMaze = true;
|
||||
nextGen = new ArrayList<Mover>();
|
||||
|
||||
pathFinder = m.copy();
|
||||
break;
|
||||
}
|
||||
|
||||
final ArrayList<Mover> neighbors = m.getNeighbors();
|
||||
for (Mover n : neighbors) {
|
||||
boolean alreadyVisited = false;
|
||||
for (Mover v : visited) {
|
||||
if (n.equals(v)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Mover s : nextGen) {
|
||||
if (n.equals(s)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyVisited) {
|
||||
nextGen.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
movers = new ArrayList<Mover>(nextGen);
|
||||
}
|
||||
|
||||
public void generateMaze() {
|
||||
while (true) {
|
||||
// While loop to reset when previous maze is unsolvable (from origin position).
|
||||
|
||||
int randomScalor = floor(random(3,8));
|
||||
cIW *= randomScalor;
|
||||
cIH *= randomScalor;
|
||||
scl = (float) height / cIH;
|
||||
diameter = scl / 2;
|
||||
|
||||
for (float i = 0; i < /*height*/width; i += scl) {
|
||||
for (float j = 0; j < height; j += scl) {
|
||||
if (random(1) <= verticalWall && i != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i, j+scl)
|
||||
));
|
||||
}
|
||||
if (random(1) <= horizontalWall && j != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i+scl, j)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
origin = new Mover((floor(random(/*cells*/cellsInWidth))+.5f) * scl, scl/2, 0);
|
||||
movers.add(origin);
|
||||
|
||||
// Check if maze is solvable by stepping through until solved.
|
||||
// Note: If there are no movers while the maze is unsolved, it means there are no spots to be visited and it's unsolvable.
|
||||
boolean isSolvable = false;
|
||||
while (!solvedMaze) {
|
||||
breadthFirstSearchStep();
|
||||
if (solvedMaze) {
|
||||
isSolvable = true;
|
||||
break;
|
||||
} else if (movers.size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resetMaze(isSolvable);
|
||||
if (isSolvable) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetMaze(boolean iS) {
|
||||
// Resetting variables depending on solvability of the maze.
|
||||
cIW = cellsInWidth;
|
||||
cIH = cellsInHeight;
|
||||
solvedMaze = false;
|
||||
reachedOrigin = false;
|
||||
movers = new ArrayList<Mover>();
|
||||
visited = new ArrayList<Mover>();
|
||||
finalPath = new ArrayList<Mover>();
|
||||
if (iS) {
|
||||
movers.add(origin);
|
||||
} else {
|
||||
previousUnsolvableMazes++;
|
||||
walls = new ArrayList<Wall>();
|
||||
}
|
||||
}
|
||||
class Mover {
|
||||
PVector position;
|
||||
int steps;
|
||||
|
||||
Mover(float x, float y, int s) {
|
||||
position = new PVector(x, y);
|
||||
steps = s;
|
||||
}
|
||||
|
||||
public boolean isNextOf(Mover other) {
|
||||
return ((other.steps == steps-1) && maxDistFrom(other, scl) && other.canMoveTo(position));
|
||||
}
|
||||
|
||||
public boolean maxDistFrom(Mover other, float maxDist) {
|
||||
return (PVector.dist(position, other.position) <= maxDist);
|
||||
}
|
||||
|
||||
public Mover copy() {
|
||||
return new Mover(position.x, position.y, steps);
|
||||
}
|
||||
|
||||
public boolean equals(Mover other) {
|
||||
return pointEquals(position, other.position);
|
||||
}
|
||||
|
||||
public boolean canMoveTo(PVector newPos) {
|
||||
if ((newPos.x < 0 || newPos.x > /*height*/width) || (newPos.y < 0 || newPos.y > height)) { return false; }
|
||||
|
||||
final Wall path = new Wall(position, newPos);
|
||||
|
||||
boolean noIntersects = true;
|
||||
for (Wall w : walls) {
|
||||
if (w.intersect(path)) {
|
||||
noIntersects = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return noIntersects;
|
||||
}
|
||||
|
||||
public ArrayList<Mover> getNeighbors() {
|
||||
ArrayList<Mover> neighbors = new ArrayList<Mover>();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final PVector converted = convertDirection(i);
|
||||
if (converted == null) { continue; }
|
||||
final PVector newPosition = PVector.add(position, converted);
|
||||
if (canMoveTo(newPosition)) {
|
||||
neighbors.add(new Mover(newPosition.x, newPosition.y, steps+1));
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
}
|
||||
|
||||
public PVector convertDirection(int direction) {
|
||||
if (direction < 0 || direction > 3) { return null; }
|
||||
|
||||
switch (direction) {
|
||||
case 0:
|
||||
return new PVector(-scl, 0);
|
||||
case 1:
|
||||
return new PVector(scl, 0);
|
||||
case 2:
|
||||
return new PVector(0, -scl);
|
||||
case 3:
|
||||
return new PVector(0, scl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class Wall {
|
||||
PVector start, end;
|
||||
|
||||
Wall(PVector a, PVector b) {
|
||||
start = new PVector(a.x, a.y);
|
||||
end = new PVector(b.x, b.y);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
line(start.x, start.y, end.x, end.y);
|
||||
}
|
||||
|
||||
public boolean intersect(Wall other) {
|
||||
final PVector a = start.copy();
|
||||
final PVector b = end.copy();
|
||||
final PVector c = other.start.copy();
|
||||
final PVector d = other.end.copy();
|
||||
|
||||
if ((pointEquals(a,c) && pointEquals(b,d)) || (pointEquals(a,d) && pointEquals(b,c))) { return true; }
|
||||
|
||||
final float t = (a.x-c.x) * (c.y-d.y) - (a.y-c.y) * (c.x-d.x);
|
||||
final float u = (b.x-a.x) * (a.y-c.y) - (b.y-a.y) * (a.x-c.x);
|
||||
final float D = (a.x-b.x) * (c.y-d.y) - (a.y-b.y) * (c.x-d.x);
|
||||
|
||||
if (D == 0) { return false; }
|
||||
|
||||
final float td = t / D;
|
||||
final float ud = u / D;
|
||||
if (td >= 0 && td <= 1 && ud >= 0 && ud <= 1) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean pointEquals(PVector A, PVector B) {
|
||||
return (A.x == B.x && A.y == B.y);
|
||||
}
|
||||
public void settings() { fullScreen(); }
|
||||
static public void main(String[] passedArgs) {
|
||||
String[] appletArgs = new String[] { "breadthFirstSearch" };
|
||||
if (passedArgs != null) {
|
||||
PApplet.main(concat(appletArgs, passedArgs));
|
||||
} else {
|
||||
PApplet.main(appletArgs);
|
||||
}
|
||||
}
|
||||
}
|
232
Export-NoJava/source/breadthFirstSearch.pde
Normal file
232
Export-NoJava/source/breadthFirstSearch.pde
Normal file
|
@ -0,0 +1,232 @@
|
|||
// Note #1: This version works using 'intersecting Lines' as a detection for Walls.
|
||||
// Note #2: The ratio of constants 'cellsInWidth' and 'cellsInHeight' must remain the same as that of your screen.
|
||||
|
||||
// final int cells = 60;
|
||||
final int cellsInWidth = 16;
|
||||
final int cellsInHeight = 9;
|
||||
final float verticalWall = .5;
|
||||
final float horizontalWall = .5;
|
||||
final int frameRate = 30; // Can't go above 60 or below 0 FPS!
|
||||
final float timeout = 5;
|
||||
|
||||
int cIW = cellsInWidth, cIH = cellsInHeight;
|
||||
float scl = 0, diameter = 0;
|
||||
boolean solvedMaze = false, reachedOrigin = false;
|
||||
ArrayList<Wall> walls = new ArrayList<Wall>();
|
||||
ArrayList<Mover> movers = new ArrayList<Mover>();
|
||||
ArrayList<Mover> visited = new ArrayList<Mover>();
|
||||
ArrayList<Mover> finalPath = new ArrayList<Mover>();
|
||||
Mover origin, pathFinder;
|
||||
|
||||
boolean outputToggle = false;
|
||||
int previousUnsolvableMazes = 0;
|
||||
|
||||
int solvedFrameCountCapture = -1;
|
||||
|
||||
void setup() {
|
||||
fullScreen();
|
||||
|
||||
generateMaze();
|
||||
|
||||
frameRate(frameRate);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(0);
|
||||
|
||||
/*
|
||||
translate(width / 2 - height / 2, 0);
|
||||
fill(0);
|
||||
stroke(0);
|
||||
rect(0, 0, height, height);
|
||||
*/
|
||||
|
||||
// Display Walls
|
||||
noFill();
|
||||
stroke(255);
|
||||
for (Wall w : walls) {
|
||||
w.show();
|
||||
}
|
||||
|
||||
// Display spots that are to be moved to
|
||||
fill(0,255,0);
|
||||
stroke(0,255,0);
|
||||
for (Mover m : movers) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
// Display origin spot
|
||||
fill(255,0,0);
|
||||
stroke(255,0,0);
|
||||
ellipse(origin.position.x, origin.position.y, diameter, diameter);
|
||||
|
||||
if (!solvedMaze) {
|
||||
/*
|
||||
// Display previously visited spots
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : visited) {
|
||||
if (!m.equals(origin)) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// As long as the maze isn't solved, keep stepping the algorithm
|
||||
breadthFirstSearchStep();
|
||||
} else {
|
||||
// When the maze is solved, display the last position and the path to get from the origin to there.
|
||||
fill(0,0,255);
|
||||
stroke(0,0,255);
|
||||
final Mover last = visited.get(visited.size()-1);
|
||||
ellipse(last.position.x, last.position.y, diameter, diameter);
|
||||
|
||||
// Display path from origin to last visited.
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : finalPath) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
if (!outputToggle) {
|
||||
println("Previously genereted '" + previousUnsolvableMazes + "' mazes which were unsolvable.");
|
||||
outputToggle = true;
|
||||
}
|
||||
|
||||
// Trace back the path from last visited to origin.
|
||||
if (!reachedOrigin) {
|
||||
for (Mover v : visited) {
|
||||
if (pathFinder.isNextOf(v)) {
|
||||
if (v.steps == 0) {
|
||||
reachedOrigin = true;
|
||||
break;
|
||||
}
|
||||
finalPath.add(v.copy());
|
||||
pathFinder = v.copy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (solvedFrameCountCapture == -1) {
|
||||
solvedFrameCountCapture = frameCount;
|
||||
} else if (frameCount-solvedFrameCountCapture == frameRate*timeout) {
|
||||
// Possible Error: 'frameCount-solvedFrameCountCapture' could be a negative number or similar.
|
||||
// Note: frameCount, as the name implies, counts how many frames has passed. At one point, it has to reset to 0 (?).
|
||||
// -> when resetting, no matter the result, the difference will never equal the wanted window or take too long to get there.
|
||||
|
||||
outputToggle = false;
|
||||
solvedFrameCountCapture = -1;
|
||||
resetMaze(false);
|
||||
previousUnsolvableMazes = 0;
|
||||
generateMaze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void breadthFirstSearchStep() {
|
||||
// Idea: Move to every accessable (no walls inbetween the two) neighbor, when reaching the bottom, you have a path.
|
||||
// Note: I don't actually know if this is the same working principle as Breadth-First Search.
|
||||
|
||||
ArrayList<Mover> nextGen = new ArrayList<Mover>();
|
||||
for (Mover m : movers) {
|
||||
visited.add(m.copy());
|
||||
|
||||
if (m.position.y == height-scl/2) {
|
||||
solvedMaze = true;
|
||||
nextGen = new ArrayList<Mover>();
|
||||
|
||||
pathFinder = m.copy();
|
||||
break;
|
||||
}
|
||||
|
||||
final ArrayList<Mover> neighbors = m.getNeighbors();
|
||||
for (Mover n : neighbors) {
|
||||
boolean alreadyVisited = false;
|
||||
for (Mover v : visited) {
|
||||
if (n.equals(v)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Mover s : nextGen) {
|
||||
if (n.equals(s)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyVisited) {
|
||||
nextGen.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
movers = new ArrayList<Mover>(nextGen);
|
||||
}
|
||||
|
||||
void generateMaze() {
|
||||
while (true) {
|
||||
// While loop to reset when previous maze is unsolvable (from origin position).
|
||||
|
||||
int randomScalor = floor(random(3,8));
|
||||
cIW *= randomScalor;
|
||||
cIH *= randomScalor;
|
||||
scl = (float) height / cIH;
|
||||
diameter = scl / 2;
|
||||
|
||||
for (float i = 0; i < /*height*/width; i += scl) {
|
||||
for (float j = 0; j < height; j += scl) {
|
||||
if (random(1) <= verticalWall && i != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i, j+scl)
|
||||
));
|
||||
}
|
||||
if (random(1) <= horizontalWall && j != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i+scl, j)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
origin = new Mover((floor(random(/*cells*/cellsInWidth))+.5) * scl, scl/2, 0);
|
||||
movers.add(origin);
|
||||
|
||||
// Check if maze is solvable by stepping through until solved.
|
||||
// Note: If there are no movers while the maze is unsolved, it means there are no spots to be visited and it's unsolvable.
|
||||
boolean isSolvable = false;
|
||||
while (!solvedMaze) {
|
||||
breadthFirstSearchStep();
|
||||
if (solvedMaze) {
|
||||
isSolvable = true;
|
||||
break;
|
||||
} else if (movers.size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resetMaze(isSolvable);
|
||||
if (isSolvable) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resetMaze(boolean iS) {
|
||||
// Resetting variables depending on solvability of the maze.
|
||||
cIW = cellsInWidth;
|
||||
cIH = cellsInHeight;
|
||||
solvedMaze = false;
|
||||
reachedOrigin = false;
|
||||
movers = new ArrayList<Mover>();
|
||||
visited = new ArrayList<Mover>();
|
||||
finalPath = new ArrayList<Mover>();
|
||||
if (iS) {
|
||||
movers.add(origin);
|
||||
} else {
|
||||
previousUnsolvableMazes++;
|
||||
walls = new ArrayList<Wall>();
|
||||
}
|
||||
}
|
111
Export-NoJava/source/extra.pde
Normal file
111
Export-NoJava/source/extra.pde
Normal file
|
@ -0,0 +1,111 @@
|
|||
class Mover {
|
||||
PVector position;
|
||||
int steps;
|
||||
|
||||
Mover(float x, float y, int s) {
|
||||
position = new PVector(x, y);
|
||||
steps = s;
|
||||
}
|
||||
|
||||
boolean isNextOf(Mover other) {
|
||||
return ((other.steps == steps-1) && maxDistFrom(other, scl) && other.canMoveTo(position));
|
||||
}
|
||||
|
||||
boolean maxDistFrom(Mover other, float maxDist) {
|
||||
return (PVector.dist(position, other.position) <= maxDist);
|
||||
}
|
||||
|
||||
Mover copy() {
|
||||
return new Mover(position.x, position.y, steps);
|
||||
}
|
||||
|
||||
boolean equals(Mover other) {
|
||||
return pointEquals(position, other.position);
|
||||
}
|
||||
|
||||
boolean canMoveTo(PVector newPos) {
|
||||
if ((newPos.x < 0 || newPos.x > /*height*/width) || (newPos.y < 0 || newPos.y > height)) { return false; }
|
||||
|
||||
final Wall path = new Wall(position, newPos);
|
||||
|
||||
boolean noIntersects = true;
|
||||
for (Wall w : walls) {
|
||||
if (w.intersect(path)) {
|
||||
noIntersects = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return noIntersects;
|
||||
}
|
||||
|
||||
ArrayList<Mover> getNeighbors() {
|
||||
ArrayList<Mover> neighbors = new ArrayList<Mover>();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final PVector converted = convertDirection(i);
|
||||
if (converted == null) { continue; }
|
||||
final PVector newPosition = PVector.add(position, converted);
|
||||
if (canMoveTo(newPosition)) {
|
||||
neighbors.add(new Mover(newPosition.x, newPosition.y, steps+1));
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
}
|
||||
|
||||
PVector convertDirection(int direction) {
|
||||
if (direction < 0 || direction > 3) { return null; }
|
||||
|
||||
switch (direction) {
|
||||
case 0:
|
||||
return new PVector(-scl, 0);
|
||||
case 1:
|
||||
return new PVector(scl, 0);
|
||||
case 2:
|
||||
return new PVector(0, -scl);
|
||||
case 3:
|
||||
return new PVector(0, scl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class Wall {
|
||||
PVector start, end;
|
||||
|
||||
Wall(PVector a, PVector b) {
|
||||
start = new PVector(a.x, a.y);
|
||||
end = new PVector(b.x, b.y);
|
||||
}
|
||||
|
||||
void show() {
|
||||
line(start.x, start.y, end.x, end.y);
|
||||
}
|
||||
|
||||
boolean intersect(Wall other) {
|
||||
final PVector a = start.copy();
|
||||
final PVector b = end.copy();
|
||||
final PVector c = other.start.copy();
|
||||
final PVector d = other.end.copy();
|
||||
|
||||
if ((pointEquals(a,c) && pointEquals(b,d)) || (pointEquals(a,d) && pointEquals(b,c))) { return true; }
|
||||
|
||||
final float t = (a.x-c.x) * (c.y-d.y) - (a.y-c.y) * (c.x-d.x);
|
||||
final float u = (b.x-a.x) * (a.y-c.y) - (b.y-a.y) * (a.x-c.x);
|
||||
final float D = (a.x-b.x) * (c.y-d.y) - (a.y-b.y) * (c.x-d.x);
|
||||
|
||||
if (D == 0) { return false; }
|
||||
|
||||
final float td = t / D;
|
||||
final float ud = u / D;
|
||||
if (td >= 0 && td <= 1 && ud >= 0 && ud <= 1) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean pointEquals(PVector A, PVector B) {
|
||||
return (A.x == B.x && A.y == B.y);
|
||||
}
|
232
breadthFirstSearch.pde
Normal file
232
breadthFirstSearch.pde
Normal file
|
@ -0,0 +1,232 @@
|
|||
// Note #1: This version works using 'intersecting Lines' as a detection for Walls.
|
||||
// Note #2: The ratio of constants 'cellsInWidth' and 'cellsInHeight' must remain the same as that of your screen.
|
||||
|
||||
// final int cells = 60;
|
||||
final int cellsInWidth = 16;
|
||||
final int cellsInHeight = 9;
|
||||
final float verticalWall = .5;
|
||||
final float horizontalWall = .5;
|
||||
final int frameRate = 30; // Can't go above 60 or below 0 FPS!
|
||||
final float timeout = 5;
|
||||
|
||||
int cIW = cellsInWidth, cIH = cellsInHeight;
|
||||
float scl = 0, diameter = 0;
|
||||
boolean solvedMaze = false, reachedOrigin = false;
|
||||
ArrayList<Wall> walls = new ArrayList<Wall>();
|
||||
ArrayList<Mover> movers = new ArrayList<Mover>();
|
||||
ArrayList<Mover> visited = new ArrayList<Mover>();
|
||||
ArrayList<Mover> finalPath = new ArrayList<Mover>();
|
||||
Mover origin, pathFinder;
|
||||
|
||||
boolean outputToggle = false;
|
||||
int previousUnsolvableMazes = 0;
|
||||
|
||||
int solvedFrameCountCapture = -1;
|
||||
|
||||
void setup() {
|
||||
fullScreen();
|
||||
|
||||
generateMaze();
|
||||
|
||||
frameRate(frameRate);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
background(0);
|
||||
|
||||
/*
|
||||
translate(width / 2 - height / 2, 0);
|
||||
fill(0);
|
||||
stroke(0);
|
||||
rect(0, 0, height, height);
|
||||
*/
|
||||
|
||||
// Display Walls
|
||||
noFill();
|
||||
stroke(255);
|
||||
for (Wall w : walls) {
|
||||
w.show();
|
||||
}
|
||||
|
||||
// Display spots that are to be moved to
|
||||
fill(0,255,0);
|
||||
stroke(0,255,0);
|
||||
for (Mover m : movers) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
// Display origin spot
|
||||
fill(255,0,0);
|
||||
stroke(255,0,0);
|
||||
ellipse(origin.position.x, origin.position.y, diameter, diameter);
|
||||
|
||||
if (!solvedMaze) {
|
||||
/*
|
||||
// Display previously visited spots
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : visited) {
|
||||
if (!m.equals(origin)) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// As long as the maze isn't solved, keep stepping the algorithm
|
||||
breadthFirstSearchStep();
|
||||
} else {
|
||||
// When the maze is solved, display the last position and the path to get from the origin to there.
|
||||
fill(0,0,255);
|
||||
stroke(0,0,255);
|
||||
final Mover last = visited.get(visited.size()-1);
|
||||
ellipse(last.position.x, last.position.y, diameter, diameter);
|
||||
|
||||
// Display path from origin to last visited.
|
||||
fill(255,255,0);
|
||||
stroke(255,255,0);
|
||||
for (Mover m : finalPath) {
|
||||
ellipse(m.position.x, m.position.y, diameter, diameter);
|
||||
}
|
||||
|
||||
if (!outputToggle) {
|
||||
println("Previously genereted '" + previousUnsolvableMazes + "' mazes which were unsolvable.");
|
||||
outputToggle = true;
|
||||
}
|
||||
|
||||
// Trace back the path from last visited to origin.
|
||||
if (!reachedOrigin) {
|
||||
for (Mover v : visited) {
|
||||
if (pathFinder.isNextOf(v)) {
|
||||
if (v.steps == 0) {
|
||||
reachedOrigin = true;
|
||||
break;
|
||||
}
|
||||
finalPath.add(v.copy());
|
||||
pathFinder = v.copy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (solvedFrameCountCapture == -1) {
|
||||
solvedFrameCountCapture = frameCount;
|
||||
} else if (frameCount-solvedFrameCountCapture == frameRate*timeout) {
|
||||
// Possible Error: 'frameCount-solvedFrameCountCapture' could be a negative number or similar.
|
||||
// Note: frameCount, as the name implies, counts how many frames has passed. At one point, it has to reset to 0 (?).
|
||||
// -> when resetting, no matter the result, the difference will never equal the wanted window or take too long to get there.
|
||||
|
||||
outputToggle = false;
|
||||
solvedFrameCountCapture = -1;
|
||||
resetMaze(false);
|
||||
previousUnsolvableMazes = 0;
|
||||
generateMaze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void breadthFirstSearchStep() {
|
||||
// Idea: Move to every accessable (no walls inbetween the two) neighbor, when reaching the bottom, you have a path.
|
||||
// Note: I don't actually know if this is the same working principle as Breadth-First Search.
|
||||
|
||||
ArrayList<Mover> nextGen = new ArrayList<Mover>();
|
||||
for (Mover m : movers) {
|
||||
visited.add(m.copy());
|
||||
|
||||
if (m.position.y == height-scl/2) {
|
||||
solvedMaze = true;
|
||||
nextGen = new ArrayList<Mover>();
|
||||
|
||||
pathFinder = m.copy();
|
||||
break;
|
||||
}
|
||||
|
||||
final ArrayList<Mover> neighbors = m.getNeighbors();
|
||||
for (Mover n : neighbors) {
|
||||
boolean alreadyVisited = false;
|
||||
for (Mover v : visited) {
|
||||
if (n.equals(v)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Mover s : nextGen) {
|
||||
if (n.equals(s)) {
|
||||
alreadyVisited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyVisited) {
|
||||
nextGen.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
movers = new ArrayList<Mover>(nextGen);
|
||||
}
|
||||
|
||||
void generateMaze() {
|
||||
while (true) {
|
||||
// While loop to reset when previous maze is unsolvable (from origin position).
|
||||
|
||||
int randomScalor = floor(random(3,8));
|
||||
cIW *= randomScalor;
|
||||
cIH *= randomScalor;
|
||||
scl = (float) height / cIH;
|
||||
diameter = scl / 2;
|
||||
|
||||
for (float i = 0; i < /*height*/width; i += scl) {
|
||||
for (float j = 0; j < height; j += scl) {
|
||||
if (random(1) <= verticalWall && i != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i, j+scl)
|
||||
));
|
||||
}
|
||||
if (random(1) <= horizontalWall && j != 0) {
|
||||
walls.add(new Wall(
|
||||
new PVector(i, j),
|
||||
new PVector(i+scl, j)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
origin = new Mover((floor(random(/*cells*/cellsInWidth))+.5) * scl, scl/2, 0);
|
||||
movers.add(origin);
|
||||
|
||||
// Check if maze is solvable by stepping through until solved.
|
||||
// Note: If there are no movers while the maze is unsolved, it means there are no spots to be visited and it's unsolvable.
|
||||
boolean isSolvable = false;
|
||||
while (!solvedMaze) {
|
||||
breadthFirstSearchStep();
|
||||
if (solvedMaze) {
|
||||
isSolvable = true;
|
||||
break;
|
||||
} else if (movers.size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resetMaze(isSolvable);
|
||||
if (isSolvable) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resetMaze(boolean iS) {
|
||||
// Resetting variables depending on solvability of the maze.
|
||||
cIW = cellsInWidth;
|
||||
cIH = cellsInHeight;
|
||||
solvedMaze = false;
|
||||
reachedOrigin = false;
|
||||
movers = new ArrayList<Mover>();
|
||||
visited = new ArrayList<Mover>();
|
||||
finalPath = new ArrayList<Mover>();
|
||||
if (iS) {
|
||||
movers.add(origin);
|
||||
} else {
|
||||
previousUnsolvableMazes++;
|
||||
walls = new ArrayList<Wall>();
|
||||
}
|
||||
}
|
111
extra.pde
Normal file
111
extra.pde
Normal file
|
@ -0,0 +1,111 @@
|
|||
class Mover {
|
||||
PVector position;
|
||||
int steps;
|
||||
|
||||
Mover(float x, float y, int s) {
|
||||
position = new PVector(x, y);
|
||||
steps = s;
|
||||
}
|
||||
|
||||
boolean isNextOf(Mover other) {
|
||||
return ((other.steps == steps-1) && maxDistFrom(other, scl) && other.canMoveTo(position));
|
||||
}
|
||||
|
||||
boolean maxDistFrom(Mover other, float maxDist) {
|
||||
return (PVector.dist(position, other.position) <= maxDist);
|
||||
}
|
||||
|
||||
Mover copy() {
|
||||
return new Mover(position.x, position.y, steps);
|
||||
}
|
||||
|
||||
boolean equals(Mover other) {
|
||||
return pointEquals(position, other.position);
|
||||
}
|
||||
|
||||
boolean canMoveTo(PVector newPos) {
|
||||
if ((newPos.x < 0 || newPos.x > /*height*/width) || (newPos.y < 0 || newPos.y > height)) { return false; }
|
||||
|
||||
final Wall path = new Wall(position, newPos);
|
||||
|
||||
boolean noIntersects = true;
|
||||
for (Wall w : walls) {
|
||||
if (w.intersect(path)) {
|
||||
noIntersects = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return noIntersects;
|
||||
}
|
||||
|
||||
ArrayList<Mover> getNeighbors() {
|
||||
ArrayList<Mover> neighbors = new ArrayList<Mover>();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final PVector converted = convertDirection(i);
|
||||
if (converted == null) { continue; }
|
||||
final PVector newPosition = PVector.add(position, converted);
|
||||
if (canMoveTo(newPosition)) {
|
||||
neighbors.add(new Mover(newPosition.x, newPosition.y, steps+1));
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
}
|
||||
|
||||
PVector convertDirection(int direction) {
|
||||
if (direction < 0 || direction > 3) { return null; }
|
||||
|
||||
switch (direction) {
|
||||
case 0:
|
||||
return new PVector(-scl, 0);
|
||||
case 1:
|
||||
return new PVector(scl, 0);
|
||||
case 2:
|
||||
return new PVector(0, -scl);
|
||||
case 3:
|
||||
return new PVector(0, scl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class Wall {
|
||||
PVector start, end;
|
||||
|
||||
Wall(PVector a, PVector b) {
|
||||
start = new PVector(a.x, a.y);
|
||||
end = new PVector(b.x, b.y);
|
||||
}
|
||||
|
||||
void show() {
|
||||
line(start.x, start.y, end.x, end.y);
|
||||
}
|
||||
|
||||
boolean intersect(Wall other) {
|
||||
final PVector a = start.copy();
|
||||
final PVector b = end.copy();
|
||||
final PVector c = other.start.copy();
|
||||
final PVector d = other.end.copy();
|
||||
|
||||
if ((pointEquals(a,c) && pointEquals(b,d)) || (pointEquals(a,d) && pointEquals(b,c))) { return true; }
|
||||
|
||||
final float t = (a.x-c.x) * (c.y-d.y) - (a.y-c.y) * (c.x-d.x);
|
||||
final float u = (b.x-a.x) * (a.y-c.y) - (b.y-a.y) * (a.x-c.x);
|
||||
final float D = (a.x-b.x) * (c.y-d.y) - (a.y-b.y) * (c.x-d.x);
|
||||
|
||||
if (D == 0) { return false; }
|
||||
|
||||
final float td = t / D;
|
||||
final float ud = u / D;
|
||||
if (td >= 0 && td <= 1 && ud >= 0 && ud <= 1) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean pointEquals(PVector A, PVector B) {
|
||||
return (A.x == B.x && A.y == B.y);
|
||||
}
|
Loading…
Reference in New Issue
Block a user