diff --git a/README.md b/README.md index 5872791..4eb9bf8 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ This repo contains my attempt at this year's Advent of Code (2023) I will try and do some problems (probably not all of them) in Scala ## Progress: -#### Stars: 18 / 50 -| Mon | Tue | Wed | Thu | Fri | Sat | Sun | -|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:| -| | | | | 1
:star::star: | 2
:star::star: | 3
:star::star: | -| 4
:star::star: | 5
:star::star: | 6
:star::star: | 7
:star::star: | 8
:star::star: | 9
:star::star: | 10
:star: | -| 11 | 12 | 13 | 14 | 15 | 16 | 17 | -| 18 | 19 | 20 | 21 | 22 | 23 | 24 | -| 25 | | | | | | | \ No newline at end of file +#### Stars: 20 / 50 +| Mon | Tue | Wed | Thu | Fri | Sat | Sun | +|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:------------------:| +| | | | | 1
:star::star: | 2
:star::star: | 3
:star::star: | +| 4
:star::star: | 5
:star::star: | 6
:star::star: | 7
:star::star: | 8
:star::star: | 9
:star::star: | 10
:star::star: | +| 11 | 12 | 13 | 14 | 15 | 16 | 17 | +| 18 | 19 | 20 | 21 | 22 | 23 | 24 | +| 25 | | | | | | | \ No newline at end of file diff --git a/src/day10/Painter.scala b/src/day10/Painter.scala new file mode 100644 index 0000000..00c7819 --- /dev/null +++ b/src/day10/Painter.scala @@ -0,0 +1,77 @@ +package day10 + +import scala.collection.mutable.ArrayBuffer + +class Painter(startX: Int, startY: Int, excludeX: Int, excludeY: Int) extends Walker(startX, startY, excludeX, excludeY) { + def walk(grid: Array[Array[Byte]], zones: Array[Array[Int]], path: ArrayBuffer[(Int, Int)]): Unit = { + val prevX: Int = lastX + val prevY: Int = lastY + val curX: Int = x + val curY: Int = y + val curTile: Byte = grid(y)(x) + super.walk(grid) + val newX: Int = x + val newY: Int = y + val dx: Int = newX - prevX + val dy: Int = newY - prevY + + // West-East + if (curTile == 5) { + // Going east + if (dx > 0) { + setZone(path, zones, curX, curY - 1, 1) + setZone(path, zones, curX, curY + 1, 2) + + // Going west + } else { + setZone(path, zones, curX, curY - 1, 2) + setZone(path, zones, curX, curY + 1, 1) + } + + // North-South + } else if (curTile == 10) { + // Going south + if (dy > 0) { + setZone(path, zones, curX+1, curY, 1) + setZone(path, zones, curX-1, curY, 2) + + // Going north + } else { + setZone(path, zones, curX+1, curY, 2) + setZone(path, zones, curX-1, curY, 1) + } + + // Corner + } else if (curTile != 15) { + val east: Boolean = (curTile & 1) != 0 + val south: Boolean = (curTile & 2) != 0 + + val toEast: Boolean = dx > 0 + val toSouth: Boolean = dy > 0 + + val offsetBottom: Boolean = east ^ toEast ^ toSouth + val zoneY: Boolean = !toSouth ^ east + + val offsetRight: Boolean = south ^ toEast ^ toSouth + val zoneX: Boolean = toEast ^ south + + val offsetY: Int = if (offsetBottom) 1 else -1 + val zoneYVal: Int = if (zoneY) 2 else 1 + setZone(path, zones, curX, curY+offsetY, zoneYVal) + + val offsetX: Int = if (offsetRight) 1 else -1 + val zoneXVal: Int = if (zoneX) 2 else 1 + setZone(path, zones, curX+offsetX, curY, zoneXVal) + } + } + + private def setZone(path: ArrayBuffer[(Int, Int)], zones: Array[Array[Int]], posX: Int, posY: Int, zone: Int): Unit = { + val height: Int = zones.length + val width: Int = if (height == 0) 0 else zones(0).length + if (0 <= posX && posX < width && 0 <= posY && posY < height) { + if (!path.contains((posX, posY))) { + zones(posY)(posX) = zone + } + } + } +} diff --git a/src/day10/Puzzle2.scala b/src/day10/Puzzle2.scala new file mode 100644 index 0000000..075a436 --- /dev/null +++ b/src/day10/Puzzle2.scala @@ -0,0 +1,147 @@ +package day10 + +import scala.collection.immutable.HashMap +import scala.collection.mutable.ArrayBuffer +import scala.io.{BufferedSource, Source} + +object Puzzle2 { + var grid: Array[Array[Byte]] = Array.empty + var zones: Array[Array[Int]] = Array.empty + + // bits: NWSE + val TILES: Map[Char, Byte] = HashMap( + '|' -> 10, + '-' -> 5, + 'L' -> 9, + 'J' -> 12, + '7' -> 6, + 'F' -> 3, + '.' -> 0, + 'S' -> 15 + ) + + var height: Int = 0 + var width: Int = 0 + var startX: Int = 0 + var startY: Int = 0 + + def loadInput(path: String): Unit = { + val source: BufferedSource = Source.fromFile(path) + val lines: Array[String] = source.getLines().toArray + + height = lines.length + width = if (height == 0) 0 else lines(0).length + grid = Array.ofDim(height, width) + zones = Array.ofDim(height, width) + + for ((line: String, y: Int) <- lines.zipWithIndex) { + for ((c: Char, x: Int) <- line.zipWithIndex) { + grid(y)(x) = TILES(c) + if (c == 'S') { + startX = x + startY = y + } + } + } + + source.close() + } + + def calculateArea(): Int = { + val path: ArrayBuffer[(Int, Int)] = new ArrayBuffer() + path.addOne((startX, startY)) + + val walker: Walker = new Walker(startX, startY) + + do { + walker.walk(grid) + path.addOne(walker.getPos()) + } while (walker.getX() != startX || walker.getY() != startY) + println(s"Found path (length = ${path.length})") + + val painter: Painter = new Painter(startX, startY, 2*startX-path(1)._1, 2*startY-path(1)._2) + do { + painter.walk(grid, zones, path) + } while (painter.getX() != startX || painter.getY() != startY) + println("Painted path neighbours") + + var newZones: Array[Array[Int]] = Array.ofDim(height, width) + var filled: Boolean = false + var exteriorZone: Int = 0 + + do { + filled = true + newZones = copyZones() + for (y: Int <- 0 until height) { + for (x: Int <- 0 until width) { + if (zones(y)(x) == 0 && !path.contains((x, y))) { + filled = false + fillTile(x, y, newZones) + } + } + } + zones = newZones + } while (!filled) + println("Filled zones") + + for (y: Int <- 0 until height) { + for (x: Int <- 0 until width) { + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + if (zones(y)(x) != 0) { + exteriorZone = zones(y)(x) + } + } + } + } + println(s"Found exterior zone: $exteriorZone") + + var area: Int = 0 + for (y: Int <- 0 until height) { + for (x: Int <- 0 until width) { + if (zones(y)(x) != exteriorZone && zones(y)(x) != 0) area += 1 + } + } + println(s"Calculated area: $area") + + return area + } + + def fillTile(x: Int, y: Int, newZones: Array[Array[Int]]): Unit = { + var zone: Int = 0 + + for ((dx: Int, dy: Int) <- Walker.OFFSETS) { + val x2: Int = x + dx + val y2: Int = y + dy + if (0 <= x2 && x2 < width && 0 <= y2 && y2 < height) { + if (zones(y2)(x2) != 0) { + zone = zones(y2)(x2) + } + } + } + + newZones(y)(x) = zone + } + + def copyZones(): Array[Array[Int]] = { + val result: Array[Array[Int]] = Array.ofDim(height, width) + for (y: Int <- 0 until height) { + for (x: Int <- 0 until width) { + result(y)(x) = zones(y)(x) + } + } + return result + } + + def solve(path: String): Int = { + loadInput(path) + + val solution: Int = calculateArea() + + return solution + } + + def main(args: Array[String]): Unit = { + val solution: Int = solve("./res/day10/input1.txt") + println(solution) + } +} diff --git a/src/day10/Walker.scala b/src/day10/Walker.scala index 8da9609..10d391e 100644 --- a/src/day10/Walker.scala +++ b/src/day10/Walker.scala @@ -1,15 +1,11 @@ package day10 class Walker { - private var x: Int = 0 - private var y: Int = 0 - private var lastX: Int = -1 - private var lastY: Int = -1 - private var distance: Int = 0 - - val OFFSETS: Array[(Int, Int)] = Array( - (1, 0), (0, 1), (-1, 0), (0, -1) - ) + protected var x: Int = 0 + protected var y: Int = 0 + protected var lastX: Int = -1 + protected var lastY: Int = -1 + protected var distance: Int = 0 def this(startX: Int, startY: Int) = { this() @@ -41,7 +37,7 @@ class Walker { private def getNextPos(grid: Array[Array[Byte]], width: Int, height: Int): (Int, Int) = { val curTile: Byte = grid(y)(x) - for (((dx: Int, dy: Int), i: Int) <- OFFSETS.zipWithIndex) { + for (((dx: Int, dy: Int), i: Int) <- Walker.OFFSETS.zipWithIndex) { val x2: Int = x + dx val y2: Int = y + dy if (x2 != lastX || y2 != lastY) { @@ -58,3 +54,9 @@ class Walker { throw new Exception("Dead-end path") } } + +object Walker { + val OFFSETS: Array[(Int, Int)] = Array( + (1, 0), (0, 1), (-1, 0), (0, -1) + ) +} \ No newline at end of file diff --git a/tests/day10/Puzzle2Test.scala b/tests/day10/Puzzle2Test.scala new file mode 100644 index 0000000..bfeb306 --- /dev/null +++ b/tests/day10/Puzzle2Test.scala @@ -0,0 +1,24 @@ +package day10 + +import org.scalatest.funsuite.AnyFunSuite + +class Puzzle2Test extends AnyFunSuite { + test("Puzzle2.solve 1") { + assert(Puzzle2.solve("tests_res/day10/input1.txt") == 1) + } + test("Puzzle2.solve 2") { + assert(Puzzle2.solve("tests_res/day10/input2.txt") == 1) + } + test("Puzzle2.solve 3") { + assert(Puzzle2.solve("tests_res/day10/input3.txt") == 4) + } + test("Puzzle2.solve 4") { + assert(Puzzle2.solve("tests_res/day10/input4.txt") == 4) + } + test("Puzzle2.solve 5") { + assert(Puzzle2.solve("tests_res/day10/input5.txt") == 8) + } + test("Puzzle2.solve 6") { + assert(Puzzle2.solve("tests_res/day10/input6.txt") == 10) + } +} diff --git a/tests_res/day10/input3.txt b/tests_res/day10/input3.txt new file mode 100644 index 0000000..6933a28 --- /dev/null +++ b/tests_res/day10/input3.txt @@ -0,0 +1,9 @@ +........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +........... \ No newline at end of file diff --git a/tests_res/day10/input4.txt b/tests_res/day10/input4.txt new file mode 100644 index 0000000..c9f3dd0 --- /dev/null +++ b/tests_res/day10/input4.txt @@ -0,0 +1,9 @@ +.......... +.S------7. +.|F----7|. +.||....||. +.||....||. +.|L-7F-J|. +.|..||..|. +.L--JL--J. +.......... \ No newline at end of file diff --git a/tests_res/day10/input5.txt b/tests_res/day10/input5.txt new file mode 100644 index 0000000..2e5dcbb --- /dev/null +++ b/tests_res/day10/input5.txt @@ -0,0 +1,10 @@ +.F----7F7F7F7F-7.... +.|F--7||||||||FJ.... +.||.FJ||||||||L7.... +FJL7L7LJLJ||LJ.L-7.. +L--J.L7...LJS7F-7L7. +....F-J..F7FJ|L7L7L7 +....L7.F7||L7|.L7L7| +.....|FJLJ|FJ|F7|.LJ +....FJL-7.||.||||... +....L---J.LJ.LJLJ... \ No newline at end of file diff --git a/tests_res/day10/input6.txt b/tests_res/day10/input6.txt new file mode 100644 index 0000000..fbc0300 --- /dev/null +++ b/tests_res/day10/input6.txt @@ -0,0 +1,10 @@ +FF7FSF7F7F7F7F7F---7 +L|LJ||||||||||||F--J +FL-7LJLJ||||||LJL-77 +F--JF--7||LJLJ7F7FJ- +L---JF-JLJ.||-FJLJJ7 +|F|F-JF---7F7-L7L|7| +|FFJF7L7F-JF7|JL---7 +7-L-JL7||F7|L7F-7F7| +L.L7LFJ|||||FJL7||LJ +L7JLJL-JLJLJL--JLJ.L \ No newline at end of file