|
1 | 1 | package AdventOfCode2022
|
2 | 2 |
|
3 | 3 | object Day22:
|
4 |
| - val facing = Seq(Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)) |
| 4 | + val (right, down, left, up) = (Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)) |
| 5 | + |
| 6 | + enum Tile: |
| 7 | + case Open, Solid, Wrap |
| 8 | + import Tile._ |
5 | 9 |
|
6 | 10 | case class Point(x: Int, y: Int):
|
7 | 11 | def +(other: Point): Point = Point(x + other.x, y + other.y)
|
8 | 12 | def clockwise: Point = Point(-y, x)
|
9 | 13 | def counterClockwise: Point = Point(y, -x)
|
10 | 14 |
|
11 |
| - case class State(position: Point, direction: Point): |
12 |
| - def password: Int = 1000 * (position.y + 1) + 4 * (position.x + 1) + facing.indexOf(direction) |
13 |
| - |
14 |
| - def parse(input: Seq[String]): (Seq[Int], Seq[Int], Seq[Int], Seq[Int], Set[Point], Seq[(Int, String)]) = |
15 |
| - val prefix = input.dropRight(2) |
| 15 | + case class State(position: Point, direction: Point) |
16 | 16 |
|
| 17 | + def parse(input: Seq[String]): Map[Point, Tile] = |
17 | 18 | val points = for
|
18 |
| - (row, y) <- prefix.zipWithIndex |
19 |
| - (col, x) <- row.zipWithIndex |
20 |
| - if col == '#' |
21 |
| - yield Point(x, y) |
22 |
| - |
23 |
| - val maxSize = prefix.map(_.length).max |
24 |
| - val padded = prefix.map(_.padTo(maxSize, ' ')) |
25 |
| - val minX = padded.map(_.segmentLength(_ == ' ')) |
26 |
| - val maxX = padded.map(row => row.size - row.reverse.segmentLength(_ == ' ')) |
27 |
| - |
28 |
| - val tranposed = padded.transpose |
29 |
| - val minY = tranposed.map(_.segmentLength(_ == ' ')) |
30 |
| - val maxY = tranposed.map(col => col.size - col.reverse.segmentLength(_ == ' ')) |
31 |
| - |
32 |
| - val numbers = input.last.split("\\D+").map(_.toInt).toSeq |
33 |
| - val letters = input.last.split("\\d+").tail.toSeq |
34 |
| - val moves = numbers.zipAll(letters, 0, "X") |
35 |
| - |
36 |
| - (minX, maxX, minY, maxY, points.toSet, moves) |
37 |
| - end parse |
38 |
| - |
39 |
| - def part1(input: Seq[String]): Int = |
40 |
| - val (minX, maxX, minY, maxY, points, moves) = parse(input) |
41 |
| - val initial = State(Point(minX.head, 0), Point(1, 0)) |
42 |
| - |
43 |
| - val result = moves.foldLeft(initial) { case (State(position, direction), (number, letter)) => |
44 |
| - val nextPosition = (1 to number).foldLeft(position) { (position, _) => |
45 |
| - val next = position + direction |
46 |
| - val candidate = direction match |
47 |
| - case Point(-1, 0) => if next.x >= minX(next.y) then next else next.copy(x = maxX(next.y) - 1) |
48 |
| - case Point(1, 0) => if next.x < maxX(next.y) then next else next.copy(x = minX(next.y)) |
49 |
| - case Point(0, -1) => if next.y >= minY(next.x) then next else next.copy(y = maxY(next.x) - 1) |
50 |
| - case Point(0, 1) => if next.y < maxY(next.x) then next else next.copy(y = minY(next.x)) |
51 |
| - if points.contains(candidate) then position else candidate |
52 |
| - } |
| 19 | + (row, y) <- input.dropRight(2).zipWithIndex |
| 20 | + (cell, x) <- row.zipWithIndex |
| 21 | + if cell != ' ' |
| 22 | + yield Point(x, y) -> (if cell == '.' then Open else Solid) |
| 23 | + points.toMap.withDefaultValue(Wrap) |
| 24 | + |
| 25 | + def follow(points: Map[Point, Tile], path: String, handleWrap: State => State): Int = |
| 26 | + val numbers = path.split("\\D+").map(_.toInt).toSeq |
| 27 | + val letters = path.split("\\d+").toSeq |
| 28 | + val moves = numbers.zip(letters) |
| 29 | + |
| 30 | + val initial = State(Point(50, 0), Point(1, 0)) |
| 31 | + val result = moves.foldLeft(initial) { case (state, (number, letter)) => |
53 | 32 | val nextDirection = letter match
|
54 |
| - case "L" => direction.counterClockwise |
55 |
| - case "R" => direction.clockwise |
56 |
| - case _ => direction |
57 |
| - State(nextPosition, nextDirection) |
58 |
| - } |
59 |
| - |
60 |
| - result.password |
61 |
| - end part1 |
| 33 | + case "L" => state.direction.counterClockwise |
| 34 | + case "R" => state.direction.clockwise |
| 35 | + case _ => state.direction |
62 | 36 |
|
63 |
| - def part2(input: Seq[String]): Int = |
64 |
| - val (minX, _, _, _, points, moves) = parse(input) |
65 |
| - val initial = State(Point(minX.head, 0), Point(1, 0)) |
66 |
| - |
67 |
| - val result = moves.foldLeft(initial) { case (state, (number, letter)) => |
68 |
| - val nextState = (1 to number).foldLeft(state) { case (state, _) => |
| 37 | + (1 to number).foldLeft(State(state.position, nextDirection)) { (state, _) => |
69 | 38 | val State(position, direction) = state
|
70 |
| - val cubeX = position.x / 50 |
71 |
| - val cubeY = position.y / 50 |
72 |
| - val modX = position.x % 50 |
73 |
| - val modY = position.y % 50 |
74 | 39 | val next = position + direction
|
75 |
| - val nextX = if next.x >= 0 then next.x / 50 else next.x |
76 |
| - val nextY = if next.y >= 0 then next.y / 50 else next.y |
77 |
| - |
78 |
| - val (candidatePosition, candidateDirection) = |
79 |
| - if cubeX == nextX && cubeY == nextY then |
80 |
| - (next, direction) |
81 |
| - else |
82 |
| - (cubeX, cubeY, nextX, nextY) match |
83 |
| - // A to F |
84 |
| - case (1, 0, 1, -1) => Point(0, 150 + modX) -> Point(1, 0) |
85 |
| - // A to C |
86 |
| - case (1, 0, 1, 1) => next -> direction |
87 |
| - // A to E |
88 |
| - case (1, 0, 0, 0) => Point(0, 149 - modY) -> Point(1, 0) |
89 |
| - // A to B |
90 |
| - case (1, 0, 2, 0) => next -> direction |
91 |
| - |
92 |
| - // B to F |
93 |
| - case (2, 0, 2, -1) => Point(0 + modX, 199) -> direction |
94 |
| - // B to C |
95 |
| - case (2, 0, 2, 1) => Point(99, 50 + modX) -> Point(-1, 0) |
96 |
| - // B to A |
97 |
| - case (2, 0, 1, 0) => next -> direction |
98 |
| - // B to D |
99 |
| - case (2, 0, 3, 0) => Point(99, 149 - modY) -> Point(-1, 0) |
100 |
| - |
101 |
| - // C to A |
102 |
| - case (1, 1, 1, 0) => next -> direction |
103 |
| - // C to D |
104 |
| - case (1, 1, 1, 2) => next -> direction |
105 |
| - // C to E |
106 |
| - case (1, 1, 0, 1) => Point(modY, 100) -> Point(0, 1) |
107 |
| - // C to B |
108 |
| - case (1, 1, 2, 1) => Point(100 + modY, 49) -> Point(0, -1) |
109 |
| - |
110 |
| - // D to C |
111 |
| - case (1, 2, 1, 1) => next -> direction |
112 |
| - // D to F |
113 |
| - case (1, 2, 1, 3) => Point(49, 150 + modX) -> Point(-1, 0) |
114 |
| - // D to E |
115 |
| - case (1, 2, 0, 2) => next -> direction |
116 |
| - // D to B |
117 |
| - case (1, 2, 2, 2) => Point(149, 49 - modY) -> Point(-1, 0) |
118 |
| - |
119 |
| - // E to C |
120 |
| - case (0, 2, 0, 1) => Point(50, 50 + modX) -> Point(1, 0) |
121 |
| - // E to F |
122 |
| - case (0, 2, 0, 3) => next -> direction |
123 |
| - // E to A |
124 |
| - case (0, 2, -1, 2) => Point(50, 49 - modY) -> Point(1, 0) |
125 |
| - // E to D |
126 |
| - case (0, 2, 1, 2) => next -> direction |
127 |
| - |
128 |
| - // F to E |
129 |
| - case (0, 3, 0, 2) => next -> direction |
130 |
| - // F to B |
131 |
| - case (0, 3, 0, 4) => Point(100 + modX, 0) -> direction |
132 |
| - // F to A |
133 |
| - case (0, 3, -1, 3) => Point(50 + modY, 0) -> Point(0, 1) |
134 |
| - // F to D |
135 |
| - case (0, 3, 1, 3) => Point(50 + modY, 149) -> Point(0, -1) |
136 |
| - end match |
137 |
| - end if |
138 |
| - |
139 |
| - if points.contains(candidatePosition) then state else State(candidatePosition, candidateDirection) |
| 40 | + points(next) match |
| 41 | + case Open => State(next, direction) |
| 42 | + case Solid => state |
| 43 | + case Wrap => handleWrap(state) |
140 | 44 | }
|
| 45 | + } |
141 | 46 |
|
142 |
| - val nextDirection = letter match |
143 |
| - case "L" => nextState.direction.counterClockwise |
144 |
| - case "R" => nextState.direction.clockwise |
145 |
| - case _ => nextState.direction |
| 47 | + val facing = Seq(right, down, left, up) |
| 48 | + 1000 * (result.position.y + 1) + 4 * (result.position.x + 1) + facing.indexOf(result.direction) |
| 49 | + end follow |
146 | 50 |
|
147 |
| - State(nextState.position, nextDirection) |
148 |
| - } |
| 51 | + def part1(input: Seq[String]): Int = |
| 52 | + val tiles = parse(input) |
| 53 | + |
| 54 | + val valid = tiles.filterNot((k, v) => v == Wrap).keySet |
| 55 | + val minX = valid.groupMapReduce(_.y)(_.x)(_ min _) |
| 56 | + val maxX = valid.groupMapReduce(_.y)(_.x)(_ max _) |
| 57 | + val minY = valid.groupMapReduce(_.x)(_.y)(_ min _) |
| 58 | + val maxY = valid.groupMapReduce(_.x)(_.y)(_ max _) |
| 59 | + |
| 60 | + def handleWrap(state: State): State = |
| 61 | + val State(position, direction) = state |
| 62 | + val nextPosition = direction match |
| 63 | + case `right` => Point(minX(position.y), position.y) |
| 64 | + case `left` => Point(maxX(position.y), position.y) |
| 65 | + case `down` => Point(position.x, minY(position.x)) |
| 66 | + case `up` => Point(position.x, maxY(position.x)) |
| 67 | + if tiles(nextPosition) == Open then State(nextPosition, direction) else state |
| 68 | + |
| 69 | + follow(tiles, input.last, handleWrap) |
| 70 | + end part1 |
149 | 71 |
|
150 |
| - result.password |
| 72 | + def part2(input: Seq[String]): Int = |
| 73 | + val tiles = parse(input) |
| 74 | + |
| 75 | + def handleWrap(state: State): State = |
| 76 | + val State(position, direction) = state |
| 77 | + val (cubeX, cubeY) = (position.x / 50, position.y / 50) |
| 78 | + val (modX, modY) = (position.x % 50, position.y % 50) |
| 79 | + |
| 80 | + // Cube faces: |
| 81 | + // BA |
| 82 | + // C |
| 83 | + // ED |
| 84 | + // F |
| 85 | + val (nextPosition, nextDirection) = (cubeX, cubeY, direction) match |
| 86 | + case (2, 0, `up`) => Point(modX, 199) -> up // A to F |
| 87 | + case (2, 0, `down`) => Point(99, 50 + modX) -> left // A to C |
| 88 | + case (2, 0, `right`) => Point(99, 149 - modY) -> left // A to D |
| 89 | + case (1, 0, `up`) => Point(0, 150 + modX) -> right // B to F |
| 90 | + case (1, 0, `left`) => Point(0, 149 - modY) -> right // B to E |
| 91 | + case (1, 1, `left`) => Point(modY, 100) -> down // C to E |
| 92 | + case (1, 1, `right`) => Point(100 + modY, 49) -> up // C to A |
| 93 | + case (1, 2, `down`) => Point(49, 150 + modX) -> left // D to F |
| 94 | + case (1, 2, `right`) => Point(149, 49 - modY) -> left // D to A |
| 95 | + case (0, 2, `up`) => Point(50, 50 + modX) -> right // E to C |
| 96 | + case (0, 2, `left`) => Point(50, 49 - modY) -> right // E to B |
| 97 | + case (0, 3, `down`) => Point(100 + modX, 0) -> down // F to A |
| 98 | + case (0, 3, `left`) => Point(50 + modY, 0) -> down // F to B |
| 99 | + case (0, 3, `right`) => Point(50 + modY, 149) -> up // F to D |
| 100 | + |
| 101 | + if tiles(nextPosition) == Open then State(nextPosition, nextDirection) else state |
| 102 | + end handleWrap |
| 103 | + |
| 104 | + follow(tiles, input.last, handleWrap) |
151 | 105 | end part2
|
152 | 106 |
|
153 | 107 | def main(args: Array[String]): Unit =
|
|
0 commit comments