Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
64 / 64
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Grid
100.00% covered (success)
100.00%
64 / 64
100.00% covered (success)
100.00%
5 / 5
39
100.00% covered (success)
100.00%
1 / 1
 createGridFromArray
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 setNode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isWalkable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 getNeighbors
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
1 / 1
25
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Algorithm\PathFinding
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 *
13 * Extended based on:
14 * MIT License
15 * (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com>
16 * (c) PathFinding.js
17 */
18declare(strict_types=1);
19
20namespace phpOMS\Algorithm\PathFinding;
21
22/**
23 * Grid of nodes.
24 *
25 * @package phpOMS\Algorithm\PathFinding
26 * @license OMS License 2.0
27 * @link    https://jingga.app
28 * @since   1.0.0
29 */
30class Grid
31{
32    /**
33     * Grid system containing all nodes
34     *
35     * @var array<int, array<int, Node>>
36     * @since 1.0.0
37     */
38    private array $nodes = [[]];
39
40    /**
41     * Create a grid from an array
42     *
43     * [
44     *      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
45     *      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,],
46     *      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
47     *      [0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,],
48     *      [0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 9, 0, 0, 0, 0,],
49     *      [0, 0, 1, 0, 9, 0, 0, 0, 0, 0, 9, 0, 9, 9, 9,],
50     *      [0, 0, 0, 0, 9, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0,],
51     *      [0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
52     *      [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 9, 9, 9, 9, 0,],
53     *      [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0,],
54     *      [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 9, 9, 0, 0, 0,],
55     *      [0, 0, 0, 0, 0, 9, 9, 9, 0, 0, 9, 2, 0, 0, 0,],
56     *      [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 0, 0, 0,],
57     *      [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
58     *      [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
59     * ]
60     *
61     * @param array<int, int[]> $gridArray Grid defined in an array (0 = empty, 1 = start, 2 = end, 9 = not walkable)
62     * @param string            $node      Node type name
63     *
64     * @return Grid
65     *
66     * @since 1.0.0
67     */
68    public static function createGridFromArray(array $gridArray, string $node) : self
69    {
70        $grid = new self();
71        foreach ($gridArray as $y => $yRow) {
72            foreach ($yRow as $x => $xElement) {
73                if ($xElement === 0 || $xElement === 1 || $xElement === 2) {
74                    /** @var \phpOMS\Algorithm\PathFinding\Node $empty */
75                    $empty = new $node($x, $y, 1.0, true);
76                    $grid->setNode($x, $y, $empty);
77                } elseif ($xElement === 9) {
78                    /** @var \phpOMS\Algorithm\PathFinding\Node $wall */
79                    $wall = new $node($x, $y, 1.0, false);
80                    $grid->setNode($x, $y, $wall);
81                }
82            }
83        }
84
85        return $grid;
86    }
87
88    /**
89     * Set node at position
90     *
91     * @param int  $x    X-Coordinate
92     * @param int  $y    Y-Coordinate
93     * @param Node $node Node to set
94     *
95     * @return void
96     *
97     * @since 1.0.0
98     */
99    public function setNode(int $x, int $y, Node $node) : void
100    {
101        $this->nodes[$y][$x] = $node;
102    }
103
104    /**
105     * Get node at position
106     *
107     * @param int $x X-Coordinate
108     * @param int $y Y-Coordinate
109     *
110     * @return null|Node
111     *
112     * @since 1.0.0
113     */
114    public function getNode(int $x, int $y) : ?Node
115    {
116        if (!isset($this->nodes[$y]) || !isset($this->nodes[$y][$x])) {
117            return null;
118        }
119
120        return $this->nodes[$y][$x];
121    }
122
123    /**
124     * Is node walkable"
125     *
126     * @param int $x X-Coordinate
127     * @param int $y Y-Coordinate
128     *
129     * @return bool
130     *
131     * @since 1.0.0
132     */
133    public function isWalkable(int $x, int $y) : bool
134    {
135        return isset($this->nodes[$y]) && isset($this->nodes[$y][$x]) && $this->nodes[$y][$x]->isWalkable;
136    }
137
138    /**
139     * Get neighbors of node
140     *
141     * @param Node $node     Node to get neighbors from
142     * @param int  $movement Allowed movements
143     *
144     * @return Node[]
145     *
146     * @since 1.0.0
147     */
148    public function getNeighbors(Node $node, int $movement) : array
149    {
150        $x = $node->getX();
151        $y = $node->getY();
152
153        $neighbors = [];
154
155        $s0 = false;
156        $s1 = false;
157        $s2 = false;
158        $s3 = false;
159        $d0 = false;
160        $d1 = false;
161        $d2 = false;
162        $d3 = false;
163
164        if ($this->isWalkable($x, $y - 1)) {
165            $neighbors[] = $this->getNode($x, $y - 1);
166            $s0          = true;
167        }
168
169        if ($this->isWalkable($x + 1, $y)) {
170            $neighbors[] = $this->getNode($x + 1, $y);
171            $s1          = true;
172        }
173
174        if ($this->isWalkable($x, $y + 1)) {
175            $neighbors[] = $this->getNode($x, $y + 1);
176            $s2          = true;
177        }
178
179        if ($this->isWalkable($x - 1, $y)) {
180            $neighbors[] = $this->getNode($x - 1, $y);
181            $s3          = true;
182        }
183
184        if ($movement === MovementType::STRAIGHT) {
185            /** @var Node[] $neighbors */
186            return $neighbors;
187        }
188
189        if ($movement === MovementType::DIAGONAL_NO_OBSTACLE) {
190            $d0 = $s3 && $s0;
191            $d1 = $s0 && $s1;
192            $d2 = $s1 && $s2;
193            $d3 = $s2 && $s3;
194        } elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) {
195            $d0 = $s3 || $s0;
196            $d1 = $s0 || $s1;
197            $d2 = $s1 || $s2;
198            $d3 = $s2 || $s3;
199        } elseif ($movement === MovementType::DIAGONAL) {
200            $d0 = true;
201            $d1 = true;
202            $d2 = true;
203            $d3 = true;
204        }
205
206        if ($d0 && $this->isWalkable($x - 1, $y - 1)) {
207            $neighbors[] = $this->getNode($x - 1, $y - 1);
208        }
209
210        if ($d1 && $this->isWalkable($x + 1, $y - 1)) {
211            $neighbors[] = $this->getNode($x + 1, $y - 1);
212        }
213
214        if ($d2 && $this->isWalkable($x + 1, $y + 1)) {
215            $neighbors[] = $this->getNode($x + 1, $y + 1);
216        }
217
218        if ($d3 && $this->isWalkable($x - 1, $y + 1)) {
219            $neighbors[] = $this->getNode($x - 1, $y + 1);
220        }
221
222        /** @var Node[] $neighbors */
223        return $neighbors;
224    }
225}