some-dungeon-game/class_level.php
2021-03-18 22:07:56 +02:00

298 lines
8.6 KiB
PHP

<?php
class Level
{
const MIN_STROKES = 5;
const MAX_STROKES = 25;
const BLOOM_MAX = 1, BLOOM_MIN = 0;
// This needs its own definitions of these so we can use some stupid array tricks
const DIR_NONE = -1, DIR_UP = 0, DIR_RIGHT = 1, DIR_DOWN = 2, DIR_LEFT = 3;
private $coords;
private $width;
private $height;
private $dungeon;
private $changes;
public function __construct(Dungeon $dungeon, $createRandom = true)
{
$this->coords = array();
$this->width = 0;
$this->height = 0;
if ($createRandom)
$this->createRandom();
$this->dungeon = $dungeon;
}
public function whatsAt(Coord $place)
{
return (isset($this->coords[(string) $place]))? $this->coords[(string) $place] : null;
}
public function insertEntity(Coord $place, Entity $entity)
{
if (isset($this->coords[(string) $place])
&& !($this->coords[(string) $place] instanceof EmptyFloor))
throw new PlaceOccupiedException('Cannot insert, place already taken.');
$this->coords[(string) $place] = $entity;
$this->width = max($this->width, $place->x);
$this->height = max($this->height, $place->y);
}
public function removeEntity(Coord $place)
{
if (!isset($this->coords[(string) $place])
|| $this->coords[(string) $place] instanceof EmptyFloor)
throw new PlaceException('Cannot remove, nothing there.');
unset($this->coords[(string) $place]);
$floor = new EmptyFloor();
$floor->setPlace(new Place($this, $place));
}
public function printLevel(Display $disp)
{
foreach ($this->coords as $coord => $content)
{
// +1 to X and Y since in the terminal they start from 1,1
$expCoord = Coord::createFromString($coord);
$disp->writeTo($expCoord->x + 1, $expCoord->y + 1, $this->whatsAt($expCoord));
}
}
public function getWidth()
{
return $this->width;
}
public function getHeight()
{
return $this->height;
}
public function getDungeon()
{
return $dungeon;
}
public function getEmptyPlace()
{
$keys = array_keys($this->coords);
shuffle($keys);
do
{
$coord = array_pop($keys);
if ($coord === null)
throw new PlaceException('No empty place found for entity.');
$content = $this->coords[$coord];
} while (!($content instanceof EmptyFloor));
return Coord::createFromString($coord);
}
private function createRandom()
{
$strokes = array();
$strokeAmount = rand(static::MIN_STROKES, static::MAX_STROKES);
$ends = array();
for ($i = 0; $i < $strokeAmount; ++$i)
{
$start = null; $dir = 0;
if (empty($ends)) // If empty, start somewhere
{
$start = new Coord(rand(2, TERM_X - 3), rand(2, TERM_Y - 3));
$freeDir = rand(self::DIR_UP, self::DIR_LEFT);
$ends[] = $start;
$strokes[(string) $start] = true;
}
else // Else find a previous intersection and start there
{
$freeDir = self::DIR_NONE;
shuffle($ends);
$endAmnt = count($ends);
for ($r = 0; $r < $endAmnt && $freeDir == self::DIR_NONE; ++$r)
{
$start = $ends[$r];
$freeDir = $this->getFreeDir($start, $strokes);
}
// No free spots could be found, end algorithm
if ($freeDir == self::DIR_NONE)
break;
}
// Now create strokes from that place onwards
$strokeLines = rand(1, ceil($strokeAmount / 2));
for ($r = 0; $r < $strokeLines; ++$r)
{
$newStart = $this->strokeLine($start, $strokes, $ends);
if ($newStart === null) // Couldn't find a free direction
break;
$start = $newStart;
$ends[] = $start;
}
}
// Widen created strokes
$strokes = $this->bloom($strokes);
// Build walls
$this->buildWalls($strokes);
}
private function getFreeDir(Coord $coord, array $reserved)
{
$dirs = range(self::DIR_UP, self::DIR_LEFT);
shuffle($dirs);
foreach ($dirs as $dir)
{
if ($dir == self::DIR_UP && $coord->y > 1 && !isset($reserved[(string) (new Coord($coord->x, $coord->y - 1))]))
return $dir;
elseif ($dir == self::DIR_DOWN && $coord->y < TERM_Y - 2 && !isset($reserved[(string) (new Coord($coord->x, $coord->y + 1))]))
return $dir;
elseif ($dir == self::DIR_RIGHT && $coord->x < TERM_X - 2 && !isset($reserved[(string) (new Coord($coord->x + 1, $coord->y))]))
return $dir;
elseif ($dir == self::DIR_LEFT && $coord->x > 1 && !isset($reserved[(string) (new Coord($coord->x - 1, $coord->y))]))
return $dir;
}
return self::DIR_NONE;
}
private function strokeLine(Coord $start, array &$strokes, array &$ends)
{
// Get direction
$dir = $this->getFreeDir($start, $strokes);
if ($dir == self::DIR_NONE)
return null;
switch ($dir)
{
case self::DIR_UP:
$maxLen = ceil(($start->y - 1) / 3);
break;
case self::DIR_DOWN:
$maxLen = ceil((TERM_Y - 2 - $start->y) / 3);
break;
case self::DIR_RIGHT:
$maxLen = ceil((TERM_X - 2 - $start->x) / 3);
break;
case self::DIR_LEFT:
$maxLen = ceil(($start->x - 1) / 3);
}
$length = rand(ceil($maxLen / 2), $maxLen);
$nextCoord = clone $start;
// Now add line to $strokes
for ($i = 1; $i <= $length; ++$i)
{
switch ($dir)
{
case self::DIR_UP:
$nextCoord = new Coord($nextCoord->x, $nextCoord->y - 1);
break;
case self::DIR_DOWN:
$nextCoord = new Coord($nextCoord->x, $nextCoord->y + 1);
break;
case self::DIR_RIGHT:
$nextCoord = new Coord($nextCoord->x + 1, $nextCoord->y);
break;
case self::DIR_LEFT:
$nextCoord = new Coord($nextCoord->x - 1, $nextCoord->y);
}
$strokes[(string) $nextCoord] = true;
$ends[] = $nextCoord;
}
return $nextCoord;
}
private function bloom(array $reserved)
{
$bloom = rand(self::BLOOM_MIN, self::BLOOM_MAX);
$added = array();
foreach ($reserved as $key => $coord)
{
$coord = Coord::createFromString($key);
for ($x = ($coord->x - $bloom); $x <= ($coord->x + $bloom); ++$x)
{
for ($y = ($coord->y - $bloom); $y <= ($coord->y + $bloom); ++$y)
{
if ($x > 0 && $x < TERM_X - 1 && $y > 0 && $y < TERM_Y - 1)
{
$added[(string) (new Coord($x, $y))] = true;
}
}
}
// Calculate next bloom size
$action = rand(1, 6);
switch ($action)
{
case 1:
// Completely new bloom 1/6 of the time
$bloom = rand(self::BLOOM_MIN, self::BLOOM_MAX);
break;
case 2:
// Slightly new bloom 1/6 of the time
$vals = array(max($bloom - 1, self::BLOOM_MIN), min($bloom + 1, self::BLOOM_MAX));
$bloom = $vals[array_rand($vals)];
break;
// Else no change to bloom 2/3 of the time
}
}
return array_merge($reserved, $added);
}
private function buildWalls(array $reserved)
{
foreach ($reserved as $coord => $dummy)
{
$coord = Coord::createFromString($coord);
// Loop around each cell
for ($x = ($coord->x - 1); $x <= ($coord->x + 1); ++$x)
{
for ($y = ($coord->y - 1); $y <= ($coord->y + 1); ++$y)
{
if ($x < 0 || $y < 0 || $x >= TERM_X || $y >= TERM_Y)
throw new SDGException();
if ($x >= 0 && $x < TERM_X && $y >= 0 && $y < TERM_Y && !isset($reserved[$x . ',' . $y])) // This cell is empty
{
try
{
$wall = new Wall();
$wall->setPlace(new Place($this, new Coord($x, $y)));
} catch (PlaceOccupiedException $e)
{ }
}
}
}
// Create empty floor tile for reserved place
$floor = new EmptyFloor();
$floor->setPlace(new Place($this, $coord));
}
}
}