add maze generator

This commit is contained in:
Dennis Eichhorn 2019-12-15 12:45:45 +01:00
parent 7e831763e5
commit acd148b70b
2 changed files with 259 additions and 0 deletions

View File

@ -0,0 +1,158 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package phpOMS\Algorithm\Maze
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace phpOMS\Algorithm\Maze;
/**
* Maze generator
*
* @package phpOMS\Algorithm\Maze
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
class MazeGenerator
{
/**
* Constructor
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Generate a random maze
*
* @param int $width Width
* @param int $height Height
*
* @return array
*
* @since 1.0.0
*/
public static function random(int $width, int $height) : array
{
$n = $height * $width - 1;
$horizontal = \array_fill(0, $height, []);
$vertical = \array_fill(0, $height, []);
$pos = [\mt_rand(0, $height) - 1, \mt_rand(0, $width) - 1];
$path = [$pos];
$unvisited = [];
for ($i = 0; $i < $height + 2; ++$i) {
$unvisited[] = [];
for ($j = 0; $j < $width + 1; ++$j) {
$unvisited[$i][] = $i > 0 && $i < $height + 1 && $j > 0 && ($i !== $pos[0] + 1 || $j != $pos[1] + 1);
}
}
while (0 < $n) {
$potential = [
[$pos[0] + 1, $pos[1]],
[$pos[0], $pos[1] + 1],
[$pos[0] - 1, $pos[1]],
[$pos[0], $pos[1] - 1],
];
$neighbors = [];
for ($i = 0; $i < 4; ++$i) {
if ($unvisited[$potential[$i][0] + 1][$potential[$i][1] + 1] ?? false) {
$neighbors[] = $potential[$i];
}
}
if (!empty($neighbors)) {
--$n;
$next = $neighbors[\mt_rand(0, \count($neighbors) - 1)];
$unvisited[$next[0] + 1][$next[1] + 1] = false;
if ($next[0] === $pos[0]) {
$horizontal[$next[0]][($next[1] + $pos[1] - 1) / 2] = true;
} else {
$vertical[($next[0] + $pos[0] - 1) / 2][$next[1]] = true;
}
$path[] = $next;
$pos = $next;
} else {
$pos = \array_pop($path);
if ($pos === null) {
$n = 0;
}
}
}
$maze = [];
for ($i = 0; $i < $height * 2 + 1; ++$i) {
$line = [];
if ($i % 2 === 0) {
for ($j = 0; $j < $width * 4 + 1; ++$j) {
if ($j % 4 === 0) {
$line[$j] = '+';
} else {
$line[$j] = $i > 0 && ($vertical[$i / 2 - 1][(int) \floor($j / 4)] ?? false) ? ' ' : '-';
}
}
} else {
for ($j = 0; $j < $width * 4 + 1; ++$j) {
if ($j % 4 === 0) {
$line[$j] = $j > 0 && ($horizontal[($i - 1) / 2][$j / 4 - 1] ?? false) ? ' ' : '|';
} else {
$line[$j] = ' ';
}
}
}
if ($i === 0) {
$line[1] = $line[2] = $line[3] = ' ';
}
if ($height * 2 - 1 === $i) {
$line[4 * $width] = ' ';
}
$maze[] = $line;
}
return $maze;
}
/**
* Render a maze
*
* @param array $maze Maze to render
*
* @return void
*
* @since 1.0.0
*/
public static function render(array $maze) : void
{
foreach ($maze as $y => $row) {
foreach ($row as $x => $column) {
echo $column;
}
echo "\n";
}
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package tests
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace phpOMS\tests\Algorithm\PathFinding;
use phpOMS\Algorithm\Maze\MazeGenerator;
require_once __DIR__ . '/../../Autoloader.php';
/**
* @testdox phpOMS\tests\Algorithm\Maze\MazeGeneratorTest: Maze generation
*
* @internal
*/
class MazeGeneratorTest extends \PHPUnit\Framework\TestCase
{
/**
* @testdox A random maze can be generated
* @covers phpOMS\Algorithm\Maze\MazeGenerator
* @group framework
*/
public function testMazeGeneration() : void
{
$maze = MazeGenerator::random(10, 7);
// correct top
self::assertEquals(
['+', ' ', ' ', ' ', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+'],
$maze[0]
);
// correct bottom
self::assertEquals(
['+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+', '-', '-', '-', '+'],
$maze[14]
);
// correct left
self::assertEquals(
['+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+'],
[$maze[0][0], $maze[1][0], $maze[2][0], $maze[3][0], $maze[4][0], $maze[5][0], $maze[6][0], $maze[7][0], $maze[8][0], $maze[9][0], $maze[10][0], $maze[11][0], $maze[12][0], $maze[13][0], $maze[14][0]]
);
// correct right
self::assertEquals(
['+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', ' ', '+'],
[$maze[0][40], $maze[1][40], $maze[2][40], $maze[3][40], $maze[4][40], $maze[5][40], $maze[6][40], $maze[7][40], $maze[8][40], $maze[9][40], $maze[10][40], $maze[11][40], $maze[12][40], $maze[13][40], $maze[14][40]]
);
}
/**
* @testdox A random maze can be rendered
* @covers phpOMS\Algorithm\Maze\MazeGenerator
* @group framework
*/
public function testMazeRender() : void
{
\ob_start();
MazeGenerator::render(MazeGenerator::random(10, 7));
$ob = \ob_get_clean();
\ob_clean();
$maze = \explode("\n", $ob);
// correct top
self::assertEquals(
'+ +---+---+---+---+---+---+---+---+---+',
$maze[0]
);
// correct bottom
self::assertEquals(
'+---+---+---+---+---+---+---+---+---+---+',
$maze[14]
);
// correct left
self::assertEquals(
['+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+'],
[$maze[0][0], $maze[1][0], $maze[2][0], $maze[3][0], $maze[4][0], $maze[5][0], $maze[6][0], $maze[7][0], $maze[8][0], $maze[9][0], $maze[10][0], $maze[11][0], $maze[12][0], $maze[13][0], $maze[14][0]]
);
// correct right
self::assertEquals(
['+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', '|', '+', ' ', '+'],
[$maze[0][40], $maze[1][40], $maze[2][40], $maze[3][40], $maze[4][40], $maze[5][40], $maze[6][40], $maze[7][40], $maze[8][40], $maze[9][40], $maze[10][40], $maze[11][40], $maze[12][40], $maze[13][40], $maze[14][40]]
);
}
}