使用具有特定运动规则的DFS进行迷宫生成

时间:2019-07-14 09:25:56

标签: javascript typescript graph maze graph-traversal

我特别需要生成带有一些运动约束的路线/迷宫。

鉴于10x10矩阵中的随机选取的cell(其中level是需要生成的可移动字段的数量),生成路由槽矩阵。

运动约束如下:

  • 水平和垂直恰好3个单元格
  • 对角线总共2个单元格
  • 您不能两次踩到同一字段

我想出了较低的级别,但是目前无法生成最大lvl。

我似乎无法正确地绕过回溯。

interface Cell extends Cords {
  visited: boolean
  neibhoursAvailable: Cords[]
  pickedEdge: Cords | {}
}

interface Cords {
  x: number
  y: number
}

type Matrice = Cell[][]

const rand = (arr: Cords[]) => {
  return arr[~~(Math.random() * arr.length)]
}

const matrix = (width: number, height: number): Cell[][] => {
  return Array(width)
    .fill({
      x: 0,
      y: 0,
      visited: false,
      neibhoursAvailable: [],
      pickedEdge: {},
    })
    .map(() =>
      Array(height).fill({
        x: 0,
        y: 0,
        visited: false,
        neibhoursAvailable: [],
        pickedEdge: {},
      }),
    )
}

const createCell = (
  x: number,
  y: number,
  visited: boolean,
  neibhoursAvailable: [],
): Cell => {
  return { x, y, visited, neibhoursAvailable, pickedEdge: {} }
}

let matrice = matrix(10, 10).map(
  (i, idx): Cell[] => {
    return i.map((_, idy) => {
      return {
        x: idx,
        y: idy,
        visited: false,
        neibhoursAvailable: [],
        pickedEdge: {},
      }
    })
  },
)

const checkTraversability = (
  startCords: Cords,
  matrice: Matrice,
): Cell | undefined => {
  // Check left
  console.log(startCords)
  if (startCords.x === undefined) {
    return undefined
  }
  if (startCords.y === undefined) {
    return undefined
  }
  const { x, y } = startCords
  const cell: Cell = matrice[x][y]

  if (cell.x - 3 >= 0 && !matrice[cell.x - 3][cell.y].visited) {
    cell.neibhoursAvailable.push({ x: cell.x - 3, y: cell.y })
  }

  // Check right
  if (cell.x + 3 < 10 && !matrice[cell.x + 3][cell.y].visited) {
    cell.neibhoursAvailable.push({ x: cell.x + 3, y: cell.y })
  }

  if (cell.y - 3 >= 0 && !matrice[cell.x][cell.y - 3].visited) {
    cell.neibhoursAvailable.push({ x: cell.x, y: cell.y - 3 })
  }
  if (cell.y + 3 < 10 && !matrice[cell.x][cell.y + 3].visited) {
    cell.neibhoursAvailable.push({ x: cell.x, y: cell.y + 3 })
  }

  // Check Diagonals

  if (
    cell.x + 2 < 10 &&
    cell.y + 2 < 10 &&
    !matrice[cell.x + 2][cell.y + 2].visited
  ) {
    cell.neibhoursAvailable.push({ x: cell.x + 2, y: cell.y + 2 })
  }

  if (
    cell.x + 2 < 10 &&
    cell.y - 2 >= 0 &&
    !matrice[cell.x + 2][cell.y - 2].visited
  ) {
    cell.neibhoursAvailable.push({ x: cell.x + 2, y: cell.y - 2 })
  }

  if (
    cell.x - 2 >= 0 &&
    cell.y + 2 < 10 &&
    !matrice[cell.x - 2][cell.y + 2].visited
  ) {
    cell.neibhoursAvailable.push({ x: cell.x - 2, y: cell.y + 2 })
  }

  if (
    cell.x - 2 >= 0 &&
    cell.y - 2 >= 0 &&
    !matrice[cell.x - 2][cell.y - 2].visited
  ) {
    cell.neibhoursAvailable.push({ x: cell.x - 2, y: cell.y + 2 })
  }
  return cell
}

let stack: Cell[] = []

const generateMaze = (
  matrice: Cell[][],
  stack: Cell[],
  startCords: { x: number; y: number },
  level: number,
) => {
  const traversable = checkTraversability(startCords, matrice)
  if (level >= 0) {
    traversable.visited = true
    const randomEdge = rand(traversable.neibhoursAvailable)
    traversable.neibhoursAvailable = traversable.neibhoursAvailable.filter(
      i => !(i.x === randomEdge.x && i.y === randomEdge.y),
    )
    traversable.pickedEdge = randomEdge
    stack.push(traversable)
    generateMaze(matrice, stack, randomEdge, level - 1)
  }
  return matrice
}

const gen = generateMaze(matrice, stack, { x: 0, y: 0 }, 10)
console.log(gen)

任何建议或指导将不胜感激。

1 个答案:

答案 0 :(得分:1)

确实,回溯带来了问题。

一些问题:

  • checkTraversability的结尾附近有一个错误:您将cell.y + 2推入坐标,而坐标应该为cell.y - 2
  • matrix函数有一个无用的参数传递给第一个fill()调用。永远不会使用该值,因为您将该数组映射为2D数组,因此第一个fill可能只是fill(null)
  • 当随机选择的边导致无解而应该选择另一个并尝试使用时,会缺少一个循环。
  • generateMaze返回matrice,但是您可以将返回值保留给更有用的东西,因为调用者已经可以访问在第一个传递给函数的matrice位置,并且该位置会因调用而发生变化。
  • 不需要全局stack变量并将其传递给函数。相反,您可以从达到0级的那一刻开始反向构建它。您可以返回然后是只有最后一个单元格的路径,而在回溯时,可以在当前单元格之前添加该路径。最后,您将拥有想要的路径作为初始调用的返回值。
  • 我认为不需要从单元的邻居列表中删除选定的边,因为您无论如何都不想再次访问该单元。另一方面,一个单元不能被两次访问的条件足以避免遵循相同的边缘。
  • 返回值之间必须有区别,以指示搜索是否成功。如果成功,则可以返回路径(如上所述),如果失败,则可以返回undefined
  • undefined返回一个空数组并且我们还没有达到0级时,该函数应该返回checkTraversability
  • 仅当确定递归调用成功找到解决方案时,才应将边缘标记为“已拾取”。
  • 当没有边缘导致解决方案时,请不要忘记删除visited标记
  • 与其使用rand来拾取一个随机边缘,还不如创建一个对边缘进行混洗的函数,然后对其进行迭代,直到获得成功为止。如果没有成功,则返回undefined

以下是您可以使用的一些代码部分:

shuffle函数:

function shuffle(a) {
    for (let i = a.length - 1; i > 0; i--) {
        let j = Math.floor(Math.random() * (i + 1));
        let x = a[i];
        a[i] = a[j];
        a[j] = x;
    }
    return a;
}

generateMaze函数

const generateMaze = (
  matrice: Cell[][],
  startCords: { x: number; y: number },
  level: number,
) => {    
  const traversable = checkTraversability(startCords, matrice);
  traversable.visited = true;
  if (level <= 0) return [traversable]; // found a solution: just return the end of the path
  for (let randomEdge of shuffle([...traversable.neibhoursAvailable])) {
    // Not needed to remove edge.
    let path = generateMaze(matrice, randomEdge, level - 1);
    if (path) { // Success: Only now mark the edge as picked
      traversable.pickedEdge = randomEdge;
      return [traversable, ...path]; // and backtrack building the path
    }
  }
  // failure: unmark this node (!), and just return undefined
  traversable.visited = false;
}

const path = generateMaze(matrice, { x: 0, y: 0 }, 10);
console.log(path);

这实际上将返回包含11个单元格和10条边的路径。我不确定10级是否意味着10个 other 单元格或是否包含起始单元格。如果需要减少一,则将if (level <= 0)更改为if (level <= 1)