-
Notifications
You must be signed in to change notification settings - Fork 0
/
Day24Test.scala
125 lines (101 loc) · 4.05 KB
/
Day24Test.scala
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
package org.lemon.advent.year2022
import org.lemon.advent._
import scala.collection.mutable
class Day24Test extends UnitTest {
type Coord = (Int, Int)
extension (coord: Coord)
def x = coord._1
def y = coord._2
def up = (coord.x, coord.y - 1)
def down = (coord.x, coord.y + 1)
def left = (coord.x - 1, coord.y)
def right = (coord.x + 1, coord.y)
def cardinals = Seq(coord.up, coord.down, coord.left, coord.right)
def manhattan(rhs: Coord) = (coord.x - rhs.x).abs + (coord.y - rhs.y).abs
def +(rhs: Coord) = (coord.x + rhs.x, coord.y + rhs.y)
case class Blizzard(at: Coord, direction: Coord)
case class Maze(xBounds: Range, yBounds: Range, blizzards: Seq[Set[Coord]], start: Coord, end: Coord):
def inBounds(coord: Coord) = coord match
case c if c == start || c == end => true
case c => xBounds.contains(c.x) && yBounds.contains(c.y)
def cycle(n: Int, bounds: Range): Int =
if n > bounds.max then bounds.min
else if n < bounds.min then bounds.max
else n
def cycle(coord: Coord, xBounds: Range, yBounds: Range): Coord = (cycle(coord.x, xBounds), cycle(coord.y, yBounds))
def move(blizzard: Blizzard, xBounds: Range, yBounds: Range) =
blizzard.copy(at = cycle(blizzard.at + blizzard.direction, xBounds, yBounds))
def parseBoard(in: Seq[String]) =
val start = (in.head.indexOf('.'), 0)
val end = (in.last.indexOf('.'), in.size - 1)
val yBounds = 1 to in.size - 2
val xBounds = 1 to in.head.size - 2
val initialBlizzards =
for
y <- in.indices
x <- in(y).indices
if in(y)(x) != '.'
if in(y)(x) != '#'
yield in(y)(x) match
case '>' => Blizzard(at = (x, y), direction = (1, 0))
case '<' => Blizzard(at = (x, y), direction = (-1, 0))
case '^' => Blizzard(at = (x, y), direction = (0, -1))
case 'v' => Blizzard(at = (x, y), direction = (0, 1))
def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
def lcm(a: Int, b: Int) = a * b / gcd(a, b)
def tick(blizzards: Seq[Blizzard]) = blizzards.map(move(_, xBounds, yBounds))
val allBlizzards = Iterator.iterate(initialBlizzards)(tick)
.take(lcm(xBounds.size, yBounds.size))
.map(_.map(_.at).toSet)
.toIndexedSeq
Maze(xBounds, yBounds, allBlizzards, start, end)
case class Step(at: Coord, time: Int)
def solve(maze: Maze, startingTime: Int = 0): Step =
given Ordering[Step] = Ordering[Int].on((step: Step) => step.at.manhattan(maze.end) + step.time).reverse
val queue = mutable.PriorityQueue[Step]()
val visited = mutable.Set.empty[Step]
queue += Step(at = maze.start, time = startingTime)
while !queue.isEmpty && queue.head.at != maze.end do
val step = queue.dequeue
val time = step.time + 1
val blizzards = maze.blizzards(time % maze.blizzards.size)
val neighbours = (step.at.cardinals :+ step.at).filter(maze.inBounds).map(Step(_, time))
queue ++= neighbours
.filterNot(n => blizzards(n.at))
.filterNot(visited)
.tapEach(visited.add)
queue.dequeue
def part1(in: Seq[String]) =
val maze = parseBoard(in)
solve(maze).time
def part2(in: Seq[String]) =
val maze = parseBoard(in)
val moveThoseDamnElves = solve(maze)
val goBackCauseTheyJerks = solve(maze.copy(start = maze.end, end = maze.start), moveThoseDamnElves.time)
val meetBackUpFfs = solve(maze, goBackCauseTheyJerks.time)
meetBackUpFfs.time
test("part 1 example") {
val in = """|#.######
|#>>.<^<#
|#.<..<<#
|#>v.><>#
|#<^v^^>#
|######.#""".stripMargin
part1(in.linesIterator.toSeq) shouldBe 18
}
test("part 1") {
part1(readLines(file(2022)(24))) shouldBe 247
}
test("part 2 example") {
val in = """|#.######
|#>>.<^<#
|#.<..<<#
|#>v.><>#
|#<^v^^>#
|######.#""".stripMargin
part2(in.linesIterator.toSeq) shouldBe 54
}
test("part 2") {
part2(readLines(file(2022)(24))) shouldBe 728
}
}