-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay15.php
278 lines (240 loc) · 7.59 KB
/
Day15.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<?php
/**
* Solution for Advent of Code Day 15.
*
* @Author: Digitalbüro Mokorana
* @Date: 2024-12-15 11:11:26
* @Last Modified by: Stefan Koch <stefan.koch@mokorana.de>
* @Last Modified time: 2024-12-15 19:02:38
*
* @package Aoc
*/
namespace Aoc;
/**
* The class is used, it's just called dynamically from App.php.
*
* @psalm-suppress UnusedClass
*/
class Day15 extends AbstractDay
{
/**
* Movement directions represented as [row, column].
*/
private const DIRECTIONS = [
'^' => [-1, 0], // NORTH
'>' => [0, 1], // EAST
'v' => [1, 0], // SOUTH
'<' => [0, -1] // WEST
];
/**
* Moves a box in the specified direction if possible.
*
* @param array $grid The grid representation.
* @param int $x The current row of the box.
* @param int $y The current column of the box.
* @param string $m The movement direction.
* @return bool True if the box was moved, false otherwise.
*/
private function moveBoxes(array &$grid, int $x, int $y, string $m): bool
{
$oX = $x;
$oY = $y;
while (true) {
$bX = $x + static::DIRECTIONS[$m][0];
$bY = $y + static::DIRECTIONS[$m][1];
if ($grid[$bX][$bY] == '.') {
$grid[$bX][$bY] = 'O';
$grid[$oX][$oY] = '.';
return true;
}
if ($grid[$bX][$bY] == '#') {
return false;
} else {
$x = $bX;
$y = $bY;
}
}
}
/**
* Pushes boxes in the specified direction if possible.
*
* @param array $map The grid representation with doubled columns.
* @param int $robotR Robot's current row.
* @param int $robotC Robot's current column.
* @param int $dr Row direction.
* @param int $dc Column direction.
* @return bool True if the boxes were pushed, false otherwise.
*/
private function pushBoxes(array &$map, int $robotR, int $robotC, int $dr, int $dc): bool
{
$queue = [[$robotR, $robotC]];
$seen = [];
$boxes = [];
// Locate connected box parts
while (!empty($queue)) {
[$r, $c] = array_shift($queue);
if (isset($seen["$r-$c"])) {
continue;
}
$seen["$r-$c"] = true;
if ($map[$r][$c] === '[' || $map[$r][$c] === ']') {
$boxes[] = [$r, $c];
if ($map[$r][$c] === '[' && isset($map[$r][$c + 1]) && $map[$r][$c + 1] === ']') {
$queue[] = [$r, $c + 1];
}
if ($map[$r][$c] === ']' && isset($map[$r][$c - 1]) && $map[$r][$c - 1] === '[') {
$queue[] = [$r, $c - 1];
}
}
if ($map[$r + $dr][$c + $dc] === '[' || $map[$r + $dr][$c + $dc] === ']') {
$queue[] = [$r + $dr, $c + $dc];
}
}
// Validate move
foreach ($boxes as [$r, $c]) {
$nextR = $r + $dr;
$nextC = $c + $dc;
if (!isset($map[$nextR][$nextC]) || $map[$nextR][$nextC] == '#') {
return false; // Cannot move the box
}
}
// Move boxes
foreach (array_reverse($boxes) as [$r, $c]) {
$nextR = $r + $dr;
$nextC = $c + $dc;
$map[$nextR][$nextC] = $map[$r][$c];
$map[$r][$c] = '.';
}
return true;
}
/**
* Solve part 1 of the challenge.
*
* @param array<string> $parts The grid lines.
* @return int
*/
public function solvePart1(array $parts): int
{
$grid = explode("\n", $parts[0]);
$movements = str_replace("\n", "", $parts[1]);
// Find robot
$robot = [];
foreach ($grid as $r => $row) {
for ($c = 0; $c < strlen($row); $c++) {
if ($row[$c] == '@') {
$robot = [$r, $c];
}
}
}
if (count($robot) !== 2) {
throw new \RuntimeException('Robot position is not defined.');
}
// Move Robot
for ($m = 0; $m < strlen($movements); $m++) {
$rX = $robot[0] + static::DIRECTIONS[$movements[$m]][0];
$rY = $robot[1] + static::DIRECTIONS[$movements[$m]][1];
if ($grid[$rX][$rY] == 'O' && !$this->moveBoxes($grid, $rX, $rY, $movements[$m])) {
continue;
}
if ($grid[$rX][$rY] !== '#') {
$grid[$robot[0]][$robot[1]] = '.';
$grid[$rX][$rY] = '@';
$robot = [$rX, $rY];
}
}
// Get GPS coordinates
$pt1 = 0;
foreach ($grid as $r => $row) {
if (!is_string($row)) {
throw new \UnexpectedValueException('Each row must be a string.');
}
for ($c = 0; $c < strlen($row); $c++) {
if ($row[$c] == 'O') {
$pt1 += 100 * (int)$r + $c;
}
}
}
return $pt1;
}
/**
* Solve part 2 of the challenge.
*
* @param array<string> $parts The grid lines.
* @return int
*/
public function solvePart2(array $parts): int
{
$grid = explode("\n", $parts[0]);
$movements = str_replace("\n", "", $parts[1]);
// Double map
$map = [];
foreach ($grid as $r => $row) {
for ($c = 0; $c < strlen($row); $c++) {
if ($row[$c] === 'O') {
$map[$r][$c * 2] = '[';
$map[$r][$c * 2 + 1] = ']';
} elseif ($row[$c] === '@') {
$map[$r][$c * 2] = '@';
$map[$r][$c * 2 + 1] = '.';
} else {
$map[$r][$c * 2] = $row[$c];
$map[$r][$c * 2 + 1] = $row[$c];
}
}
}
// Find robot
$robot = [];
foreach ($map as $r => $row) {
for ($c = 0; $c < count($row); $c++) {
if ($row[$c] == '@') {
$robot = [$r, $c];
break 2;
}
}
}
// Validate $robot
if (count($robot) !== 2) {
throw new \RuntimeException('Robot position is not defined.');
}
// Move Robot
foreach (str_split($movements) as $m) {
[$dr, $dc] = self::DIRECTIONS[$m];
$nextR = $robot[0] + $dr;
$nextC = $robot[1] + $dc;
if (
($map[$nextR][$nextC] === '[' || $map[$nextR][$nextC] === ']') &&
!$this->pushBoxes($map, $nextR, $nextC, $dr, $dc)
) {
continue;
}
if ($map[$nextR][$nextC] !== '#') {
$map[$robot[0]][$robot[1]] = '.';
$map[$nextR][$nextC] = '@';
$robot = [$nextR, $nextC];
}
}
// Get GPS coordinates
$pt2 = 0;
foreach ($map as $r => $row) {
for ($c = 0; $c < count($row); $c++) {
if ($row[$c] === '[') {
$pt2 += 100 * (int) $r + $c;
}
}
}
return (int)$pt2;
}
/**
* Main solution function to handle input and return results for both parts.
*
* @return array<string, int>
*/
public function solve(): array
{
$lines = explode("\n\n", $this->getInputString());
return [
"Part 1" => $this->solvePart1($lines),
"Part 2" => $this->solvePart2($lines)
];
}
}