i have a map stored as a multidimensional array ($map[row][col]
) and i'd wish to create a path from point A to point B.
since i can have some obstacles with turns, corners etc etc, i'd wish to use the A* search to calculate the fastest path.
so the general function isf(x) = g(x) + h(x)
and i have all of these values. g(x)
is cost of the move (and it's saved on the map); h(x)
is the linear distance between A and B.
so i have everything i need, but i have a question: how can i organize everything?
i have no need to test for alternative paths, since a square on the map can be passable or not, so when i reach the target it should be the shortest one.
how can i organize everything?
i tried with multidimensional array, but i get lost.. :(
EDIT
i worked out some code, it's pretty a wall of text :)
//$start = array(28, 19), $end = array(14, 19)
//$this->map->map is a multidimensional array, everything has a cost of 1, except for
//blocking squares that cost 99
//$this->map->map == $this->radar
//blocking square at 23-17, 22-18, 22-19, 22-20, 23-21, 19-17, 20-18,20-19,20-20,19-21
//they are like 2 specular mustache :P
function createPath($start, $end)
{
$found = false;
$temp = $this->cost($start, $end);
foreach($temp as $t){
if($t['cost'] == $this->map->map[$end[0]][$end[1]]) $found = true;
$this->costStack[$t['cost']][] = array('grid' => $t['grid'], 'dir' => $t['dir']);
}
ksort($this->costStack);
if(!$found) {
foreach($this->costStack as $k => $stack){
foreach($stack as $kn => $node){
$curNode = $node['grid'];
unset($this->costStack[$k][$kn]);
break;
}
if(!count($this->costStack[$k])) unset($this->costStack[$k]);
break;
}
$this->createPath($curNode, $end);
}
}
function cost($current, $target)
{
$return = array();
//$AIM = array('n' => array(-1, 0),'e' => array( 0, 1),'s' => array( 1, 0),'w' => array( 0, -1));
foreach($this->AIM as $direction => $offset){
$position[0] = $current[0] + $offset[0];
$position[1] = $current[1] + $offset[1];
//radar is a copy of the map
if ( $this->radar[$position[0]][$position[1]] == 'V') continue;
else $this->radar[$position[0]][$position[1]] = 'V';
$h = (int) $this->distance($position, $target);
$g = $this->map->map[$position[0]][$position[1]];
$return[] = array('grid' => $position,
'dir' => $direction,
'cost' => $h + $g);
}
return $return;
}
i hope you can understand everything, i tried to be clear as much as possible.
finally i can get to my destination, expanding only cheaper nodes, but now i have a problem.
how can i turn it into directions? i have to store a stack of orders (ie n, n, e etc etc), how can i identify a path inside these values?
My structure was:
I'll try to collect some code samples from a prior project, could take a while though.
(found my old project ;))
It's probably not exactly what you're looking for, but maybe it's a start.
So using the files below, and mazes defined like:
00000000000000000000000 00000000000000000000000 0000000000W000000000000 0000000000W000000000000 0000000000W000000000000 0000000000W00000WWWWWWW 0000000000W000000000000 S000000000W00000000000E
(test/maze.txt)
You'll get something like this:
00000000000000000000000 0000000000X000000000000 000000000XWXX0000000000 00000000X0W00X000000000 000000XX00W000X00000000 00000X0000W0000XWWWWWWW 0000X00000W00000XXX0000 SXXX000000W00000000XXXE
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');
header('Content-Type: text/plain; charset="utf-8"');
// simple autoloader
function __autoload($className) {
$path = '/lib/' . str_replace('_', '/', $className) . '.php';
foreach (explode(PATH_SEPARATOR, get_include_path()) as $prefix) {
if (file_exists($prefix . $path)) {
require_once $prefix . $path;
}
}
}
// init maze
$maze = new Maze_Reader('test/maze.txt');
$startNode = $maze->getByValue('S', true);
$endNode = $maze->getByValue('E', true);
$astar = new AStar;
if ($astar->getPath($startNode, $endNode)) {
do {
if (!in_array($endNode->value(), array('S', 'E'))) {
$endNode->value('X');
}
} while ($endNode = $endNode->predecessor());
}
echo $maze;
/**
* A simple AStar implementation
*/
class AStar
{
protected $openList;
protected $closedList;
/**
* Constructs the astar object
*/
public function __construct() {
$this->openList = new PriorityQueue;
$this->closedList = new SplObjectStorage;
}
public function getPath($startNode, $endNode) {
$this->openList->insert(0, $startNode);
while (!$this->openList->isEmpty()) {
$currentNode = $this->openList->extract();
if ($currentNode->equals($endNode)) {
return $currentNode;
}
$this->expandNode($currentNode, $endNode);
$this->closedList[$currentNode] = true;
}
return false;
}
protected function expandNode($currentNode, $endNode) {
foreach ($currentNode->successors() as $successor) {
if (isset($this->closedList[$successor])) {
continue;
}
$tentative_g = $currentNode->g() + $currentNode->distance($successor);
if ($this->openList->indexOf($successor) > -1 && $tentative_g >= $successor->g()) {
continue;
}
$successor->predecessor($currentNode);
$successor->g($tentative_g);
$f = $tentative_g + $successor->distance($endNode);
if ($this->openList->indexOf($successor) > -1) {
$this->openList->changeKey($successor, $f);
continue;
}
$this->openList->insert($f, $successor);
}
}
}
class PriorityQueue
{
protected $keys = array();
protected $values = array();
/**
* Helper function to swap two <key>/<value> pairs
*
* @param Integer a
* @param Integer b
* @return Integer b
*/
protected function swap($a, $b) {
// swap keys
$c = $this->keys[$a];
$this->keys[$a] = $this->keys[$b];
$this->keys[$b] = $c;
// swap values
$c = $this->values[$a];
$this->values[$a] = $this->values[$b];
$this->values[$b] = $c;
return $b;
}
/**
* Heapify up
*
* @param Integer pos
* @return void
*/
protected function upHeap($pos) {
while ($pos > 0) {
$parent = ($pos - 1) >> 2;
if ($this->compare($this->keys[$pos], $this->keys[$parent]) >= 0) {
break;
}
$pos = $this->swap($pos, $parent);
}
}
/**
* Heapify down
*
* @param Integer pos
* @return void
*/
protected function downHeap($pos) {
$len = sizeof($this->keys);
$max = ($len - 1) / 2;
while ($pos < $max) {
$child = 2 * $pos + 1;
if ($child < $len - 1 && $this->compare($this->keys[$child], $this->keys[$child + 1]) > 0) {
$child += 1;
}
if ($this->compare($this->keys[$pos], $this->keys[$child]) <= 0) {
break;
}
$pos = $this->swap($pos, $child);
}
}
/**
* Insert an <key>/<value> pair into the queue
*
* @param Object key
* @param Object value
* @return this
*/
public function insert($key, $value) {
$this->keys[] = $key;
$this->values[] = $value;
$this->upHeap(sizeof($this->keys) - 1);
return $this;
}
/**
* Extract the top <value>
*
* @return Object
*/
public function extract() {
$resultValue = $this->values[0];
$lastValue = array_pop($this->values);
$lastKey = array_pop($this->keys);
if (sizeof($this->keys) > 0) {
$this->values[0] = $lastValue;
$this->keys[0] = $lastKey;
$this->downHeap(0);
}
return $resultValue;
}
/**
* Changes the <key> of a <value>
*
* @param Object key
* @param Object value
* @return this
*/
public function changeKey($key, $value) {
$pos = $this->indexOf($value);
if ($pos !== false) {
$this->keys[$pos] = $key;
$this->upHeap($pos);
}
return $this;
}
/**
* Returns the index of <value> or false if <value> is not in the queue
*
* @return false|Int
*/
public function indexOf($value) {
return array_search($value, $this->values, true);
}
/**
* Used to campare two <key>s.
*
* @param Object a
* @param Object b
* @return Number
*/
protected function compare($a, $b) {
return $a - $b;
}
/**
* Returns true if the queue is empty
*
* @return Boolean
*/
public function isEmpty() {
return sizeof($this->keys) === 0;
}
}
class Maze_Reader implements IteratorAggregate
{
/**
* The initial maze
* @var string
*/
protected $rawMaze;
/**
* A tow dimensional array holding the parsed maze
* @var array
*/
protected $map = array();
/**
* A flat array holding all maze nodes
* @var array
*/
protected $nodes = array();
/**
* A value map for easier access
* @var array
*/
protected $valueMap = array();
/**
* Constructs a maze reader
*
* @param string $file A path to a maze file
*/
public function __construct($file) {
$this->rawMaze = file_get_contents($file);
$this->parseMaze($this->rawMaze);
}
/**
* Parses the raw maze into usable Maze_Nodes
*
* @param string $maze
*/
protected function parseMaze($maze) {
foreach (explode("\n", $maze) as $y => $row) {
foreach (str_split(trim($row)) as $x => $cellValue) {
if (!isset($this->map[$x])) {
$this->map[$x] = array();
}
if (!isset($this->valueMap[$cellValue])) {
$this->valueMap[$cellValue] = array();
}
$this->nodes[] = new Maze_Node($x, $y, $cellValue, $this);;
$this->map[$x][$y] =& $this->nodes[sizeof($this->nodes) - 1];
$this->valueMap[$cellValue][] =& $this->nodes[sizeof($this->nodes) - 1];
}
}
}
/**
* Returns the neighobrs of $node
*
* @return array
*/
public function getNeighbors(Maze_Node $node) {
$result = array();
$top = $node->y() - 1;
$right = $node->x() + 1;
$bottom = $node->y() + 1;
$left = $node->x() - 1;
// top left
if (isset($this->map[$left], $this->map[$left][$top])) {
$result[] = $this->map[$left][$top];
}
// top center
if (isset($this->map[$node->x()], $this->map[$node->x()][$top])) {
$result[] = $this->map[$node->x()][$top];
}
// top right
if (isset($this->map[$right], $this->map[$right][$top])) {
$result[] = $this->map[$right][$top];
}
// right
if (isset($this->map[$right], $this->map[$right][$node->y()])) {
$result[] = $this->map[$right][$node->y()];
}
// bottom right
if (isset($this->map[$right], $this->map[$right][$bottom])) {
$result[] = $this->map[$right][$bottom];
}
// bottom center
if (isset($this->map[$node->x()], $this->map[$node->x()][$bottom])) {
$result[] = $this->map[$node->x()][$bottom];
}
// bottom left
if (isset($this->map[$left], $this->map[$left][$bottom])) {
$result[] = $this->map[$left][$bottom];
}
// left
if (isset($this->map[$left], $this->map[$left][$node->y()])) {
$result[] = $this->map[$left][$node->y()];
}
return $result;
}
/**
* @IteratorAggregate
*/
public function getIterator() {
return new ArrayIterator($this->nodes);
}
/**
* Returns a node by value
*
* @param mixed $value
* @param boolean $returnOne
* @param mixed $fallback
* @return mixed
*/
public function getByValue($value, $returnOne = false, $fallback = array()) {
$result = isset($this->valueMap[$value]) ? $this->valueMap[$value] : $fallback;
if ($returnOne && is_array($result)) {
$result = array_shift($result);
}
return $result;
}
/**
* Simple output
*/
public function __toString() {
$result = array();
foreach ($this->map as $x => $col) {
foreach ($col as $y => $node) {
$result[$y][$x] = (string)$node;
}
}
return implode("\n", array_map('implode', $result));
}
}
class Maze_Node
{
protected $x;
protected $y;
protected $value;
protected $maze;
protected $g;
protected $predecessor;
/**
* @param Integer $x
* @param Integer $y
* @param mixed $value
* @param Maze_Reader $maze
*/
public function __construct($x, $y, $value, $maze) {
$this->x = $x;
$this->y = $y;
$this->value = $value;
$this->maze = $maze;
}
/**
* Getter for x
*
* @return Integer
*/
public function x() {
return $this->x;
}
/**
* Getter for y
*
* @return Integer
*/
public function y() {
return $this->y;
}
/**
* Setter/Getter for g
*
* @param mixed $g
* @return mixed
*/
public function g($g = null) {
if ($g !== null) {
$this->g = $g;
}
return $this->g;
}
/**
* Setter/Getter for value
*
* @param mixed $value
* @return mixed
*/
public function value($value = null) {
if ($value !== null) {
$this->value = $value;
}
return $this->value;
}
/**
* Setter/Getter for predecessor
*
* @param Maze_Node $predecessor
* @return Maze_Node|null
*/
public function predecessor(Maze_Node $predecessor = null) {
if ($predecessor !== null) {
$this->predecessor = $predecessor;
}
return $this->predecessor;
}
/**
* simple distance getter
*
* @param Maze_Node $that
* @return Float
*/
public function distance(Maze_Node $that) {
if ($that->value() === 'W') {
return PHP_INT_MAX;
}
return sqrt(pow($that->x() - $this->x, 2) + pow($that->y() - $this->y, 2));
}
/**
* Test for equality
*
* @param Maze_Node $that
* @return boolean
*/
public function equals(Maze_Node $that) {
return $this == $that;
}
/**
* Returns the successors of this node
*
* @return array
*/
public function successors() {
return $this->maze->getNeighbors($this);
}
/**
* For debugging
*
* @return string
*/
public function __toString() {
return (string)$this->value;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With