230,794.38 on 20x20, 100k runs
Latest Update: I finally built perfect dynamic 2-path solution. I said perfect since the previous version is actually not symmetric, it was easier to get longer path if the drunkard took one path over the other. The current one is symmetric, so it can get higher expected number of steps. After few trials, it seems to be around 230k, an improvement over the previous one which is about 228k. But statistically speaking those numbers are still within their huge deviation, so I don't claim that this is significantly better, but I believe this should be better than the previous version.
The code is at the bottom of this post. It is updated so that it's much faster than the previous version, completing 1000 runs in 23s.
Below is sample run and sample maze:
Perfect Walker
Average: 230794.384
Max: 1514506
Min:25860
Completed in 2317.374s
_ _ _ _ _ _ _ _ _ _ _ _.
| | | | | | | | | | | | | | | _ _ _ _
| | | | | | | | | | | | | | | |_ _ _ _
| | | | | | | | | | | | | | | _ _ _ _|
| | | | | | | | | | | | | | | |_ _ _ _
| | | | | | | | | | | | | | | _ _ _ _|
| | | | | | | | | | | | | | | |_ _ _ _
| | | | | | | | | | | | | | | _ _ _ _|
| | | | | | | | | | | | | |_| |_ _ _ _
| | | | | | | | | | | | | _ _ _ _ _ _|
| | | | | | | | | | | | | |_ _ _ _ _ _
| | | | | | | | | | | | | _ _ _ _ _ _|
| | | | | | | | | | | | | |_ _ _ _ _ _
| | | | | | | | | | | | | _ _ _ _ _ _|
| | | | | |_| |_| |_| |_| |_ _ _ _ _ _
| | | | | _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| | | | | |_ _ _ _ _ _ _ _ _ _ _ _ _ _
| | | | | _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_| |_| |_ _ _ _ _ _ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Previous submissions
Finally I can match Sparr's result! =D
Based on my previous experiments (see bottom of this post), the best strategy is to have double path and close one as the drunkard reaches any of them, and the variable comes from how good we can dynamically predict where the drunkard will go as to increase the chance of him getting into longer path.
So based on my DOUBLE_PATH
strategy, I built another one, which changes the maze (my DOUBLE_PATH
maze was easily modifiable) depending on the drunkard movement. As he takes a path with more than one available options, I will close the paths so as to leave only two possible options (one from which he came, another the untravelled).
This sounds similar to what Sparr has achieved, as the result shows. The difference with his is too small for it to be considered better, but I would say that my approach is more dynamic than him, since my maze is more modifiable than Sparr's =)
The result with a sample final maze:
EXTREME_DOUBLE_PATH
Average: 228034.89
Max: 1050816
Min:34170
Completed in 396.728s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _| |_ _ _ _ _ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Experiments Section
The best turns out to be the same strategy as stokastic, I take pride in experimenting using various strategies and printing nice outputs :)
Each of the printed maze below is the last maze after the drunkard has reached home, so they might be slightly different from run to run due to the randomness in the drunkard movement and dinamicity of the adversary.
I'll describe each strategy:
Single Path
This is the simplest approach, which will create a single path from entry to exit.
SINGLE_PATH
Average: 162621.612
Max: 956694
Min:14838
Completed in 149.430s
_ _ _ _ _ _ _ _ _ _
| |_| |_| |_| |_| |_| |_| |_| |_| |_| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Island (level 0)
This is an approach that tries to trap the drunkard in an almost isolated island. Doesn't work as good as I expected, but this is one of my first ideas, so I include it.
There are two paths leading to the exit, and when the drunkard gets near to one of them, the adversary closes it, forcing him to find the other exit (and possibly gets trapped again in the island)
ISLAND
Average: 74626.070
Max: 428560
Min:1528
Completed in 122.512s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
| |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Double Path
This is the most discussed strategy, which is to have two equal length paths to the exit, and close one of them as the drunkard gets near to one of them.
DOUBLE_PATH
Average: 197743.472
Max: 1443406
Min:21516
Completed in 308.177s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
_ _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ _
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Island (level 1)
Inspired by the multiple paths of island and the high walk count in single path, we connect the island to the exit and make single path maze in the island, creating in total three paths to exit, and similar to previous case, close any of the exit as the drunkard gets near.
This works slightly better than pure single path, but still doesn't defeat the double path.
ISLAND
Average: 166265.132
Max: 1162966
Min:19544
Completed in 471.982s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _|_
| | |_| |_| |_| |_| |_| |_| |_| |_| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Island (level 2)
Trying to expand the previous idea, I created nested island, creating in total five paths, but it doesn't seem to work that well.
ISLAND
Average: 164222.712
Max: 927608
Min:22024
Completed in 793.591s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_
| | _ _ _ _ _ _ _ _|_|
| | | |_| |_| |_| |_| |_| |_| |_| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
|_|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Island (level 3)
Noticing that double path actually works better than single path, let's make the island in double path!
The result is an improvement over Island (level 1), but it still doesn't beat pure double path.
For comparison, the result for double path of the size of the island is 131,134.42 moves on average. So this does add quite significant number of moves (around 40k), but not enough to beat double path.
ISLAND
Average: 171730.090
Max: 769080
Min:29760
Completed in 587.646s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
| _ _ _ _ _ _ _ _| |_ _ _ _ _ _ _ _ |
| |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Island (level 4)
Again, experimenting with nested island, and again it doesn't work so well.
ISLAND
Average: 149723.068
Max: 622106
Min:25752
Completed in 830.889s
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_|
| | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_|
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| | |_ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
| | _ _ _ _ _ _ _| |_ _ _ _ _ _ _ | |
| |_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _| | |
|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
Conclusion
All in all, this proves that having a single long path from drunkard current position to the exit works best, which is achieved by the double path strategy, since after closing an exit, the drunkard will have to travel the maximum distance possible to get to the exit.
This further hints that the basic strategy should still be double path, and we can only modify how dynamic the paths are created, which has been done by Sparr. So I believe his strategy is the way to go!
Code
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.TreeSet;
public class Walker {
enum Strategy{
SINGLE_PATH,
ISLAND,
DOUBLE_PATH,
EXTREME_DOUBLE_PATH,
PERFECT_DOUBLE_PATH,
}
int width,height;
int x,y; //walker's position
int dX,dY; //destination
Point[][] points;
int stepCount = 0;
public static void main(String[]args){
int side = 20;
// runOnce(side, Strategy.EXTREME_DOUBLE_PATH, 0);
runOnce(side, Strategy.PERFECT_DOUBLE_PATH, 0);
// for(Strategy strategy: Strategy.values()){
// runOnce(side, strategy, 0);
// }
// runOnce(side, Strategy.ISLAND, 1);
// runOnce(side, Strategy.ISLAND, 2);
// Scanner scanner = new Scanner(System.in);
// System.out.println("Enter side, strategy (SINGLE_PATH, ISLAND, DOUBLE_PATH, EXTREME_DOUBLE_PATH), and level:");
// while(scanner.hasNext()){
// side = scanner.nextInt();
// Strategy strategy = Strategy.valueOf(scanner.next());
// int level = scanner.nextInt();
// scanner.nextLine();
// runOnce(side, strategy, level);
// System.out.println("Enter side, strategy (SINGLE_PATH, ISLAND, DOUBLE_PATH, EXTREME_DOUBLE_PATH), and level:");
// }
// scanner.close();
}
private static Walker runOnce(int side, Strategy strategy, int level) {
Walker walker = null;
long total = 0;
int max = 0;
int min = Integer.MAX_VALUE;
double count = 1000;
long start = System.currentTimeMillis();
for(int i=0; i<count; i++){
walker = new Walker(0,0,side,side,side-1,side-1, strategy, level, false);
total += walker.stepCount;
max = Math.max(walker.stepCount, max);
min = Math.min(walker.stepCount, min);
// System.out.println("Iteration "+i+": "+walker.stepCount);
}
System.out.printf("%s\nAverage: %.3f\nMax: %d\nMin:%d\n",strategy, total/count, max, min);
System.out.printf("Completed in %.3fs\n", (System.currentTimeMillis()-start)/1000.0);
walker.printPath();
return walker;
}
private void createIsland(int botLeftX, int botLeftY, int topRightX, int topRightY){
for(int i=botLeftY+1; i<topRightY; i++){
if(i>botLeftY+1) deletePath(points[botLeftX][i].right());
if(i<topRightY-1) deletePath(points[topRightX][i].left());
}
for(int i=botLeftX+1; i<topRightX; i++){
if(i>botLeftX+1) deletePath(points[i][botLeftY].up());
if(i<topRightX-1) deletePath(points[i][topRightY].down());
}
}
private void createSinglePath(int botLeftX, int botLeftY, int topRightX, int topRightY){
for(int i=botLeftY; i<topRightY; i++){
if(i==topRightY-1 && (topRightY+1-botLeftY)%2==0){
for(int j=botLeftX; j<topRightX; j++){
if(j==topRightX-1 && (j-botLeftX)%2==0){
deletePath(points[topRightX][topRightY].down());
} else {
deletePath(points[j][topRightY-1+((j-botLeftX)%2)].right());
}
}
} else {
for(int j=botLeftX+(i-botLeftY)%2; j<topRightX+((i-botLeftY)%2); j++){
deletePath(points[j][i].up());
}
}
}
}
private void createDoublePath(int botLeftX, int botLeftY, int topRightX, int topRightY){
for(int i=botLeftY; i<topRightY; i++){
if(i>botLeftY && (width%4!=1 || i<topRightY-1)) deletePath(points[width/2-1][i].right());
if(i==topRightY-1 && (topRightY+1-botLeftY)%2==1){
for(int j=botLeftX; j<topRightX; j++){
if((j-botLeftX)%2==0 || j<topRightX-1){
deletePath(points[j][topRightY-1+((j-botLeftX)%2)].right());
} else {
deletePath(points[topRightX-1][topRightY-1].right());
}
}
} else {
if((i-botLeftY)%2==0){
for(int j=botLeftX+1; j<topRightX; j++){
deletePath(points[j][i].up());
}
} else {
for(int j=botLeftX; j<topRightX+1; j++){
if(j!=width/2 && j!=width/2-1){
deletePath(points[j][i].up());
}
}
}
}
}
}
public Walker(int startingX,int startingY, int Width, int Height, int destinationX, int destinationY, Strategy strategy, int level, boolean animate){
width = Width;
height = Height;
dX = destinationX;
dY = destinationY;
x=startingX;
y=startingY;
points = new Point[width][height];
for(int y=0; y<height; y++){
for(int x=0; x<width; x++){
points[x][y] = new Point(x,y);
}
}
for(int y=0; y<height; y++){
for(int x=0; x<width; x++){
if(x<width-1) new Edge(points[x][y], points[x+1][y]);
if(y<height-1) new Edge(points[x][y], points[x][y+1]);
}
}
if(strategy == Strategy.SINGLE_PATH) createSinglePath(0,0,width-1,height-1);
if(strategy == Strategy.DOUBLE_PATH) createDoublePath(0,0,width-1,height-1);
List<EdgeList> edgeLists = new ArrayList<EdgeList>();
if(strategy == Strategy.ISLAND){
List<Edge> edges = new ArrayList<Edge>();
if(level==0){
createIsland(0,0,width-1,height-1);
deletePath(points[width-2][height-2].right());
deletePath(points[width-2][height-2].up());
} else {
for(int i=0; i<level; i++){
createIsland(i,i,width-1-i, height-1-i);
}
createDoublePath(level,level,width-1-level,height-1-level);
for(int i=height-1; i>=height-level; i--){
edges.add(points[i-2][i].right());
edges.add(points[i][i-2].up());
edgeLists.add(new EdgeList(points[i-1][i].right(), points[i][i-1].up()));
}
}
edges.add(points[width-1-level][height-1-level].down());
edges.add(points[width-1-level][height-1-level].left());
edgeLists.add(new EdgeList(edges.toArray(new Edge[0])));
}
int[] availableVerticals = new int[height];
if(strategy == Strategy.EXTREME_DOUBLE_PATH){
for(int i=1; i<width-1; i++){
deletePath(points[i][0].up());
}
availableVerticals[0] = 2;
for(int i=1; i<height; i++){
availableVerticals[i] = width;
}
}
boolean[][] available = new boolean[width][height];
if(strategy == Strategy.PERFECT_DOUBLE_PATH){
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
if(x%2==1 && y%2==1){
available[x][y] = true;
} else {
available[x][y] = false;
}
}
}
}
// printPath();
while(!walk()){
if(animate)try{Thread.sleep(500);}catch(InterruptedException e){}
if(strategy == Strategy.ISLAND){
if(x==y && (x==1 || (x>=2 && x<=level))){
if(!hasBeenWalked(points[x][x].down())){
deletePath(points[x][x].down());
} else if(!hasBeenWalked(points[x][x].left())){
deletePath(points[x][x].left());
}
}
}
if(strategy == Strategy.EXTREME_DOUBLE_PATH){
Point cur = points[x][y];
int untravelled = 0;
for(Edge edge: cur.edges) if(edge!=null && !edge.walked) untravelled++;
if(untravelled>1){
if(cur.up()!=null && availableVerticals[y]>2 && !cur.up().walked){
deletePath(cur.up());
availableVerticals[y]--;
}
if(cur.down()!=null && !cur.down().walked){
deletePath(cur.down());
availableVerticals[y-1]--;
}
if(cur.up()!=null && cur.left()!=null && !cur.left().walked){
deletePath(cur.left());
deletePath(points[x][y+1].left());
}
if(cur.up()!=null && cur.right()!=null && !cur.right().walked){
deletePath(cur.right());
if(y<height-1) deletePath(points[x][y+1].right());
}
}
}
if(strategy == Strategy.PERFECT_DOUBLE_PATH){
Point cur = points[x][y];
int untravelled = 0;
for(Edge edge: cur.edges) if(edge!=null && !edge.walked) untravelled++;
if(x%2!=1 || y%2!=1){
if(untravelled>1){
if(cur.down()==null && hasBeenWalked(cur.right())){
if(canBeDeleted(cur.up())) deletePath(cur.up());
}
if(cur.down()==null && hasBeenWalked(cur.left())){
if(x%2==0 && y%2==1 && canBeDeleted(cur.right())) deletePath(cur.right());
else if(cur.right()!=null && canBeDeleted(cur.up())) deletePath(cur.up());
}
if(cur.left()==null && hasBeenWalked(cur.up())){
if(canBeDeleted(cur.right())) deletePath(cur.right());
}
if(cur.left()==null && hasBeenWalked(cur.down())){
if(x%2==1 && y%2==0 && canBeDeleted(cur.up())) deletePath(cur.up());
else if (cur.up()!=null && canBeDeleted(cur.right())) deletePath(cur.right());
}
}
} else {
if(!hasBeenWalked(cur.left())){
if(x>1 && available[x-2][y]){
if(untravelled>1){
available[x-2][y] = false;
deletePath(cur.up());
}
} else if(cur.up()!=null){
if(canBeDeleted(cur.left())) deletePath(cur.left());
if(canBeDeleted(points[x][y+1].left())) deletePath(points[x][y+1].left());
}
}
if(!hasBeenWalked(cur.down())){
if(y>1 && available[x][y-2]){
if(untravelled>1){
available[x][y-2] = false;
deletePath(cur.right());
}
} else if(cur.right()!=null){
if(canBeDeleted(cur.down())) deletePath(cur.down());
if(canBeDeleted(points[x+1][y].down())) deletePath(points[x+1][y].down());
}
}
}
}
if(strategy == Strategy.DOUBLE_PATH || strategy == Strategy.EXTREME_DOUBLE_PATH
|| strategy == Strategy.PERFECT_DOUBLE_PATH){
if(x==width-2 && y==height-1 && points[width-1][height-1].down()!=null){
deletePath(points[width-1][height-1].left());
}
if(x==width-1 && y==height-2 && points[width-1][height-1].left()!=null){
deletePath(points[width-1][height-1].down());
}
} else if(strategy == Strategy.ISLAND){
for(EdgeList edgeList: edgeLists){
boolean deleted = false;
for(Edge edge: edgeList.edges){
if(edge.start.x == x && edge.start.y == y){
if(!hasBeenWalked(edge)){
deletePath(edge);
edgeList.edges.remove(edge);
if(edgeList.edges.size() == 1){
edgeLists.remove(edgeList);
}
deleted = true;
break;
}
}
}
if(deleted) break;
}
}
if(animate)printPath();
}
}
public boolean hasBeenWalked(Edge edge){
if(edge == null) return false;
return edge.walked;
}
public boolean canBeDeleted(Edge edge){
if(edge == null) return false;
return !edge.walked;
}
public List<Edge> getAdjacentUntravelledEdges(){
List<Edge> result = new ArrayList<Edge>();
for(Edge edge: points[x][y].edges){
if(edge!=null && !hasBeenWalked(edge)) result.add(edge);
}
return result;
}
public void printPath(){
StringBuilder builder = new StringBuilder();
for(int y=height-1; y>=0; y--){
for(int x=0; x<width; x++){
Point point = points[x][y];
if(this.x==x && this.y==y){
if(point.up()!=null) builder.append('?');
else builder.append('.');
} else {
if(point.up()!=null) builder.append('|');
else builder.append(' ');
}
if(point.right()!=null) builder.append('_');
else builder.append(' ');
}
builder.append('\n');
}
System.out.print(builder.toString());
}
public boolean walk(){
ArrayList<Edge> possibleMoves = new ArrayList<Edge>();
Point cur = points[x][y];
for(Edge edge: cur.edges){
if(edge!=null) possibleMoves.add(edge);
}
int random = (int)(Math.random()*possibleMoves.size());
Edge move = possibleMoves.get(random);
move.walked = true;
if(move.start == cur){
x = move.end.x;
y = move.end.y;
} else {
x = move.start.x;
y = move.start.y;
}
stepCount++;
if(x==dX && y == dY){
return true;
} else {
return false;
}
}
public boolean isSolvable(){
TreeSet<Point> reachable = new TreeSet<Point>();
Queue<Point> next = new LinkedList<Point>();
next.offer(points[x][y]);
reachable.add(points[x][y]);
while(next.size()>0){
Point cur = next.poll();
ArrayList<Point> neighbors = new ArrayList<Point>();
if(cur.up()!=null) neighbors.add(cur.up().end);
if(cur.right()!=null) neighbors.add(cur.right().end);
if(cur.down()!=null) neighbors.add(cur.down().start);
if(cur.left()!=null) neighbors.add(cur.left().start);
for(Point neighbor: neighbors){
if(!reachable.contains(neighbor)){
if(neighbor == points[dX][dY]) return true;
reachable.add(neighbor);
next.offer(neighbor);
}
}
}
return false;
}
public boolean deletePath(Edge toDelete){
if(toDelete == null) return true;
// if(toDelete.walked){
// System.err.println("Edge already travelled!");
// return false;
// }
int startIdx = toDelete.getStartIdx();
int endIdx = toDelete.getEndIdx();
toDelete.start.edges[startIdx] = null;
toDelete.end.edges[endIdx] = null;
// if(!isSolvable()){
// toDelete.start.edges[startIdx] = toDelete;
// toDelete.end.edges[endIdx] = toDelete;
// System.err.println("Invalid deletion!");
// return false;
// }
return true;
}
static class EdgeList{
List<Edge> edges;
public EdgeList(Edge... edges){
this.edges = new ArrayList<Edge>();
this.edges.addAll(Arrays.asList(edges));
}
}
static class Edge implements Comparable<Edge>{
Point start, end;
boolean walked;
public Edge(Point start, Point end){
walked = false;
this.start = start;
this.end = end;
this.start.edges[getStartIdx()] = this;
this.end.edges[getEndIdx()] = this;
if(start.compareTo(end)>0){
Point tmp = end;
end = start;
start = tmp;
}
}
public Edge(int x1, int y1, int x2, int y2){
this(new Point(x1,y1), new Point(x2,y2));
}
public boolean exists(){
return start.edges[getStartIdx()] != null || end.edges[getEndIdx()] != null;
}
public int getStartIdx(){
if(start.x == end.x){
if(start.y < end.y) return 0;
else return 2;
} else {
if(start.x < end.x) return 1;
else return 3;
}
}
public int getEndIdx(){
if(start.x == end.x){
if(start.y < end.y) return 2;
else return 0;
} else {
if(start.x < end.x) return 3;
else return 1;
}
}
public boolean isVertical(){
return start.x==end.x;
}
@Override
public int compareTo(Edge o) {
int result = start.compareTo(o.start);
if(result!=0) return result;
return end.compareTo(o.end);
}
}
static class Point implements Comparable<Point>{
int x,y;
Edge[] edges;
public Point(int x, int y){
this.x = x;
this.y = y;
edges = new Edge[4];
}
public Edge up(){ return edges[0]; }
public Edge right(){ return edges[1]; }
public Edge down(){ return edges[2]; }
public Edge left(){ return edges[3]; }
public int compareTo(Point o){
int result = Integer.compare(x, o.x);
if(result!=0) return result;
result = Integer.compare(y, o.y);
if(result!=0) return result;
return 0;
}
}
}