高效算法生成一种迷宫

时间:2018-02-25 09:35:15

标签: algorithm

我已经做了很多搜索,并且已经找到了很多帮助来生成迷宫,但是我有一个非常具体的要求,并且我尝试过的所有循环都失败了。

我创建了一个编辑器,我可以绘制我需要的东西,但是生成器会有很大的帮助,但这已经失败了。

要求:

给定一个DIV元素的正方形网格(不小于10x10且不大于60x60),我需要一个穿过网格的连接路径,除了开始/结束之外,它不会在任何点触及自身。 在所有路径方块之间必须始终至少有一个空白方块(只要路径永远不会与自身接触,任何数量的空白都可以)。 没有死角也没有循环(路径会自行穿过)。

这有点像反向迷宫 - 我需要填充整个网格,事实上我在路径周围有很多空间没有问题。可能更容易想到这与 Monopoly棋盘游戏的类似路线,其中围绕棋盘的路径徘徊而不是绕过边缘。我实际上被困在一个足够的描述中,因此称它为反向迷宫。

我尝试的事情:

许多过于复杂的循环。我不是非常接近,问题也是性能问题。 设计用于生成迷宫的大量代码。其中一些确实非常好,但它们都产生了一个典型的迷宫,这根本不是我真正需要的,并且调整代码已证明比在循环中编写一组疯狂的循环更难。

任何想法都会有所帮助。感谢。

更新代码

好的,我已经将KIKO的PHP代码翻译成了Javascript,但是在某个地方,我做了一个我无法追踪的简单错误:代码工作并生成一个正确尺寸的表格,并通过它生成一条路径。

但是,在函数“isWithinGrid”中,我必须从表的宽度和高度中减去1,否则整个事情都将失败,如果我这样做,代码将工作并创建一个通过表的路径减去一个单元格,虽然显然是路径的一部分,但会被错误地着色。

请注意,有时路径会被破坏或触碰自己。我毫不怀疑一些小问题导致所有这些,但目前这是我提出的最好的,任何进一步的帮助将不胜感激。

class Grid{
	constructor(width,height){    
		this.width  = width;
		this.height = height;
		this.cells = [];
		for(var x=0; x < this.width; x++){
			var tmparray = [];
			for(var y=0; y < this.height; y++){
				tmparray.push(false);
			}
			this.cells.push(tmparray);
		}
	}
	isWithinGrid(x,y){
		return (x >= 0) && (x <= this.width-1) && (y >= 0) && (y <= this.height-1);
	}
	isWithinPath(x,y){
		return this.isWithinGrid(x,y) && this.cells[x][y];
	}  
	setCellInPath(x,y,boolean){
		this.cells[x][y] = boolean;
		return this;
	}
	drawHorizontalLine(x1,x2,y){
		for(var x=x1; x < x2; x++){
			this.setCellInPath(x,y,true);
		}	  
		return this;
	}
	drawVerticalLine(x,y1,y2){
		for(var y=y1; y < y2; y++){
			this.setCellInPath(x,y,true);
		}	
		return this;
	}
	drawSquare(){
		var left   = Math.round(this.width/5);
		var right  = Math.round(4*this.width/5);
		var top    = Math.round(this.height/5);
		var bottom = Math.round(4*this.height/5);
		this.drawHorizontalLine(left,right,top)
		.drawHorizontalLine(left,right,bottom)
		.drawVerticalLine(left,top,bottom)
		.drawVerticalLine(right,top,bottom);
		return this;
	}
	moveCell(x,y,dx,dy){
		this.setCellInPath(x,y,false);
		this.setCellInPath(x+dx,y+dy,true);
	}
	canMoveCell(x,y,dx,dy){
		return this.isWithinPath(x,y) &&
		this.isWithinGrid(x+dx,y+dy) &&
		!this.isWithinPath(x+dx,y+dy) &&
		!this.isWithinPath(x+2*dx,y+2*dy) &&
		!this.isWithinPath(x+dy+dx,y+dx+dy)
		!this.isWithinPath(x-dy+dx,y-dx+dy);
	}

	tryToDistortOnce(x,y,dx,dy){
		if (!this.canMoveCell(x,y,dx,dy)) return false;
		if (!this.canMoveCell(x+dy,y+dx,dx,dy)) return false;
		if (!this.canMoveCell(x-dy,y-dx,dx,dy)) return false;
		this.moveCell(x,y,dx,dy);
		this.setCellInPath(x+dy+dx,y+dx+dy,true);
		this.setCellInPath(x-dy+dx,y-dx+dy,true);
		return true;
	}
	distortOnce(){
		var x=0, y=0, dx=0, dy=0;
		do {
			x = Math.floor(Math.random() * this.width) + 1;
			y = Math.floor(Math.random() * this.height) + 1;
		} while (!this.isWithinPath(x,y));
		switch (Math.floor(Math.random() * 4) + 1){
			case 1: dx = -1; dy = 0; break;
			case 2: dx = +1; dy = 0; break;
			case 3: dx = 0; dy = +1; break;
			case 4: dx = 0; dy = -1; break;
		}
		if (this.tryToDistortOnce(x,y,dx,dy)){
			do {
				x += dx;
				y += dy;
			} while (this.tryToDistortOnce(x,y,dx,dy));
			return true;
		}
		return false;
	}
	distortPath(numberOfDistortions = 10){
		for(var counter=1; counter < numberOfDistortions; counter++){
			var tries = 0;
			while (!this.distortOnce() && (tries < this.width+this.height)){ tries++; }
		}
		return this;
	}
	renderGrid(){
		var str = '<table class="TSTTAB">';		
		for(var y=0; y < this.width; y++){
			for(var x=0; x < this.height; x++){
				str += '<td'+(this.cells[y][x] ? ' class="path">' : '>');
			}
			str += '</tr>';
		}
		str += '</table>';
    document.getElementById('cont').innerHTML =str;
		return this;
	}
}
var Testgrid = new Grid(20,20);
Testgrid.drawSquare().distortPath(10).renderGrid();
.TSTTAB{background-color:#7F7F7F;border-collapse:collapse;}
.TSTTAB td{ width:20px; height: 20px; border: 1px solid #000;background-color: #E5E5E5; }
.TSTTAB td.path { background-color: #44F; }
<div id='cont'></div>

1 个答案:

答案 0 :(得分:3)

嗯,我试过了。对于一个简单的问题,一小时的工作似乎绰绰有余。当然,这远非完美,但它说明了我在谈论的内容。它会生成如下解决方案:

Example of a 20x20 grid with a complexity of 20

完整的代码是:

<?php

// error reporting
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// configuration
const SIZE_X     = 20;
const SIZE_Y     = 20;
const COMPLEXITY = 20;

// grid class
class Grid
{

  public function __construct($width,$height)
  {
    // remember
    $this->width  = $width;
    $this->height = $height;
    // initiate grid
    foreach (range(1,$width) as $x) {
      foreach (range(1,$height) as $y) {
        $this->cells[$x][$y] = FALSE;  // false means: not in path
      }
    }
  }

  public function isWithinGrid($x,$y)
  // testb whether (x,y) is within the grid
  {
    return ($x >= 1) && ($x <= $this->width) &&
           ($y >= 1) && ($y <= $this->height);
  }

  public function isWithinPath($x,$y)
  // is a cell part of the path?
  {
    return $this->isWithinGrid($x,$y) && $this->cells[$x][$y];
  }

  public function setCellInPath($x,$y,$boolean)
  // remember whether a cell is part of the path or not
  {
    $this->cells[$x][$y] = $boolean;
    return $this;
  }

  public function drawHorizontalLine($x1,$x2,$y)
  // simple horizontal line
  {
    foreach (range($x1,$x2) as $x) $this->setCellInPath($x,$y,TRUE);
    return $this;
  }

  public function drawVerticalLine($x,$y1,$y2)
  // simple vertical line
  {
    foreach (range($y1,$y2) as $y) $this->setCellInPath($x,$y,TRUE);
    return $this;
  }

  public function drawSquare()
  // simple square
  {
    $left   = round($this->width/5);
    $right  = round(4*$this->width/5);
    $top    = round($this->height/5);
    $bottom = round(4*$this->height/5);
    $this->drawHorizontalLine($left,$right,$top)
         ->drawHorizontalLine($left,$right,$bottom)
         ->drawVerticalLine($left,$top,$bottom)
         ->drawVerticalLine($right,$top,$bottom);
    return $this;
  }

  private function moveCell($x,$y,$dx,$dy)
  // move a cell
  {
    $this->setCellInPath($x,$y,FALSE);
    $this->setCellInPath($x+$dx,$y+$dy,TRUE);
  }

  private function canMoveCell($x,$y,$dx,$dy)
  // answers the question whether or not we can move (x,y) by (dx,dy)
  {
    return $this->isWithinPath($x,$y) &&                   // must be part of path
           $this->isWithinGrid($x+$dx,$y+$dy) &&           // stay within grid
           !$this->isWithinPath($x+$dx,$y+$dy) &&          // but not on the path
           !$this->isWithinPath($x+2*$dx,$y+2*$dy) &&      // and don't touch path
           !$this->isWithinPath($x+$dy+$dx,$y+$dx+$dy) &&  // and don't touch path
           !$this->isWithinPath($x-$dy+$dx,$y-$dx+$dy);    // and don't touch path
  }

  private function tryToDistortOnce($x,$y,$dx,$dy)
  {
    // this one should be able to move
    if (!$this->canMoveCell($x,$y,$dx,$dy)) return FALSE;
    // but also its neighbours must be able to move
    if (!$this->canMoveCell($x+$dy,$y+$dx,$dx,$dy)) return FALSE;
    if (!$this->canMoveCell($x-$dy,$y-$dx,$dx,$dy)) return FALSE;
    // move the target cell by displacement
    $this->moveCell($x,$y,$dx,$dy);
    // move neighbours by adding two cells
    $this->setCellInPath($x+$dy+$dx,$y+$dx+$dy,TRUE);
    $this->setCellInPath($x-$dy+$dx,$y-$dx+$dy,TRUE);
    return TRUE; // success!
  }

  private function distortOnce()
  // distort a random cell, returns success or failure
  {
    // find a random cell in path
    do {
      $x = rand(1,$this->width);
      $y = rand(1,$this->height);
    } while (!$this->isWithinPath($x,$y));
    // choose one of four directions to move in
    switch (rand(1,4))
    {
      case 1: $dx = -1; $dy = 0; break;
      case 2: $dx = +1; $dy = 0; break;
      case 3: $dx = 0; $dy = +1; break;
      case 4: $dx = 0; $dy = -1; break;
    }
    // try to do it
    if ($this->tryToDistortOnce($x,$y,$dx,$dy))
    {
      // more moves
      do {
        $x += $dx;
        $y += $dy;
      } while ($this->tryToDistortOnce($x,$y,$dx,$dy));
      return TRUE; // it was a success!
    }
    return FALSE; // we failed
  }

  public function distortPath($numberOfDistortions = 10)
  // distort up to a certain amount of times
  {
    // find a random cell that is part of the path to distort
    for ($counter = 1; $counter <= $numberOfDistortions; $counter++) {
      // we try that a limited number of times, depending on the grid size
      $tries = 0;
      while (!$this->distortOnce() &&
             ($tries < $this->width+$this->height)) { $tries++; }
    }
    return $this;
  }

  public function renderGrid()
  // render grid
  {
    echo '<!DOCTYPE HTML><html><head><style>'.
         '  td { width:20px; height: 20px; border: 1px solid #000; }'.
         '  .path { background-color: #44F; }'.
         '</style></head><body><table>';
    foreach (range(1,SIZE_Y) as $y) {
      echo '<tr>';
      foreach (range(1,SIZE_X) as $x) {
        echo '<td'.($this->cells[$x][$y] ? ' class="path">' : '>');
      }
      echo '</tr>';
    }
    echo '</body></html></table>';
    return $this;
  }

}

// create grid
$grid = new Grid(SIZE_X,SIZE_Y);
// start with a square, distort and then render
$grid->drawSquare()
     ->distortPath(COMPLEXITY)
     ->renderGrid();

你可以做很多事情来改善这个......玩得开心!

在我的服务器上,此代码需要2到5毫秒才能执行。里程可能会有所不同......