package aoc.day17 import aoc._ import scala.collection.mutable val board = lines.map(Parser.parse(Parser.row, _).get).map(_.toArray).toArray val sumRows = board.map(_.scanLeft(0)(_ + _)) val sumCols = (0 until board.length) .scanLeft(new Array[Int](board(0).length)) { (last, row) => last.toIterator.zipWithIndex.map { (last, col) => last + board(row)(col) }.toArray } .toArray object Dir: private[Dir] val dx = Array(-1, 0, 1, 0) private[Dir] val dy = Array(0, 1, 0, -1) import Dir._ enum Dir: case Up, Right, Down, Left def apply(x: Int, y: Int) = val k = this.ordinal (x + dx(k), y + dy(k)) inline def times(n: Int)(x: Int, y: Int) = val k = this.ordinal val (nx, ny) = (x + n * dx(k), y + n * dy(k)) if inside(nx, ny) then val dist = this match case Up => sumCols(x)(y) - sumCols(nx)(y) case Right => sumRows(x)(ny + 1) - sumRows(x)(y + 1) case Down => sumCols(nx + 1)(y) - sumCols(x + 1)(y) case Left => sumRows(x)(y) - sumRows(x)(ny) Some(State(nx, ny, this), dist) else None object Parser extends CommonParser: val digit = """\d""".r ^^ { _.toInt } val row = rep1(digit) end Parser inline def inside(x: Int, y: Int) = x >= 0 && x < board.length && y >= 0 && y < board(0).length case class State(x: Int, y: Int, dir: Dir): inline def next(ndir: Dir)(using steps: Seq[Int]) = steps.toIterator .flatMap(ndir.times(_)(x, y)) inline def left(using Seq[Int]) = next(Dir.fromOrdinal((dir.ordinal + 3) % 4)) inline def right(using Seq[Int]) = next(Dir.fromOrdinal((dir.ordinal + 1) % 4)) def all(using Seq[Int]) = left ++ right object Walk: // private val visited = mutable.Map.empty[State, Int] private object visited: private inline def idx(x: Int, y: Int, dir: Dir) = x * board(0).length * 4 + y * 4 + dir.ordinal private val arr = Array.fill(board.length * board(0).length * 4)(1_000_000_000) inline def apply(s: State) = arr(idx(s.x, s.y, s.dir)) inline def +=(inline p: (State, Int)) = p match case (s, v) => arr(idx(s.x, s.y, s.dir)) = v private val q = mutable.PriorityQueue.empty[(Int, State)](using scala.math.Ordering.by((v, _) => -v)) def go()(using Seq[Int]): Option[Int] = Seq(Up, Down, Left, Right).foreach: dir => visited += (State(0, 0, dir) -> 0) q += ((0, State(0, 0, dir))) @scala.annotation.tailrec def loop(): Option[Int] = if q.isEmpty then None else val (d, s) = q.dequeue() if d != visited(s) then loop() else if s.x == board.length - 1 && s.y == board(0).length - 1 then Some(d) else for (ns, delta) <- s.all do val nd = d + delta val old = visited(ns) if old > nd then visited += (ns -> nd) q += ((nd, ns)) loop() var res = loop() res // part 1 def solve(using steps: Seq[Int]) = val res = Walk .go() .get println(res) def part1 = solve(using (1 to 3)) def part2 = solve(using (4 to 10)) @main def Day17(part: Int) = part match case 1 => part1 case 2 => part2