【问题标题】:Efficient algorithm to generate a kind of maze一种生成迷宫的高效算法
【发布时间】:2018-02-25 09:35:15
【问题描述】:

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

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

要求:

给定一个由 DIV 元素组成的方形网格(不小于 10x10 且不大于 60x60),我需要一条穿过和环绕网格的连接路径,该路径在除开始/结束时之外的任何点都不会触及自身。 所有路径方格之间必须始终至少有一个空白方格(只要路径不与自身接触,任何数量的空白都可以)。 不能有死胡同和环路(路径会自行交叉的地方)。

这有点像一个反向迷宫 - 我不需要填满整个网格,事实上我对路径周围的大量空间没有任何问题。沿着类似于大富翁棋盘游戏的思路可能更容易想到这一点,其中棋盘周围的路径徘徊而不是绕过边缘。我实际上被困在一个足够的描述中,因此称之为反向迷宫。

我尝试过的事情:

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

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

更新代码

好的,我已将 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; }
&lt;div id='cont'&gt;&lt;/div&gt;

【问题讨论】:

  • 分享你的代码。
  • 理念:从起点开始,随机移动。如果你被卡住了,就原路返回。
  • @connexo 我认为它没有用,因为这是一个算法问题。
  • 我不明白你的问题。什么是“空白方块”?
  • 在这种情况下,我认为一种算法开始一条路径并随机移动,然后在卡住时回溯,可能需要很长时间并且相当不成功。我会使用另一种方法:选择在其间绘制直接路径的起点和终点。从路径中选择一个随机正方形并将其沿随机方向拖动,同时保持路径连接,直到它不能再进一步。这样,您可以根据需要尽可能多地或尽可能少地扭曲路径。我知道,魔鬼在细节中,但也许这会给你新的灵感?

标签: algorithm


【解决方案1】:

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

完整代码为:

<?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 毫秒。里程可能会有所不同...

【讨论】:

  • 非常好的结果,非常感谢,但我使用的是 Javascript 或 jQuery 而不是 PHP!如果我有机会,我今天会在一次转换中大吃一惊,我会回来告诉你进展如何!非常感谢您的努力。
  • 啊,抱歉,我没有意识到这一点。没有什么可以阻止您将其转换为 Javascript。我还使用了表格而不是 div。这都是关于想法的。例如,要创建更多变化,您可以使起始矩形更加随机,改变单个失真的范围,或者使某些失真更宽。
  • 好吧,看来我的 PHP 没有达到标准来转换它。我认为它看起来很容易,但我根本无法理解表格单元格,我正在努力处理整个 drawSquare() 函数。我有一个 DIV 网格而不是一个表格,所以它应该很容易更改一个类以便为路径上的 DIV 添加边框而不是其他的,但是我无法获得在表格和 DIV 之间交叉的方法。结果几乎正是我所需要的,而且我对我需要做什么有了某种想法,但是事情并没有解决,我又被循环挂断了!
  • 是的,编程需要很多知识:HTML、CSS、PHP、Javascript、JQuery 等。没有人可以无所不知。你能告诉我你到目前为止得到了什么吗?只需将其添加到您的问题中,或暂时将其添加为答案。也许我可以解决您的 Javascript-table-div 转换问题?请让它尽可能完整......这样它至少可以运行并做一些事情。
  • 抱歉,KIKO 耽搁了,现在生活和工作妨碍了我的乐趣 - 我会尽快回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-14
相关资源
最近更新 更多