GridEnvironment::isAvailable()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 2
nc 2
nop 2
1
<?php declare(strict_types=1);
2
3
namespace Stratadox\Pathfinder\Graph\Builder;
4
5
use function array_merge;
6
use function array_reduce;
7
use function chr;
8
use Stratadox\Pathfinder\Environment;
9
use Stratadox\Pathfinder\Graph\At;
10
use Stratadox\Pathfinder\Graph\Edges;
11
use Stratadox\Pathfinder\Graph\GeometricGraph;
12
use Stratadox\Pathfinder\Graph\Location;
13
use Stratadox\Pathfinder\Graph\Road;
14
use Stratadox\Pathfinder\Graph\Roads;
15
16
final class GridEnvironment
17
{
18
    private const MOVEMENT = [
19
        [
20
            ['x' => -1, 'y' => 0],
21
            ['x' => 0, 'y' => -1],
22
            ['x' => 0, 'y' => 1],
23
            ['x' => 1, 'y' => 0],
24
        ],
25
        [
26
            ['x' => -1, 'y' => 0],
27
            ['x' => -1, 'y' => -1],
28
            ['x' => 1, 'y' => -1],
29
            ['x' => 0, 'y' => -1],
30
            ['x' => 0, 'y' => 1],
31
            ['x' => 1, 'y' => 1],
32
            ['x' => 1, 'y' => -1],
33
            ['x' => 1, 'y' => 0],
34
        ],
35
    ];
36
37
    /** @var Field[][] */
38
    private $rows;
39
    private $allowDiagonal;
40
41
    private function __construct(bool $allowDiagonal, ...$rows)
42
    {
43
        $this->allowDiagonal = $allowDiagonal;
44
        $this->rows = $rows;
45
    }
46
47
    public static function create(): self
48
    {
49
        return new self(false);
50
    }
51
52
    public static function fromArray(array $gridData): self
53
    {
54
        $grid = self::create();
55
        foreach ($gridData as $row => $columns) {
56
            $squares = [];
57
            foreach ($columns as $column => $price) {
58
                $label = self::textual($column) . ($row + 1);
59
                $squares[] = $price !== INF
60
                    ? Square::labeled($label)->costing($price)
61
                    : Obstacle::here();
62
            }
63
            $grid = $grid->withRow(...$squares);
64
        }
65
        return $grid;
66
    }
67
68
    private static function textual(int $column): string
69
    {
70
        $finalPart = $column % 26;
71
        $letter = chr(65 + $finalPart);
72
        $firstPart = (int) ($column / 26);
73
        if ($firstPart <= 0) {
74
            return $letter;
75
        }
76
        return self::textual($firstPart - 1) . $letter;
77
    }
78
79
    public function withRow(Field ...$squares): self
80
    {
81
        return new self(
82
            $this->allowDiagonal,
83
            ...array_merge($this->rows, [$squares])
84
        );
85
    }
86
87
    public function diagonalMovementAllowed(): self
88
    {
89
        return new self(true, ...$this->rows);
90
    }
91
92
    public function make(): Environment
93
    {
94
        $locations = [];
95
        foreach ($this->rows as $row => $fields) {
96
            foreach ($fields as $column => $field) {
97
                if ($field->isBlocked()) {
98
                    continue;
99
                }
100
                $locations[] = Location::at(
101
                    At::position($column, $row),
102
                    $field->label(),
103
                    $this->edgesStartingAt($row, $column)
104
                );
105
            }
106
        }
107
        return GeometricGraph::with(...$locations);
108
    }
109
110
    private function edgesStartingAt(int $row, int $column): Edges
111
    {
112
        return Roads::available(...array_reduce(
113
            self::MOVEMENT[$this->allowDiagonal],
114
            function (Edges $result, array $add) use ($row, $column): Edges {
115
                return $this->addIfAvailable(
116
                    $result,
117
                    $row + $add['x'],
118
                    $column + $add['y']
119
                );
120
            },
121
            Roads::none()
122
        ));
123
    }
124
125
    private function addIfAvailable(Edges $edges, int $row, int $column): Edges
126
    {
127
        return $this->isAvailable($row, $column)
128
            ? $edges->add(Road::towards(
129
                $this->targetAt($row, $column),
130
                $this->costOfGettingTo($row, $column)
131
            ))
132
            : $edges;
133
    }
134
135
    private function isAvailable(int $row, int $column): bool
136
    {
137
        return isset($this->rows[$row][$column])
138
            && !$this->rows[$row][$column]->isBlocked();
139
    }
140
141
    private function targetAt(int $row, int $column): string
142
    {
143
        return $this->rows[$row][$column]->label();
144
    }
145
146
    private function costOfGettingTo(int $row, int $column): float
147
    {
148
        return $this->rows[$row][$column]->price();
149
    }
150
}
151