Skip to content

Commit de6187d

Browse files
committed
Alternate approach to day 22
1 parent 8c9020b commit de6187d

File tree

2 files changed

+96
-70
lines changed

2 files changed

+96
-70
lines changed
Lines changed: 92 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,121 @@
11
package AdventOfCode2022
22

33
object Day22:
4-
val (right, down, left, up) = (Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1))
5-
6-
type State = (Point, Point)
7-
8-
enum Tile:
9-
case Open, Solid, Wrap
10-
114
case class Point(x: Int, y: Int):
125
def +(other: Point): Point = Point(x + other.x, y + other.y)
136
def clockwise: Point = Point(-y, x)
147
def counterClockwise: Point = Point(y, -x)
8+
def score: Int = 1000 * (y + 1) + 4 * (x + 1)
9+
10+
case class Vec(x: Int, y: Int, z: Int):
11+
def *(k: Int): Vec = Vec(x * k, y * k, z * k)
12+
def +(b: Vec): Vec = Vec(x + b.x, y + b.y, z + b.z)
13+
def cross(b: Vec): Vec = Vec(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x)
14+
15+
case class Info(point: Point, i: Vec, j: Vec, k: Vec)
1516

16-
def parse(input: Seq[String]): (Map[Point, Tile], String) =
17+
def parseTiles(input: Seq[String]): Map[Point, Boolean] =
1718
val points = for
1819
(row, y) <- input.dropRight(2).zipWithIndex
1920
(cell, x) <- row.zipWithIndex
2021
if cell != ' '
21-
yield Point(x, y) -> (if cell == '.' then Tile.Open else Tile.Solid)
22-
(points.toMap.withDefaultValue(Tile.Wrap), input.last)
23-
24-
def follow(tiles: Map[Point, Tile], path: String, handleWrap: State => State): Int =
25-
val numbers = path.split("\\D+").map(_.toInt).toSeq
26-
val letters = path.split("\\d+").toSeq
27-
val moves = numbers.zip(letters)
28-
29-
val initial = (Point(50, 0), Point(1, 0))
30-
val (position, direction) = moves.foldLeft(initial) { case ((position, direction), (number, letter)) =>
31-
val nextDirection = letter match
32-
case "L" => direction.counterClockwise
33-
case "R" => direction.clockwise
34-
case _ => direction
35-
36-
(1 to number).foldLeft((position, nextDirection)) { case ((position, direction), _) =>
37-
val next = position + direction
38-
tiles(next) match
39-
case Tile.Open => (next, direction)
40-
case Tile.Solid => (position, direction)
41-
case Tile.Wrap =>
42-
val (wrapPosition, wrapDirection) = handleWrap(position, direction)
43-
if tiles(wrapPosition) == Tile.Open then (wrapPosition, wrapDirection) else (position, direction)
44-
}
45-
}
22+
yield Point(x, y) -> (cell == '.')
23+
points.toMap
4624

47-
1000 * (position.y + 1) + 4 * (position.x + 1) + Seq(right, down, left, up).indexOf(direction)
48-
end follow
25+
def parseMoves(input: Seq[String]): Seq[Char] =
26+
val tokens = input.last.replace("L", " L ").replace("R", " R ").split(" ").toSeq
27+
tokens.flatMap(token => if token.head.isDigit then "F" * token.toInt else token)
4928

5029
def part1(input: Seq[String]): Int =
51-
val (tiles, path) = parse(input)
30+
val tiles = parseTiles(input)
31+
val moves = parseMoves(input)
5232

5333
val minX = tiles.keys.groupMapReduce(_.y)(_.x)(_ min _)
5434
val maxX = tiles.keys.groupMapReduce(_.y)(_.x)(_ max _)
5535
val minY = tiles.keys.groupMapReduce(_.x)(_.y)(_ min _)
5636
val maxY = tiles.keys.groupMapReduce(_.x)(_.y)(_ max _)
5737

58-
def handleWrap(position: Point, direction: Point): State = direction match
59-
case `right` => position.copy(x = minX(position.y)) -> right
60-
case `left` => position.copy(x = maxX(position.y)) -> left
61-
case `down` => position.copy(y = minY(position.x)) -> down
62-
case `up` => position.copy(y = maxY(position.x)) -> up
38+
val facing = Seq(Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1))
39+
val Seq(right, down, left, up) = facing
40+
val topLeft = tiles.keys.filter(_.y == 0).minBy(_.x)
41+
42+
val (position, direction) = moves.foldLeft((topLeft, right)) { case ((position, direction), move) =>
43+
move match
44+
case 'L' => (position, direction.counterClockwise)
45+
case 'R' => (position, direction.clockwise)
46+
case _ =>
47+
val next = position + direction
48+
tiles.get(next) match
49+
case Some(true) => (next, direction)
50+
case Some(false) => (position, direction)
51+
case None =>
52+
val wrapPosition = direction match
53+
case `right` => position.copy(x = minX(position.y))
54+
case `left` => position.copy(x = maxX(position.y))
55+
case `down` => position.copy(y = minY(position.x))
56+
case `up` => position.copy(y = maxY(position.x))
57+
if tiles(wrapPosition) then (wrapPosition, direction) else (position, direction)
58+
}
6359

64-
follow(tiles, path, handleWrap)
60+
position.score + facing.indexOf(direction)
6561
end part1
6662

67-
def part2(input: Seq[String]): Int =
68-
// Cube faces:
69-
// BA
70-
// C
71-
// ED
72-
// F
73-
def handleWrap(position: Point, direction: Point): State =
74-
val (cubeX, cubeY) = (position.x / 50, position.y / 50)
75-
val (modX, modY) = (position.x % 50, position.y % 50)
76-
(cubeX, cubeY, direction) match
77-
case (2, 0, `up`) => Point(modX, 199) -> up // A to F
78-
case (2, 0, `down`) => Point(99, 50 + modX) -> left // A to C
79-
case (2, 0, `right`) => Point(99, 149 - modY) -> left // A to D
80-
case (1, 0, `up`) => Point(0, 150 + modX) -> right // B to F
81-
case (1, 0, `left`) => Point(0, 149 - modY) -> right // B to E
82-
case (1, 1, `left`) => Point(modY, 100) -> down // C to E
83-
case (1, 1, `right`) => Point(100 + modY, 49) -> up // C to A
84-
case (1, 2, `down`) => Point(49, 150 + modX) -> left // D to F
85-
case (1, 2, `right`) => Point(149, 49 - modY) -> left // D to A
86-
case (0, 2, `up`) => Point(50, 50 + modX) -> right // E to C
87-
case (0, 2, `left`) => Point(50, 49 - modY) -> right // E to B
88-
case (0, 3, `down`) => Point(100 + modX, 0) -> down // F to A
89-
case (0, 3, `left`) => Point(50 + modY, 0) -> down // F to B
90-
case (0, 3, `right`) => Point(50 + modY, 149) -> up // F to D
91-
92-
val (tiles, path) = parse(input)
93-
follow(tiles, path, handleWrap)
63+
def part2(input: Seq[String], block: Int): Int =
64+
val tiles = parseTiles(input)
65+
val moves = parseMoves(input)
66+
67+
val scaleIJ = block - 1
68+
val scaleK = block + 1
69+
val startingPosition = Vec(-scaleIJ, -scaleIJ, -scaleK)
70+
val startingDirection = Vec(2, 0, 0)
71+
72+
val topLeft = tiles.keys.filter(_.y == 0).minBy(_.x)
73+
val start = Info(topLeft, Vec(1, 0, 0), Vec(0, 1, 0), Vec(0, 0, 1))
74+
75+
val todo = collection.mutable.Queue(start)
76+
val visited = collection.mutable.Set(topLeft)
77+
val points = collection.mutable.Map[Vec, Info]()
78+
79+
while todo.nonEmpty do
80+
val Info(offset, i, j, k) = todo.dequeue()
81+
82+
for x <- 0 until block do
83+
for y <- 0 until block do
84+
val key = (i * (2 * x - scaleIJ)) + (j * (2 * y - scaleIJ)) + (k * -scaleK) // Scale by 2 to keep points integer
85+
points(key) = Info(offset + Point(x, y), i, j, k)
86+
87+
val neighbours = Seq(
88+
Info(offset + Point(-block, 0), j.cross(i), j, j.cross(k)), // Left
89+
Info(offset + Point(block, 0), i.cross(j), j, k.cross(j)), // Right
90+
Info(offset + Point(0, -block), i, j.cross(i), k.cross(i)), // Up
91+
Info(offset + Point(0, block), i, i.cross(j), i.cross(k))) // Down
92+
93+
neighbours.foreach { next =>
94+
if tiles.contains(next.point) && !visited.contains(next.point) then
95+
todo += next
96+
visited += next.point
97+
}
98+
end while
99+
100+
val (position, direction) = moves.foldLeft((startingPosition, startingDirection)) { case ((position, direction), move) =>
101+
move match
102+
case 'L' => (position, direction.cross(points(position).k))
103+
case 'R' => (position, points(position).k.cross(direction))
104+
case _ =>
105+
val next = position + direction
106+
if points.contains(next) then
107+
if tiles(points(next).point) then (next, direction) else (position, direction)
108+
else
109+
val wrapDirection = points(position).k * 2 // This is the fun part
110+
val wrapPosition = next + wrapDirection
111+
if tiles(points(wrapPosition).point) then (wrapPosition, wrapDirection) else (position, direction)
112+
}
113+
114+
val Info(point, i, j, k) = points(position)
115+
point.score + Seq(i * 2, j * 2, i * -2, j * -2).indexOf(direction)
94116
end part2
95117

96118
def main(args: Array[String]): Unit =
97119
val data = io.Source.fromResource("AdventOfCode2022/Day22.txt").getLines().toSeq
98120
println(part1(data))
99-
println(part2(data))
121+
println(part2(data, 50))

src/test/scala/AdventOfCode2022/Day22Suite.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ class Day22Suite extends AnyFunSuite:
2222
test("Part 1 should handle sample input correctly") {
2323
assert(Day22.part1(sample) == 6032)
2424
}
25+
26+
test("Part 2 should handle sample input correctly") {
27+
assert(Day22.part2(sample, 4) == 5031)
28+
}

0 commit comments

Comments
 (0)