主要逻辑其实只是 breakWall 方法,其他的都是一些繁琐的边界判断之类的。破墙的时候注意要破两面墙,一面是当前方块的墙,一面是下一个方块的墙,方向刚好相反。
下面是运行起来的一些结果:
可以看到效果不太理想,主要的问题是通行区域过于集中,以至于经常出现大块空地。如果把迷宫规模扩大,明显发现很多区域的墙都没有破,处于完全封闭状态。
随机传送到任意方格进行破墙,应该可以解决通行区域过于集中的问题,尝试修改代码:
async breakWall (cb = async () => {}) { let { nodes } = this let current = nodes[0] for (;;) { let breakDirection = this.getRandomNext(current) await cb(current) if (breakDirection !== null) { current.value ^= breakDirection.value breakDirection.nextNode.value ^= breakDirection.oppositeValue // 改为随机选取下一个方格 current = nodes[this.getRandomInt(0, nodes.length - 1)] } else { break } } }运行结果:
通行区域确实分散了开来,但仍然存在很多无法到达的封闭方格。仔细想想,根本原因是因为整个迭代过程结束后,依然存在从未到达过的方格,所以需要想办法让每一个方格都至少到达一次,至少打破一面墙。
准备一个 nodesShuffle 数组,里面的元素和 nodes 是一样的,但是使用 洗牌算法 去打乱顺序,然后在 breakWall 里面迭代这个洗牌后的数组即可:
/** * 破墙循环 * @param {Function} cb */ async breakWall (cb = async () => {}) { let { nodesShuffle } = this let { length } = nodesShuffle for (let i = 0; i < length; i++) { let current = nodesShuffle[i] let breakDirection = this.getRandomNext(current) await cb(current) if (breakDirection !== null) { current.value ^= breakDirection.value breakDirection.nextNode.value ^= breakDirection.oppositeValue } } }完整代码:https://codesandbox.io/s/maze-vite-11-jfcum?file=http://www.likecs.com/src/App.vue
运行效果:
看起来算是有模有样了,但是仔细观察,存在互相隔绝的大区域,比如:
A、B 区域互相无法到达,有没有办法可以使得迷宫中任意两个方格,都有且只有一条通达道路呢?答案是肯定的。关键点在于,每回迭代不能从所有的方格里面随意选,而是必须要从已被破过墙的方格里面选择,这样就能够彻底杜绝孤立区域。
/** * 破墙循环 * @param {Function} cb */ async breakWall (cb = async () => {}) { let { nodes, nodesChecked } = this nodesChecked.push(nodes[0]) nodes[0].checked = true for (; nodesChecked.length > 0;) { let randomIndex = this.getRandomInt(0, nodesChecked.length - 1) let current = nodesChecked[randomIndex] let breakDirection = this.getRandomNext(current) await cb(current) if (breakDirection !== null) { current.value ^= breakDirection.value let { nextNode } = breakDirection nextNode.value ^= breakDirection.oppositeValue nextNode.checked = true nodesChecked.push(nextNode) } else { nodesChecked.splice(randomIndex, 1) } } } /** * 获取周围可以破的墙 * @param {Cell} node * @returns */ getNextDirections (node) { const { 上, 左, 下, 右 } = MazeGanerator let { x, y, value } = node return [ 上, 左, 下, 右 ] .filter(direction => (value & direction) === direction) .map(direction => { let nextX let nextY let oppositeValue if (direction === 上) { oppositeValue = 下 nextX = x nextY = y - 1 } else if (direction === 左) { oppositeValue = 右 nextX = x - 1 nextY = y } else if (direction === 下) { oppositeValue = 上 nextX = x nextY = y + 1 } else if (direction === 右) { oppositeValue = 左 nextX = x + 1 nextY = y } // 边界判断 if (nextX >= 0 && nextY >= 0 && nextX < this.width && nextY < this.height) { let nextNode = this.nodes[this.posToIndex(nextX, nextY)] return { x: nextX, y: nextY, value: direction, oppositeValue, nextNode } } else { return null } }) .filter(item => item !== null && item.nextNode.checked === false) }