diff --git a/2024/src/day13.scala b/2024/src/day13.scala new file mode 100644 index 000000000..fa6b2dbad --- /dev/null +++ b/2024/src/day13.scala @@ -0,0 +1,48 @@ +package day13 + +import locations.Directory.currentDir +import inputs.Input.loadFileSync + +@main def part1: Unit = + println(s"The solution is ${part1(loadInput())}") + +@main def part2: Unit = + println(s"The solution is ${part2(loadInput())}") + +def loadInput(): String = loadFileSync(s"$currentDir/../input/day13") + +case class Claw(ax: Long, ay: Long, bx: Long, by: Long, x: Long, y: Long): + def solve: Option[Long] = for + b <- (x * ay - y * ax) safeDiv (bx * ay - by * ax) + a <- (x - b * bx) safeDiv ax + yield a * 3 + b + +object Claw: + def parse(xs: Seq[String]): Option[Claw] = xs match + case Seq( + s"Button A: X+${L(ax)}, Y+${L(ay)}", + s"Button B: X+${L(bx)}, Y+${L(by)}", + s"Prize: X=${L(x)}, Y=${L(y)}", + ) => + Some(Claw(ax, ay, bx, by, x, y)) + case _ => None + +def parse(input: String): Seq[Claw] = + input.split("\n+").toSeq.grouped(3).flatMap(Claw.parse).toSeq + +extension (a: Long) + infix def safeDiv(b: Long): Option[Long] = + Option.when(b != 0 && a % b == 0)(a / b) + +object L: + def unapply(s: String): Option[Long] = s.toLongOption + +def part1(input: String): Long = + parse(input).flatMap(_.solve).sum + +def part2(input: String): Long = + val diff = 10_000_000_000_000L + parse(input) + .map(c => c.copy(x = c.x + diff, y = c.y + diff)) + .flatMap(_.solve) + .sum diff --git a/2024/src/inputs.scala b/2024/src/inputs.scala new file mode 100644 index 000000000..1d41e4738 --- /dev/null +++ b/2024/src/inputs.scala @@ -0,0 +1,9 @@ +package inputs + +import scala.util.Using +import scala.io.Source + +object Input: + + def loadFileSync(path: String): String = + Using.resource(Source.fromFile(path))(_.mkString) diff --git a/2024/src/locations.scala b/2024/src/locations.scala new file mode 100644 index 000000000..986d2a3bb --- /dev/null +++ b/2024/src/locations.scala @@ -0,0 +1,19 @@ +package locations + +import scala.quoted.* + +object Directory: + + /** The absolute path of the parent directory of the file that calls this method + * This is stable no matter which directory runs the program. + */ + inline def currentDir: String = ${ parentDirImpl } + + private def parentDirImpl(using Quotes): Expr[String] = + // position of the call to `currentDir` in the source code + val position = quotes.reflect.Position.ofMacroExpansion + // get the path of the file calling this macro + val srcFilePath = position.sourceFile.getJPath.get + // get the parent of the path, which is the directory containing the file + val parentDir = srcFilePath.getParent().toAbsolutePath + Expr(parentDir.toString) // convert the String to Expr[String]