Passed
Push — master ( 152a70...0c8393 )
by Jose
03:00
created

AStar   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 20
eloc 60
c 2
b 0
f 0
dl 0
loc 167
ccs 68
cts 68
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 6 1
A __construct() 0 5 1
A generatePathFromStartNodeTo() 0 13 2
A calculateRealCost() 0 6 1
A nodeAlreadyPresentInListWithBetterOrSameRealCost() 0 14 3
B executeAlgorithm() 0 42 6
A clear() 0 4 1
A calculateEstimatedCost() 0 6 1
A generateAdjacentNodes() 0 11 2
A getAdjacentNodesWithTentativeScore() 0 10 2
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->getAdjacentNodesWithTentativeScore($currentNode, $goal);
59
60 10
            foreach ($successors as $successor) {
61 9
                if ($this->nodeAlreadyPresentInListWithBetterOrSameRealCost($successor, $this->openList)) {
62 6
                    continue;
63
                }
64
65 9
                if ($this->nodeAlreadyPresentInListWithBetterOrSameRealCost($successor, $this->closedList)) {
66 7
                    continue;
67
                }
68
69 9
                $successor->setParent($currentNode);
70
71 9
                $this->closedList->remove($successor);
72
73 9
                $this->openList->add($successor);
74
            }
75
        }
76
77 12
        return $path;
78
    }
79
80
    /**
81
     * Sets the algorithm to its initial state
82
     */
83 12
    private function clear(): void
84
    {
85 12
        $this->openList->clear();
86 12
        $this->closedList->clear();
87 12
    }
88
89
    /**
90
     * @param Node $node
91
     * @return Node[]
92
     */
93 10
    private function generateAdjacentNodes(Node $node): iterable
94
    {
95 10
        $adjacentNodes = [];
96
97 10
        $adjacentStates = $this->domainLogic->getAdjacentNodes($node->getState());
98
99 10
        foreach ($adjacentStates as $state) {
100 9
            $adjacentNodes[] = new Node($state);
101
        }
102
103 10
        return $adjacentNodes;
104
    }
105
106 9
    private function calculateRealCost(Node $node, Node $adjacent): float | int
107
    {
108 9
        $state = $node->getState();
109 9
        $adjacentState = $adjacent->getState();
110
111 9
        return $this->domainLogic->calculateRealCost($state, $adjacentState);
112
    }
113
114 12
    private function calculateEstimatedCost(Node $start, Node $end): float | int
115
    {
116 12
        $startState = $start->getState();
117 12
        $endState = $end->getState();
118
119 12
        return $this->domainLogic->calculateEstimatedCost($startState, $endState);
120
    }
121
122
    /**
123
     * @param Node $node
124
     * @return mixed[]
125
     */
126 11
    private function generatePathFromStartNodeTo(Node $node): iterable
127
    {
128 11
        $path = [];
129
130 11
        $currentNode = $node;
131
132 11
        while ($currentNode !== null) {
133 11
            array_unshift($path, $currentNode->getState());
134
135 11
            $currentNode = $currentNode->getParent();
136
        }
137
138 11
        return $path;
139
    }
140
141
    /**
142
     * @param Node $node
143
     * @param Node $goal
144
     * @return Node[]
145
     */
146 10
    private function getAdjacentNodesWithTentativeScore(Node $node, Node $goal): iterable
147
    {
148 10
        $nodes = $this->generateAdjacentNodes($node);
149
150 10
        foreach ($nodes as $adjacentNode) {
151 9
            $adjacentNode->setG($node->getG() + $this->calculateRealCost($node, $adjacentNode));
152 9
            $adjacentNode->setH($this->calculateEstimatedCost($adjacentNode, $goal));
153
        }
154
155 10
        return $nodes;
156
    }
157
158 9
    private function nodeAlreadyPresentInListWithBetterOrSameRealCost(
159
        Node $node,
160
        NodeCollectionInterface $nodeList
161
    ): bool {
162 9
        if ($nodeList->contains($node)) {
163
            /** @var Node $nodeInList Cannot be null because the list contains it */
164 8
            $nodeInList = $nodeList->get($node->getId());
165
166 8
            if ($node->getG() >= $nodeInList->getG()) {
167 7
                return true;
168
            }
169
        }
170
171 9
        return false;
172
    }
173
}
174