0) { mt_srand($_GET["seed"]); } # Maze dimensions # Horizontal dimension of maze (maximum) $maze_x = ($_GET["maze_x"]>=2 && $_GET["maze_x"]<9999) ? floor($_GET["maze_x"]) : 30; # Vertical (north/south) dimension $maze_y = ($_GET["maze_y"]>=2 && $_GET["maze_y"]<9999) ? floor($_GET["maze_y"]) : 20; # Maze generation parameters # Type of maze to be generated # hex is hexagonal # hexpara is a parallelogram # hexrect is a rectangle (hex grid shaped like a rectangle) $maze_type = $_GET["maze_type"]; if ($maze_type != "hex" && $maze_type != "hexpara" && $maze_type != "hexrect") { $maze_type = "rectangular"; } # Vertical (up/down) dimension - Defaults to 1 $maze_z = ($_GET["maze_z"]>=1 && $_GET["maze_z"]<9999 && $maze_type == "rectangular") ? floor($_GET["maze_z"]) : 1; # Biases are not used for now $bias_x = 1; # Default bias in first direction (horizontal) $bias_y = 1; # Bias in 2nd dimension (up/down) $bias_z = 0; # Bias in 3rd dimension (up/down) $bias_d = 1; # Bias in 3rd dimension (hex diagonal) # Probability that cell chosen will be a run from the previous cell $chance_run = ($_GET["chance_run"]>=0 && $_GET["chance_run"]<=1) ? $_GET["chance_run"] : 0.9; # Probability that a continuous run will retain direction $chance_straight = ($_GET["chance_straight"]>=0 && $_GET["chance_straight"]<=1) ? $_GET["chance_straight"] : 0.9; # View from above or 3D view $maze_view = ($_GET["maze_view"]=="3D" && $maze_type=="rectangular") ? "3D" : "2D"; # Graphical rendering parameters # Keep these fixed for now $cell_x = 20; # Height of each maze cell in pixels $cell_y = $cell_x; # Width of each cell in pixels $boundary_x = 2; # Height of dividers $boundary_y = 2; # Width of dividers $xwindow = 400; # X and Y dimensions of 3D window $ywindow = 400; class Maze { private $type = "rectangular"; private $x = 0; private $y = 0; private $z = 0; # For 3D mazes in future private $offset = array(); private $gates = array(); private $available = array(); private $mask = array(); private $debug = 0; # Constructor method function __construct ($t, $x, $y, $z) { $this->mask = array(1, 2, 4, 8, 16, 32); if ($t == "hex" || $t == "hexrect" || $t == "hexpara") { $this->type = $t; $this->offset = array(array(1,0,0),array(-1,0,0), array(0,1,0),array(0,-1,0), array(1,1,0),array(-1,-1,0),); # East, west, southwest, northeast, southeast, northwest } elseif ($t == "rectangular") { $this->type = $t; $this->offset = array(array(1,0,0),array(-1,0,0), array(0,1,0),array(0,-1,0), array(0,0,1),array(0,0,-1),); # East, west, south, north, down, up } else { throw new Exception('Invalid maze type'); } if ($y>0 && $y<999) { $this->y = $y; } else { throw new Exception('Invalid Y dimension'); } if ($x>0 && $x<999) { #$this->x = $x + (substr($this->type, 0, 3) == "hex" ? $this->y : 0); $this->x = $x; } else { throw new Exception('Invalid X dimension'); } if ($z>0 && $z<999) { $this->z = $z; } else { throw new Exception('Invalid Z dimension'); } # Fill maze out as completely blocked for ($x=0; $x<$this->x; $x++) { for ($y=0; $y<$this->y; $y++) { for ($z=0; $z<$this->z; $z++) { # Zero = no gates open $this->gates[$x][$y][$z] = 0; $this->available[$x][$y][$z] = 1; } } } # Shape hex mazes # Chop off top right and bottom left of a paralellogram maze if ($this->type == "hex") { if ($this->debug) { print "Initialise availability for hex\n"; } for ($y=0; $yy-1)/2); $y++) { for ($x=0; $xy-1)/2)-$y; $x++) { for ($z=0; $z<$this->z; $z++) { if ($this->debug) { print "($x,$y) Block [".($this->x-$x-1).",$y,$z]\n"; print "Block [$x,".($this->y-$y-1).",$z]\n"; } $this->available[$x][$this->y-$y-1][$z] = 0; $this->available[$this->x-$x-1][$y][$z] = 0; } } } } # Hexrect looks sort of like a standard wargame hex map else if ($this->type == "hexrect") { for ($y=2; $y<$this->y; $y++) { for ($x=0; $xz; $z++) { $this->available[$x][$y][$z] = 0; $this->available[$this->x-$x-1][$this->y-$y-1][$z] = 0; } } } } if ($this->debug) { for ($z=0; $z<$this->z; $z++) { for ($y=0; $y<$this->y; $y++) { for ($x=0; $x<$this->x; $x++) { print " " . $this->available[$x][$y][$z]; } print "\n"; } } } } function populate($bias_x, $bias_y, $bias_z, $bias_d, $chance_run, $chance_straight) { $bias_total = $bias_x + $bias_y + (substr($this->type, 0, 3) == "hex" ? $bias_d : 0) + $bias_z; # Pick a seed to start from $escape_check = 1000; do { $x = mt_rand(0, $this->x-1); $y = mt_rand(0, $this->y-1); $z = mt_rand(0, $this->z-1); } while ($this->available[$x][$y][$z]==0 && $escape_check>0); if ($escape_check==0) { throw new Exception('Could not find seed location'); } # Add our seed entry to the pool and mark as unavailable. $this->available[$x][$y][$z] = 0; $poolsize = $this->x*$this->y*$this->z - 1; # Note - candidates is an array, each element is a 3-tuple # Candidate 0 is always the most recently added candidate, # unless it had previously been excluded $candidates = array(array($x, $y, $z),); $cand_count = 1; $cand = 0; $force_new_candidate = 0; $dir = -1; # Force choice of new direction $offset_size = sizeof($this->offset); while ($cand_count>0) { # Pick a direction to expand # For now we'll ignore bias $dir_pool = array(); $dir_available = 0; for ($d=0; $d<$offset_size; $d++) { # Figure out location of neighbour candidate $off_x = $candidates[$cand][0]+$this->offset[$d][0]; $off_y = $candidates[$cand][1]+$this->offset[$d][1]; $off_z = $candidates[$cand][2]+$this->offset[$d][2]; # Exclude it if it's taken or outside maze if ($off_x>=0 && $off_x<$this->x && $off_y>=0 && $off_y<$this->y && $off_z>=0 && $off_z<$this->z && $this->available[$off_x][$off_y][$off_z]) { $dir_pool[$dir_available++] = $d; } else if ($d == $dir) { # Disable direction continuation # if direction is blocked $dir = -1; } } if ($dir_available == 0) { # No directions available, remove candidate $cand_count--; $candidates[$cand] = $candidates[$cand_count]; $force_new_candidate = 1; } else { if ($this->debug>1) { print "Directions: ["; for ($i=0; $i<$dir_available; $i++) { print " " . $dir_pool[$i]; } print " ]\n"; } # At this point $dir_pool contains a list # of open directions # When bias is implemented, here is where # we'll need to do it if ($dir<0 || mt_rand()/mt_getrandmax() > $chance_straight) { $dir = $dir_pool[mt_rand(0, $dir_available-1)]; } if ($this->debug>1) { print "Direction $dir - offsets [ " . $this->offset[$dir][0] . ", " . $this->offset[$dir][1] . ", " . $this->offset[$dir][2] . " ] Candidate [" . $candidates[$cand][0].",".$candidates[$cand][1].",".$candidates[$cand][2]."]\n"; } # Calculate location of neighbouring cell $off_x = $candidates[$cand][0]+$this->offset[$dir][0]; $off_y = $candidates[$cand][1]+$this->offset[$dir][1]; $off_z = $candidates[$cand][2]+$this->offset[$dir][2]; if ($this->debug>1) { print "Offset: [$off_x, $off_y, $off_z]\n"; } # Mark new cell as unavailable & reduce poolsize $this->available[$off_x][$off_y][$off_z] = 0; $poolsize--; # Open relevant gates in maze $this->gates[$candidates[$cand][0]][$candidates[$cand][1]][$candidates[$cand][2]] += $this->mask[$dir]; # Don't forget neighbour... $this->gates[$off_x][$off_y][$off_z] += $this->mask[$dir^1]; # Add new cell to candidates pool if ($this->debug) { print $candidates[$cand][0].",".$candidates[$cand][1].",".$candidates[$cand][2]."(".$this->gates[$candidates[$cand][0]][$candidates[$cand][1]][$candidates[$cand][2]].") -> $off_x,$off_y,$off_z(" . $this->gates[$off_x][$off_y][$off_z] . ")\n"; } $candidates[$cand_count++] = $candidates[0]; $candidates[0] = array($off_x, $off_y, $off_z); } # Pick a candidate from our connected pool for next loop if (mt_rand()/mt_getrandmax() < $chance_run && !$force_new_candidate) { $cand = 0; } else { if ($cand_count>0) { $cand = mt_rand(0, $cand_count-1); } $force_new_candidate = 0; $dir = -1; } } } # We don't care about sqaured off ends, so we brute force it # PHP has a more elegant method which is probably more CPU intensive private function linethick($image, $x1, $y1, $x2, $y2, $colour, $xthick, $ythick) { for ($x=0; $x<$xthick; $x++) { for ($y=0; $y<$ythick; $y++) { if ($this->debug>5) { print "LINE FROM ".($x1+$x).",".($y1+$y)." TO ".($x2+$x).",".($y2+$y)." COLOUR ".$colour; } @imageline($image, $x1+$x, $y1+$y, $x2+$x, $y2+$y, $colour); } } } # Draw X at specified pixel location private function draw_x($image, $x1, $y1, $colour, $xpixel, $ypixel, $xthick, $ythick) { if ($this->debug) { print "X at $x1, $y1\n"; } $scale = 4; $this->linethick($image, $x1-floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $x1+floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); $this->linethick($image, $x1+floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $x1-floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); } # Draw O at specified pixel location private function draw_o($image, $x1, $y1, $colour, $xpixel, $ypixel, $xthick, $ythick) { if ($this->debug) { print "O at $x1, $y1\n"; } $scale = 4; $this->linethick($image, $x1-floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $x1+floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); $this->linethick($image, $x1+floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $x1+floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); $this->linethick($image, $x1-floor(($xpixel-$xthick)/$scale), $y1-floor(($ypixel-$ythick)/$scale), $x1-floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); $this->linethick($image, $x1-floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $x1+floor(($xpixel-$xthick)/$scale), $y1+floor(($ypixel-$ythick)/$scale), $colour, $xthick, $ythick); } # Render view of maze from above function render($xpixel, $ypixel, $boundary_x, $boundary_y, $zlayer) { # Create blank canvas (all gates blocked) if ($this->type == "rectangular") { # Create image $im = @imagecreate(($xpixel+$boundary_x)*$this->x + $boundary_x, ($ypixel+$boundary_y)*$this->y + $boundary_y); # Assign colours (white back, black fore) $back = @imagecolorallocate($im, 255, 255, 255); $front = @imagecolorallocate($im, 0, 0, 0); # Backfill with white @imagefilledrectangle( $im, 0, 0, ($xpixel+$boundary_x)*$this->x + $boundary_x -1, ($ypixel+$boundary_y)*$this->y + $boundary_y - 1, $back); # Fill left and top borders @imagefilledrectangle( $im, 0, 0, ($xpixel+$boundary_x)*$this->x + $boundary_x - 1, $boundary_y-1, $front); @imagefilledrectangle( $im, 0, 0, $boundary_x-1, ($ypixel+$boundary_y)*$this->y + $boundary_y - 1, $front); # Could do X + Y in one run, but splitting up allows # for efficiencies to be introduced later # Fill horizontal gates for ($y=0; $y<$this->y; $y++) { for ($x=0; $x<$this->x; $x++) { if ((($this->gates[$x][$y][$zlayer])&($this->mask[2]))==0) { @imagefilledrectangle( $im, ($xpixel+$boundary_x)*$x, ($ypixel+$boundary_y)*($y+1), ($xpixel+$boundary_x)*($x+1)+$boundary_x-1, ($ypixel+$boundary_y)*($y+1)+$boundary_y-1, $front); } } } # Fill vertical gates for ($x=0; $x<$this->x; $x++) { for ($y=0; $y<$this->y; $y++) { if ((($this->gates[$x][$y][$zlayer])&($this->mask[0]))==0) { @imagefilledrectangle( $im, ($xpixel+$boundary_x)*($x+1), ($ypixel+$boundary_y)*($y), ($xpixel+$boundary_x)*($x+1)+$boundary_x-1, ($ypixel+$boundary_y)*($y+1)+$boundary_y-1, $front); } } } $this->draw_x($im, $boundary_x+floor($xpixel/2), $boundary_y+floor($ypixel/2), $front, $xpixel, $ypixel, $boundary_x, $boundary_y); $this->draw_o($im, ($boundary_x+$xpixel)*($this->x-1)+floor($xpixel/2), ($boundary_y+$ypixel)*($this->y-1)+floor($ypixel/2), $front, $xpixel, $ypixel, $boundary_x, $boundary_y); return $im; } else { if ($this->debug) { print "Create hex image : size ".(($xpixel+$boundary_x)*($this->x+floor(($this->y+1)/2)) + $boundary_x).",".(($ypixel+$boundary_y)*$this->y + $boundary_y)."\n"; } # Create image $im1 = @imagecreate(($xpixel+$boundary_x)*($this->x+floor(($this->y+1)/2)) + $boundary_x, ($ypixel+$boundary_y)*$this->y + $boundary_y); # Assign colours (white back, black fore) $back = @imagecolorallocate($im1, 255, 255, 255); $front = @imagecolorallocate($im1, 0, 0, 0); # Create array with relative locations of hex vertices # Top $x1 = floor(($xpixel+$boundary_x)/2); $y1 = 0; # Bottom $x2 = floor(($xpixel+$boundary_x)/2); $y2 = $ypixel+$boundary_y; # Northwest $x3 = 0; $y3 = floor(($ypixel+$boundary_y)/3); # Southeast $x4 = $xpixel+$boundary_x; $y4 = $ypixel+$boundary_y - floor(($ypixel+$boundary_y)/3); # Southwest $x5 = 0; $y5 = $ypixel+$boundary_y - floor(($ypixel+$boundary_y)/3); # Northeast $x6 = $xpixel+$boundary_x; $y6 = floor(($ypixel+$boundary_y)/3); # Backfill with white @imagefilledrectangle( $im1, 0, 0, ($this->x+floor(($this->y+1)/2)) + $boundary_x -1, ($ypixel+$boundary_y)*$this->y + $boundary_y - 1, $back); # Don't fill borders as border locations vary # East, west, southwest, northeast, southeast, northwest # 0@4-6 1@3-5 2@5-2 3@1-6 4@2-4 5@1-3 $xmin = 1000000; $xmax = 0; for ($y=0; $y<$this->y; $y++) { for ($x=0; $x<$this->x; $x++) { if ($this->gates[$x][$y][$zlayer]==0) { continue; } $xbase = floor(($xpixel+$boundary_x)*($x+($this->y-$y)/2)); $xmin = min($xbase, $xmin); $xmax = max($xbase+$x4+$boundary_x, $xmax); $ybase = ($ypixel+$boundary_y - floor(($ypixel+$boundary_y)/3))*$y; if ($this->debug > 2) { print "Render hex ($x,$y) at ($xbase,$ybase)\n"; } if ((($this->gates[$x][$y][$zlayer])&($this->mask[0]))==0) { @$this->linethick( $im1, $xbase+$x4, $ybase+$y4, $xbase+$x6, $ybase+$y6, $front, $boundary_x, $boundary_y); } if ((($this->gates[$x][$y][$zlayer])&($this->mask[1]))==0) { @$this->linethick( $im1, $xbase+$x3, $ybase+$y3, $xbase+$x5, $ybase+$y5, $front, $boundary_x, $boundary_y); } if ((($this->gates[$x][$y][$zlayer])&($this->mask[2]))==0) { @$this->linethick( $im1, $xbase+$x5, $ybase+$y5, $xbase+$x2, $ybase+$y2, $front, $boundary_x, $boundary_y); } if ((($this->gates[$x][$y][$zlayer])&($this->mask[3]))==0) { @$this->linethick( $im1, $xbase+$x1, $ybase+$y1, $xbase+$x6, $ybase+$y6, $front, $boundary_x, $boundary_y); } if ((($this->gates[$x][$y][$zlayer])&($this->mask[4]))==0) { @$this->linethick( $im1, $xbase+$x4, $ybase+$y4, $xbase+$x2, $ybase+$y2, $front, $boundary_x, $boundary_y); } if ((($this->gates[$x][$y][$zlayer])&($this->mask[5]))==0) { @$this->linethick( $im1, $xbase+$x1, $ybase+$y1, $xbase+$x3, $ybase+$y3, $front, $boundary_x, $boundary_y); } } } $this->draw_x($im1, floor(($xpixel+$boundary_x)*($this->y+1)/2), $boundary_y+floor($ypixel/2), $front, $xpixel, $ypixel, $boundary_x, $boundary_y); $this->draw_o($im1, $xbase+$boundary_x+floor($xpixel/2), $ybase+$boundary_y+floor($ypixel/2), $front, $xpixel, $ypixel, $boundary_x, $boundary_y); # Crop image $im = imagecreatetruecolor($xmax-$xmin+1, ($ypixel+$boundary_y - floor(($ypixel+$boundary_y)/3))*($this->y+1)); imagecopy($im, $im1, 0, 0, $xmin, 0, $xmax-$xmin+1, ($ypixel+$boundary_y - floor(($ypixel+$boundary_y)/3))*($this->y+1)); imagedestroy($im1); return $im; } } # 3D view of maze - to be implemented later function render3D($xwindow, $ywindow, $x, $y, $z, $direction) { header("content-type: text/html" ); ?> 3D Maze view
z==1) { ?> z>1) { ?> z==1) { ?> z>1) { ?>
Status
\n" ?>
populate($bias_x, $bias_y, $bias_z, $bias_d, $chance_run, $chance_straight); if ($maze_view == "2D") { try { $maze_image = $maze->render($cell_x, $cell_y, $boundary_x, $boundary_y, 0); header("content-type: image/png" ); imagepng($maze_image); imagedestroy($maze_image); } catch (Exception $e) { header("content-type: text/plain" ); echo 'Caught exception: ', $e->getMessage(), "\n"; } } else { $maze->render3D($xwindow, $ywindow, mt_rand(0, $maze_x-1), mt_rand(0, $maze_y-1), mt_rand(0, $maze_z-1), mt_rand(0, $maze_z>1 ? 23 : 3)); } ?>