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;
}
}
}
2I don't understand; can't you just delete all the edges at the beginning except the ones that form the longest path? – Peter Olson – 2014-09-08T21:16:50.347
3I don't see any rule showing that the drunkard can't re-walk the same edge twice. If he can take the same path between two points twice, and chooses turns at random, then logically isn't graph with the longest average (random) traversal the one with the most edges? That is, wouldn't the optimal (longest) graph be the one with no deleted edges? – millinon – 2014-09-08T21:19:58.770
I think you could make it so that he'll never reach home. For example remove all but the bottom most and right most edges. As he approaches the right most edge disable the right most edges and enable the left most and top most edges. Switch as required. He should end up wandering around on the bottom edge of the grid indefinitely – MickyT – 2014-09-08T21:28:47.300
@millinon We talked a bit about it in chat. For example, assume you have a full grid, and let the drunkard walk until he gets one space from the finish. Then delete that edge forcing him to find the other edge leading to finish. It was previously a 1/3 chance he'd finish on that turn, now it's zero, so on average it would be longer. – Geobits – 2014-09-08T21:29:22.937
@millinon We discussed this in the chat room before this was posted. There is definitely a better approach than simply deleting all the edges to make the longest single path. Consider this very simple better approach: Delete edges leaving TWO equal length paths to the exit. Wait for him to get to the end of one of those paths, then delete the edge between him and the exit. Now he has to traverse a single path back to start and to the exit, almost as long as your originally proposed single path, on top of the traversal of a half-length path he's already made.
There are much better solutions. – Sparr – 2014-09-08T21:29:35.900
@MickyT You can't add paths back in. – Geobits – 2014-09-08T21:29:48.103
@MickyT you cannot create/enable edges, only delete/close/disable them. – Sparr – 2014-09-08T21:30:01.193
3I am not a fan of requiring every entry to reinvent the wheel (walker). If someone posts a test harness/framework then I will upvote them and use it. – Sparr – 2014-09-08T21:32:26.793
1The advantage of removing a part of a path to make him go back to take the long way around is completely lost when his path is random; supposedly it's equally likely that he'll turn back at some point without needing you to remove an edge. I'd like to see some test data showing the average time with no edges removed, and then with certain edges removed as you seem to suggest. As far as this challenge, I think it would be much more interesting if the drunkard's path were deterministic. – millinon – 2014-09-08T21:42:51.577
@Geobits I thought I must of misread it – MickyT – 2014-09-08T21:50:45.427
@millinon I argued the same at first (and would still like to see the comparison, to be honest). In my example, though, the odds that he'll turn around are not the same without removing an edge. Before removing, he has a 2/3 chance of walking away from the final spot. After removing, he has a 100% chance of walking away. A deterministic path wouldn't be as interesting, because there would (I believe) be one optimal path. – Geobits – 2014-09-08T22:01:46.293
1I definitely see your point about the probability of redirecting him. However, I'd say that writing code to defeat a random walker would be much less interesting than writing code to defeat a walker that uses some kind of predictable mechanism. For example, I would prevent him from walking the same edge twice, and would introduce a backtracking behavior when he's unable to proceed from a certain point. – millinon – 2014-09-08T22:08:42.137
310 rounds is not nearly enough. Even with a static 10x10 maze, let alone an intelligent adversary and a 100x100 maze, the standard deviation is around 50% of the average case. I'm running 10000 rounds and I still wouldn't consider the results comparison-worthy. – Sparr – 2014-09-08T23:03:33.393
1Also, I'm finding plenty of depth of strategy on a 10x10 grid. Making it 100x100 just makes testing slower. I don't think the larger grid makes the challenge any deeper. – Sparr – 2014-09-08T23:56:18.523
It's really (n+1) by (n+1). – feersum – 2014-09-09T01:31:27.327
1@millinon I agree that the code to defeat a random walker will be 'boring', but as a comparison of the difference between steps in a complete and partially deleted grid: Average steps for the complete grid (100 trials) ~ 120,000 (st.dev. ~ 115,000); Average steps for a grid reduced to a 110 step 1-D trail ~ 97,000,000 (st. dev. ~86,000,000); So the edge deleted path takes almost 1000 times as long to complete.. – Penguino – 2014-09-09T02:57:00.743
@Penguino Awesome, thanks! I'm legitimately surprised by the result - it's an interesting problem to think about. – millinon – 2014-09-09T03:17:27.873
@Sparr You are right that 10 rounds may not be enough to distinguish cases. I set it low in case anyone manages to get the walks to be really long. I'll add something about this to the question. – None – 2014-09-09T06:34:28.063
In that case, I think it's better to use 10x10 maze, and repeat 10,000 times for more consistent scoring, instead of using 100x100 and repeating it less times. And the restriction of not deleting a path he already walked really limit the possibility of strategy. Can you remove that one? I think the restriction of not being able to reinclude path is already sufficient to make this interesting. – justhalf – 2014-09-09T07:05:48.107
@justhalf I would rather not decrease the size of the maze for the competition. You can of course test your code on different size mazes and report them and I am sure other people would be interested. One advantage of not deleting an edge he has already walked is that combined with the other rule you only ever need to test if there is still a route from the bottom left to the top right (you don't need to check if you can get back to the beginning). It's also more realistic in a sense. – None – 2014-09-09T07:11:07.463
Actually I would even suggest to use a set of small mazes (say, 4x4, 5x5, and 6x6) for the competition. And I don't agree that this makes it more realistic. But anyway, this is your competition, so I'll follow. Btw, to clarify, in your grid pictured above, is it 10x10 or 11x11? – justhalf – 2014-09-09T07:17:04.380
@justhalf I think those smaller examples would be very interesting. They just won't add to the final score. The graph is 11x11. – None – 2014-09-09T07:18:22.677
I think you really need to change the maze size. Even in 10x10 maze, my adversary took a few minutes to complete 1000 runs, resulting in average of 165,746 moves. I tried on 100x100 and it hasn't even finish one iteration. – justhalf – 2014-09-09T07:24:45.323
@justhalf I am amazed you got 165,746 moves for 10x10! Just run 100x100 for as many iterations as you can and report it. You can even report a histogram of times. It seems you may have found a very clever adversary! – None – 2014-09-09T07:28:44.580
That's actually far worse compared to the obvious single path, which is about 5mil (100 runs). So I think this question is not that balanced. – justhalf – 2014-09-09T08:25:29.073
1Hmm, sorry, I think there was some bugs in my previous implementations (this is another reason why giving the Walker class is important). It's only 7k instead of 165k. – justhalf – 2014-09-09T08:48:01.640
@justhalf 7k makes much more sense. Good catch! – None – 2014-09-09T09:23:28.847
@Optimizer I will run the code for my own interest and also to make sure the answers make sense. I find the whole problem fascinating . – None – 2014-09-09T11:57:39.210
It turns out that the question isn't clear enough about what it means by an "n by n graph". Assuming that the walker starts at
(0, 0), is he trying to get to(99, 99)or(100, 100)? Different answers have made different assumptions. – Peter Taylor – 2014-09-09T17:34:42.097@PeterTaylor He is trying to get to (99,99). – None – 2014-09-09T19:43:06.137
1A potential problem I see is the quality of the PRNGs. C's
rand()varies with the libraries, but most (if not all) are pretty awful. Java seems to be better, but not by much. Forgive me for beating a dead horse, but this is one of the issues that could have been prevented by using a single walker. – Dennis – 2014-09-10T19:48:30.370This would have been more fun if you have to remove one edge at a time, until you cannot remove any more edges (paths) per the rules. – ja72 – 2014-09-12T17:00:38.287
@ja72 That potentially makes a nice follow up question :) How different are the best answers to following that rule? – None – 2014-09-12T17:01:41.380
Since you won't be able to remove any path from the current location to home it becomes increasingly likely that the random path would take him home. It would be a futile race to delay the arrival. – ja72 – 2014-09-12T17:16:29.967
Has anybody tried a fractal pattern to maximize length to area ratio? – ja72 – 2014-09-12T17:17:22.537
The maximum length-to-area ratio is
n^2-1, which can be simply achieved by creating single path, like most answers have done. Also a good dynamic solution will remove zero, one, or two edges at a time, so I don't think it's that different from current best answer, perhaps just the score will be lower if you require exactly one edge to be removed at each step, since it basically removes the dynamicity of the walker. – justhalf – 2014-09-15T08:06:35.130All of the current good solutions can be implemented as removing a single edge at a time. The scores would be effectively identical. – Sparr – 2014-09-15T21:46:21.770