Completed
Push — new-implementation ( 0cc7b9 )
by Jose
11:19
created

AStar::run()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace JMGQ\AStar;
4
5
class AStar
6
{
7
    private DomainLogicInterface $domainLogic;
8
    private NodeCollectionInterface $openList;
9
    private NodeCollectionInterface $closedList;
10
11 12
    public function __construct(DomainLogicInterface $domainLogic)
12
    {
13 12
        $this->domainLogic = $domainLogic;
14 12
        $this->openList = new NodeHashTable();
15 12
        $this->closedList = new NodeHashTable();
16 12
    }
17
18
    /**
19
     * @param mixed $start
20
     * @param mixed $goal
21
     * @return mixed[]
22
     */
23 12
    public function run(mixed $start, mixed $goal): iterable
24
    {
25 12
        $startNode = new Node($start);
26 12
        $goalNode = new Node($goal);
27
28 12
        return $this->executeAlgorithm($startNode, $goalNode);
29
    }
30
31
    /**
32
     * @param Node $start
33
     * @param Node $goal
34
     * @return mixed[]
35
     */
36 12
    private function executeAlgorithm(Node $start, Node $goal): iterable
37
    {
38 12
        $path = [];
39
40 12
        $this->clear();
41
42 12
        $start->setG(0);
43 12
        $start->setH($this->calculateEstimatedCost($start, $goal));
44
45 12
        $this->openList->add($start);
46
47 12
        while (!$this->openList->isEmpty()) {
48
            /** @var Node $currentNode Cannot be null because the open list is not empty */
49 12
            $currentNode = $this->openList->extractBest();
50
51 12
            $this->closedList->add($currentNode);
52
53 12
            if ($currentNode->getId() === $goal->getId()) {
54 11
                $path = $this->generatePathFromStartNodeTo($currentNode);
55 11
                break;
56
            }
57
58 10
            $successors = $this->computeAdjacentNodes($currentNode, $goal);
59
60 10
            foreach ($successors as $successor) {
61 9
                if ($this->openList->contains($successor)) {
62
                    /** @var Node $successorInOpenList Cannot be null because the open list contains it */
63 7
                    $successorInOpenList = $this->openList->get($successor->getId());
64
65 7
                    if ($successor->getG() >= $successorInOpenList->getG()) {
66 6
                        continue;
67
                    }
68
                }
69
70 9
                if ($this->closedList->contains($successor)) {
71
                    /** @var Node $successorInClosedList Cannot be null because the closed list contains it */
72 7
                    $successorInClosedList = $this->closedList->get($successor->getId());
73
74 7
                    if ($successor->getG() >= $successorInClosedList->getG()) {
75 7
                        continue;
76
                    }
77
                }
78
79 9
                $successor->setParent($currentNode);
80
81 9
                $this->closedList->remove($successor);
82
83 9
                $this->openList->add($successor);
84
            }
85
        }
86
87 12
        return $path;
88
    }
89
90
    /**
91
     * Sets the algorithm to its initial state
92
     */
93 12
    private function clear(): void
94
    {
95 12
        $this->openList->clear();
96 12
        $this->closedList->clear();
97 12
    }
98
99
    /**
100
     * @param Node $node
101
     * @return Node[]
102
     */
103 10
    private function generateAdjacentNodes(Node $node): iterable
104
    {
105 10
        $adjacentNodes = [];
106
107 10
        $adjacentStates = $this->domainLogic->getAdjacentNodes($node->getState());
108
109 10
        foreach ($adjacentStates as $state) {
110 9
            $adjacentNodes[] = new Node($state);
111
        }
112
113 10
        return $adjacentNodes;
114
    }
115
116 9
    private function calculateRealCost(Node $node, Node $adjacent): float | int
117
    {
118 9
        $state = $node->getState();
119 9
        $adjacentState = $adjacent->getState();
120
121 9
        return $this->domainLogic->calculateRealCost($state, $adjacentState);
122
    }
123
124 12
    private function calculateEstimatedCost(Node $start, Node $end): float | int
125
    {
126 12
        $startState = $start->getState();
127 12
        $endState = $end->getState();
128
129 12
        return $this->domainLogic->calculateEstimatedCost($startState, $endState);
130
    }
131
132
    /**
133
     * @param Node $node
134
     * @return mixed[]
135
     */
136 11
    private function generatePathFromStartNodeTo(Node $node): iterable
137
    {
138 11
        $path = [];
139
140 11
        $currentNode = $node;
141
142 11
        while ($currentNode !== null) {
143 11
            array_unshift($path, $currentNode->getState());
144
145 11
            $currentNode = $currentNode->getParent();
146
        }
147
148 11
        return $path;
149
    }
150
151
    /**
152
     * @param Node $node
153
     * @param Node $goal
154
     * @return Node[]
155
     */
156 10
    private function computeAdjacentNodes(Node $node, Node $goal): iterable
157
    {
158 10
        $nodes = $this->generateAdjacentNodes($node);
159
160 10
        foreach ($nodes as $adjacentNode) {
161 9
            $adjacentNode->setG($node->getG() + $this->calculateRealCost($node, $adjacentNode));
162 9
            $adjacentNode->setH($this->calculateEstimatedCost($adjacentNode, $goal));
163
        }
164
165 10
        return $nodes;
166
    }
167
}
168