AStarPathfinder   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 63
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 11
eloc 38
c 1
b 0
f 0
dl 0
loc 63
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A withHeuristic() 0 3 1
B between() 0 45 9
A __construct() 0 5 1
1
<?php declare(strict_types=1);
2
3
namespace Stratadox\Pathfinder;
4
5
use const INF;
6
use SplPriorityQueue;
7
use Stratadox\Pathfinder\Reconstruction\PathRetracer;
8
9
final class AStarPathfinder implements SinglePathfinder
10
{
11
    private $environment;
12
    private $heuristic;
13
    private $path;
14
15
    private function __construct(Environment $environment, Heuristic $heuristic)
16
    {
17
        $this->environment = $environment;
18
        $this->heuristic = $heuristic;
19
        $this->path = new PathRetracer();
20
    }
21
22
    public static function withHeuristic(Heuristic $heuristic): self
23
    {
24
        return new self($heuristic->environment(), $heuristic);
25
    }
26
27
    public function between(string $start, string $goal): array
28
    {
29
        if (!$this->environment->has($start)) {
30
            throw NonExistingStart::tried($start);
31
        }
32
        if (!$this->environment->has($goal)) {
33
            throw NoSuchPath::between($start, $goal);
34
        }
35
        $alreadyVisited = [];
36
        $alreadyConsidered = [$start => true];
37
        $lastStepBefore = [];
38
        $distanceTo = [$start => 0];
39
        $underConsideration = new SplPriorityQueue();
40
        $underConsideration->insert($start, 0);
41
42
        while (!$underConsideration->isEmpty()) {
43
            $node = $underConsideration->top();
44
            $underConsideration->next();
45
46
            if ($goal === $node) {
47
                return $this->path->retrace($start, $goal, $lastStepBefore);
48
            }
49
50
            foreach ($this->environment->neighboursOf($node) as $neighbour) {
51
                if (isset($alreadyVisited[$neighbour])) {
52
                    continue;
53
                }
54
                $alreadyVisited[$neighbour] = true;
55
                $cost = ($distanceTo[$node] ?? INF) +
56
                    $this->environment->movementCostBetween($node, $neighbour);
57
58
                if (!isset($alreadyConsidered[$neighbour])) {
59
                    $underConsideration->insert(
60
                        $neighbour,
61
                        -($cost + $this->heuristic->estimate($neighbour, $goal))
62
                    );
63
                    $alreadyConsidered[$neighbour] = true;
64
                    if ($cost < ($distanceTo[$neighbour] ?? INF)) {
65
                        $distanceTo[$neighbour] = $cost;
66
                        $lastStepBefore[$neighbour] = $node;
67
                    }
68
                }
69
            }
70
        }
71
        throw NoSuchPath::between($start, $goal);
72
    }
73
}
74