2012-02-10 93 views
2

我正在開發一款適用於Android的遊戲,其中可探索區域將隨機生成。現在,我只是試圖讓迷宮生成(有一些ASCII藝術輸出,所以我可以看到它),我已經在這裏約4-5天,但我只是難住。使用非遞歸回溯算法生成迷宮的問題

我正在嘗試使用「深度優先搜索」算法,並且我可以找到的所有示例都使用遞歸回溯。由於這是Android和電話相對懦弱,遞歸很快導致調用堆棧溢出,這就是爲什麼我試圖寫一個自己的算法使用堆棧回溯。

我想出了這個解決方案,使用MazeGenerator類和MazeCell類。

MazeGenerator:

package com.zarokima.mistwalkers.explore; 

import java.util.Random; 
import java.util.Stack; 
import org.anddev.andengine.util.path.Direction; 
import android.graphics.Point; 

public class MazeGenerator 
{ 
private int x, y; // the dimensions of the maze 
private MazeCell[][] maze; 
private Random rand = new Random(); 
private Stack<MazeCell> stack; 

public MazeGenerator(int x, int y) 
{ 
    this.x = x; 
    this.y = y; 
    generateMaze(); 
} 

public void setSeed(long seed) 
{ 
    rand.setSeed(seed); 
} 

public void setSize(int x, int y) 
{ 
    this.x = x; 
    this.y = y; 
} 

public String outputMazeText() 
{ 
    String output = new String(); 
    for (int i = 0; i < y; i++) 
    { 
     // draw the north edge 
     for (int k = 0; k < x; k++) 
     { 
      output += maze[k][i].hasNeighbor(Direction.UP) ? "+ " : "+---"; 
     } 
     output += "+\n"; 
     // draw the west edge 
     for (int k = 0; k < x; k++) 
     { 
      output += maze[k][i].hasNeighbor(Direction.LEFT) ? " " : "| "; 
     } 
     output += "|\n"; 
    } 
    // draw the bottom line 
    for (int k = 0; k < x; k++) 
    { 
     output += "+---"; 
    } 
    output += "+\n"; 

    return output; 
} 

public void generateMaze() 
{ 
    maze = new MazeCell[x][y]; 
    for (int i = 0; i < x; i++) 
    { 
     for (int k = 0; k < y; k++) 
     { 
      maze[i][k] = new MazeCell(i, k); 
     } 
    } 

    MazeCell.setBounds(x, y); 

    stack = new Stack<MazeCell>(); 
    stack.push(maze[0][0]); 
    maze[0][0].setInMaze(true); 

    while (!stack.isEmpty()) 
    { 
     MazeCell currentCell = stack.peek(); 

     Direction[] possibleDirections = currentCell.getUncheckedDirections(); 

     if (possibleDirections.length == 0) 
     { 
      stack.pop(); 
      continue; 
     } 

     int dint = rand.nextInt(possibleDirections.length); 
     Direction direction = possibleDirections[dint]; 

     MazeCell nextCell = null; 
     Point position = currentCell.getPosition(); 

     switch (direction) 
     { 
      case UP: 
       nextCell = maze[position.x][position.y - 1]; 
       break; 
      case DOWN: 
       nextCell = maze[position.x][position.y + 1]; 
       break; 
      case LEFT: 
       nextCell = maze[position.x - 1][position.y]; 
       break; 
      case RIGHT: 
       nextCell = maze[position.x + 1][position.y]; 
       break; 
     } 

     currentCell.setNeighbor(nextCell, direction); 

     stack.push(nextCell); 
    } 
} 
} 

MazeCell:

package com.zarokima.mistwalkers.explore; 

import java.util.ArrayList; 
import org.anddev.andengine.util.path.Direction; 
import android.graphics.Point; 

public class MazeCell 
{ 
private MazeCell[] neighbors; 
private boolean[] checked; 
private boolean inMaze = false; 
private Point position; 
private static boolean setNeighbor = true; //whether the next call of SetNeighbor() should also call for the new neighbor 
private static int xMax = 10, yMax = 10; //exclusive boundary for position 
private int mapIndex; //will be used when maze generation is working properly 

public MazeCell(int x, int y) 
{ 
    position = new Point(x,y); 
    neighbors = new MazeCell[4]; 
    checked = new boolean[4]; 
    for(int i = 0; i < neighbors.length; i++) 
    { 
     neighbors[i] = null; 
    } 
} 

public Point getPosition() 
{ 
    return position; 
} 

public void setInMaze(boolean b) 
{ 
    inMaze = b; 
} 

public static void setBounds(int x, int y) 
{ 
    xMax = x; 
    yMax = y; 
} 

public void setNeighbor(MazeCell c, Direction d) 
{ 
    checked[d.ordinal()] = true; 
    switch(d) 
    { 
     case UP: 
      if(!c.hasNeighbor(Direction.DOWN) && !c.isInMaze()); 
      { 
       if(setNeighbor) 
       { 
        setNeighbor = false; 
        c.setNeighbor(this, Direction.DOWN); 
       } 
       neighbors[d.ordinal()] = c; 
      } 
      break; 
     case DOWN: 
      if(!c.hasNeighbor(Direction.UP) && !c.isInMaze()) 
      { 
       if(setNeighbor) 
       { 
        setNeighbor = false; 
        c.setNeighbor(this, Direction.UP); 
       } 
       neighbors[d.ordinal()] = c; 
      } 
      break; 
     case LEFT: 
      if(!c.hasNeighbor(Direction.RIGHT) && !c.isInMaze()) 
      { 
       if(setNeighbor) 
       { 
        setNeighbor = false; 
        c.setNeighbor(this, Direction.RIGHT); 
       } 
       neighbors[d.ordinal()] = c; 
      } 
      break; 
     case RIGHT: 
      if(!c.hasNeighbor(Direction.LEFT) && !c.isInMaze()) 
      { 
       if(setNeighbor) 
       { 
        setNeighbor = false; 
        c.setNeighbor(this, Direction.LEFT); 
       } 
       neighbors[d.ordinal()] = c; 
      } 
      break; 

    } 
    setNeighbor = true; 
    inMaze = true; 
} 

public void setDirectionChecked(Direction d, boolean b) 
{ 
    checked[d.ordinal()] = b; 
} 

public boolean hasNeighbor(Direction d) 
{ 
    return (neighbors[d.ordinal()] != null); 
} 

public MazeCell getNeighbor(Direction d) 
{ 
    return neighbors[d.ordinal()]; 
} 

public boolean isInMaze() 
{ 
    return inMaze; 
} 

public Direction[] getUncheckedDirections() 
{ 
    ArrayList<Direction> al = new ArrayList<Direction>(); 

    for(Direction d : Direction.values()) 
    { 
     //boundary cases 
     switch(d) 
     { 
      case UP: 
       if(position.y == 0) 
        continue; 
       break; 
      case DOWN: 
       if(position.y == yMax-1) 
        continue; 
       break; 
      case LEFT: 
       if(position.x == 0) 
        continue; 
       break; 
      case RIGHT: 
       if(position.x == xMax-1) 
        continue; 
       break; 
     } 
     if(checked[d.ordinal()] == false) 
      al.add(d); 
    } 

    Direction[] d = new Direction[al.size()]; 
    for(int i = 0; i < d.length; i++) 
     d[i] = al.get(i); 

    return d; 
} 
} 

這產生的結果看起來像this

注意如何每一個細胞都始終連接到它的上下鄰居。我一直無法弄清楚這裏有什麼問題。

儘管MazeCell的setNeighbor函數中的檢查看起來應該足夠了,但我還是多加了一些,看看會發生什麼。下面是第二generateMaze()方法:

public void generateMaze() 
{ 
    maze = new MazeCell[x][y]; 
    for (int i = 0; i < x; i++) 
    { 
     for (int k = 0; k < y; k++) 
     { 
      maze[i][k] = new MazeCell(i, k); 
     } 
    } 

    MazeCell.setBounds(x, y); 

    stack = new Stack<MazeCell>(); 
    stack.push(maze[0][0]); 
    maze[0][0].setInMaze(true); 

    while (!stack.isEmpty()) 
    { 
     MazeCell currentCell = stack.peek(); 

     Direction[] possibleDirections = currentCell.getUncheckedDirections(); 

     if (possibleDirections.length == 0) 
     { 
      stack.pop(); 
      continue; 
     } 

     int dint = rand.nextInt(possibleDirections.length); 
     Direction direction = possibleDirections[dint]; 
     currentCell.setDirectionChecked(direction, true); 

     MazeCell nextCell = null; 
     Point position = currentCell.getPosition(); 

     switch (direction) 
     { 
      case UP: 
       nextCell = maze[position.x][position.y - 1]; 
       break; 
      case DOWN: 
       nextCell = maze[position.x][position.y + 1]; 
       break; 
      case LEFT: 
       nextCell = maze[position.x - 1][position.y]; 
       break; 
      case RIGHT: 
       nextCell = maze[position.x + 1][position.y]; 
       break; 
     } 

     if (!nextCell.isInMaze()) 
     { 
      currentCell.setNeighbor(nextCell, direction); 

      stack.push(nextCell); 
     } 
    } 

並可以產生像this

通知段如何各個擊破結果。

我已經玩過很多不僅僅是這裏提到的東西,但沒有任何顯示任何真正的改進 - 大多數最終只是看起來像第二張照片。任何幫助?

+0

即使您使用自己的堆棧,回溯也是按照定義遞歸的。 – osa 2013-10-15 19:45:19

回答

1

我建議創建一個名爲Direction oppositeOf(Direction d)(具有明顯的邏輯)的函數。如果添加該功能,您可以完全刪除setNeighbor中的開關語句。 在這裏,我已經重寫setNeighbor有與上述完全相同的邏輯,只要使用此功能:

public void setNeighbor(MazeCell c, Direction d) 
    { 
     checked[d.ordinal()] = true; 
     if (!c.isInMaze() && !c.hasNeighbor(oppositeOf(d))) 
     { 
      if (setNeighbor) 
      { 
       setNeighbor = false; 
       c.setNeighbor(this, oppositeOf(d)); 
      } 
      neighbors[d.ordinal()] = c; 
     { 
     setNeighbor = true; 
     inMaze = true; 
    } 

...這實際上暴露了setNeighbor布爾總是等同於真(不管它的設置假的,它總是被設置爲真),我敢打賭,你不希望它這樣做。

這可能不是您最大的問題,可能還有其他邏輯錯誤。

+0

setNeighbor布爾值在函數調用後總是等於true,這很好,因爲每當從MazeGenerator調用它時,currentCell和nextCell都需要設置爲彼此的鄰居,因此在調用nextCell之前將其設置爲false在currentCell中,nextCell不會再次爲currentCell再次調用它,但是在下一次迭代之後它將被重置爲真(因爲它是一個靜態變量)。這是一種混亂的處理方式,但它只是那種方式。雖然我確實喜歡Direction.oppositeOf的想法。謝謝。 – 2012-02-10 17:48:11

+1

我的觀點是,寫入'setNeighbor'布爾的方式決不會對你在這裏的代碼做任何事情。如果if語句可能爲false,它從未檢查過。 – 2012-02-10 20:58:24

1

我認爲你發現的遞歸算法很好。你只需要通過使用棧或隊列而不是遞歸調用(你模擬你的調用棧)來將它們轉換爲迭代的。你可以找到breadth first迭代here的一個很好的例子。希望這會有所幫助,並且可以將其應用於您的問題。