Compare commits
	
		
			5 Commits
		
	
	
		
			802404ac1f
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2c429c5bbb | |||
| 56b7f06e77 | |||
| abeeee237c | |||
| 78b3d77a29 | |||
| e6ef68d818 | 
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								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 | I will try and do some problems (probably not all of them) in Scala | ||||||
|  |  | ||||||
| ## Progress: | ## Progress: | ||||||
| #### Stars: 26 / 50 | #### Stars: 30 / 50 | ||||||
| |        Mon         |        Tue        |        Wed         |        Thu         |        Fri        |        Sat        |        Sun         | | |        Mon         |        Tue        |        Wed         |        Thu         |        Fri         |        Sat         |        Sun         | | ||||||
| |:------------------:|:-----------------:|:------------------:|:------------------:|:-----------------:|:-----------------:|:------------------:| | |:------------------:|:-----------------:|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:| | ||||||
| |                    |                   |                    |                    | 1<br>:star::star: | 2<br>:star::star: | 3<br>:star::star:  | | |                    |                   |                    |                    | 1<br>:star::star:  | 2<br>:star::star:  | 3<br>:star::star:  | | ||||||
| | 4<br>:star::star:  | 5<br>:star::star: | 6<br>:star::star:  | 7<br>:star::star:  | 8<br>:star::star: | 9<br>:star::star: | 10<br>:star::star: | | | 4<br>:star::star:  | 5<br>:star::star: | 6<br>:star::star:  | 7<br>:star::star:  | 8<br>:star::star:  | 9<br>:star::star:  | 10<br>:star::star: | | ||||||
| | 11<br>:star::star: |        12         | 13<br>:star::star: | 14<br>:star::star: |        15         |        16         |         17         | | | 11<br>:star::star: |        12         | 13<br>:star::star: | 14<br>:star::star: | 15<br>:star::star: | 16<br>:star::star: |         17         | | ||||||
| |         18         |        19         |         20         |         21         |        22         |        23         |         24         | | |         18         |        19         |         20         |         21         |         22         |         23         |         24         | | ||||||
| |         25         |                   |                    |                    |                   |                   |                    | | |         25         |                   |                    |                    |                    |                    |                    | | ||||||
							
								
								
									
										33
									
								
								src/day15/Box.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/day15/Box.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | package day15 | ||||||
|  |  | ||||||
|  | import scala.collection.mutable.ArrayBuffer | ||||||
|  |  | ||||||
|  | class Box(val id: Int) { | ||||||
|  |   var lenses: ArrayBuffer[Lens] = new ArrayBuffer() | ||||||
|  |   var lensesLabels: ArrayBuffer[String] = new ArrayBuffer() | ||||||
|  |  | ||||||
|  |   def addLens(lens: Lens): Unit = { | ||||||
|  |     if (lensesLabels.contains(lens.label)) { | ||||||
|  |       val i: Int = lensesLabels.indexOf(lens.label) | ||||||
|  |       lenses(i) = lens | ||||||
|  |     } else { | ||||||
|  |       lenses.addOne(lens) | ||||||
|  |       lensesLabels.addOne(lens.label) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def removeLens(label: String): Unit = { | ||||||
|  |     if (lensesLabels.contains(label)) { | ||||||
|  |       val i: Int = lensesLabels.indexOf(label) | ||||||
|  |       lenses.remove(i) | ||||||
|  |       lensesLabels.remove(i) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   def getFocusingPower(): Long = { | ||||||
|  |     var power: Long = 0 | ||||||
|  |     for ((lens: Lens, i: Int) <- lenses.zipWithIndex) { | ||||||
|  |       power += (i+1) * lens.focalLength | ||||||
|  |     } | ||||||
|  |     return power | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								src/day15/Lens.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/day15/Lens.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | package day15 | ||||||
|  |  | ||||||
|  | class Lens(val label: String, val focalLength: Int) { | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								src/day15/Puzzle2.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/day15/Puzzle2.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | package day15 | ||||||
|  |  | ||||||
|  | import scala.io.{BufferedSource, Source} | ||||||
|  | import scala.util.matching.Regex | ||||||
|  | import scala.util.matching.Regex.Match | ||||||
|  |  | ||||||
|  | object Puzzle2 { | ||||||
|  |   var steps: Array[String] = Array.empty | ||||||
|  |   val boxes: Array[Box] = new Array(256) | ||||||
|  |   val regex: Regex = new Regex("([a-z]+)(-|=\\d)") | ||||||
|  |   def loadInput(path: String): Unit = { | ||||||
|  |     val source: BufferedSource = Source.fromFile(path) | ||||||
|  |     val line: String = source.getLines().mkString | ||||||
|  |  | ||||||
|  |     for (i: Int <- 0 until 256) { | ||||||
|  |       boxes(i) = new Box(i) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     steps = line.split(",") | ||||||
|  |  | ||||||
|  |     source.close() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def hash(str: String): Int = { | ||||||
|  |     var value: Int = 0 | ||||||
|  |     for (c: Char <- str) { | ||||||
|  |       value += c | ||||||
|  |       value *= 17 | ||||||
|  |       value %= 256 | ||||||
|  |     } | ||||||
|  |     return value | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def getFocusingPower(): Long = { | ||||||
|  |     var power: Long = 0 | ||||||
|  |     for (box: Box <- boxes) { | ||||||
|  |       power += (box.id+1) * box.getFocusingPower() | ||||||
|  |     } | ||||||
|  |     return power | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def placeLenses(): Unit = { | ||||||
|  |     for (step: String <- steps) { | ||||||
|  |       val m: Match = regex.findFirstMatchIn(step).get | ||||||
|  |       val label: String = m.group(1) | ||||||
|  |       val labelHash: Int = hash(label) | ||||||
|  |       val action: Char = m.group(2)(0) | ||||||
|  |       val box: Box = boxes(labelHash) | ||||||
|  |       if (action == '=') { | ||||||
|  |         val lens: Lens = new Lens(label, m.group(2)(1) - '0') | ||||||
|  |         box.addLens(lens) | ||||||
|  |       } else { | ||||||
|  |         box.removeLens(label) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def solve(path: String): Long = { | ||||||
|  |     loadInput(path) | ||||||
|  |     placeLenses() | ||||||
|  |  | ||||||
|  |     val solution: Long = getFocusingPower() | ||||||
|  |  | ||||||
|  |     return solution | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def main(args: Array[String]): Unit = { | ||||||
|  |     val solution: Long = solve("./res/day15/input1.txt") | ||||||
|  |     println(solution) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/day16/Mirrors.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/day16/Mirrors.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | package day16 | ||||||
|  |  | ||||||
|  | object Mirrors extends Enumeration { | ||||||
|  |   type Mirror = Value | ||||||
|  |   val NONE = Value(".") | ||||||
|  |   val DIAGONAL_TL_BR = Value("\\") | ||||||
|  |   val DIAGONAL_BL_TR = Value("/") | ||||||
|  |   val SPLITTER_HOR = Value("-") | ||||||
|  |   val SPLITTER_VER = Value("|") | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								src/day16/Puzzle1.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/day16/Puzzle1.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | package day16 | ||||||
|  |  | ||||||
|  | import day16.Mirrors.{DIAGONAL_BL_TR, DIAGONAL_TL_BR, Mirror, NONE, SPLITTER_HOR, SPLITTER_VER} | ||||||
|  |  | ||||||
|  | import scala.collection.mutable.ArrayBuffer | ||||||
|  | import scala.io.{BufferedSource, Source} | ||||||
|  |  | ||||||
|  | object Puzzle1 { | ||||||
|  |   var mirrors: Array[Array[Mirror]] = Array.empty | ||||||
|  |   var energized: Array[Array[Boolean]] = Array.empty | ||||||
|  |   var width: Int = 0 | ||||||
|  |   var height: Int = 0 | ||||||
|  |   val OFFSETS: Array[(Int, Int)] = Array( | ||||||
|  |     (1, 0), (0, 1), (-1, 0), (0, -1) | ||||||
|  |   ) | ||||||
|  |   val path: ArrayBuffer[(Int, Int, Int)] = new ArrayBuffer() | ||||||
|  |  | ||||||
|  |   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 | ||||||
|  |     mirrors = Array.ofDim(height, width) | ||||||
|  |     energized = Array.ofDim(height, width) | ||||||
|  |  | ||||||
|  |     for ((line: String, y: Int) <- lines.zipWithIndex) { | ||||||
|  |       for ((c: Char, x: Int) <- line.zipWithIndex) { | ||||||
|  |         val mirror: Mirror = Mirrors.withName(""+c) | ||||||
|  |         mirrors(y)(x) = mirror | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     source.close() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def shootBeam(startX: Int, startY: Int, startDir: Int): Unit = { | ||||||
|  |     if (startX < 0 || startX >= width || startY < 0 || startY >= height) return | ||||||
|  |     var x: Int = startX | ||||||
|  |     var y: Int = startY | ||||||
|  |     var dir: Int = startDir | ||||||
|  |     var continue: Boolean = true | ||||||
|  |     do { | ||||||
|  |       if (path.contains((x, y, dir))) return | ||||||
|  |       path.addOne((x, y, dir)) | ||||||
|  |  | ||||||
|  |       energized(y)(x) = true | ||||||
|  |  | ||||||
|  |       mirrors(y)(x) match { | ||||||
|  |         case NONE => {} | ||||||
|  |         case DIAGONAL_TL_BR => { | ||||||
|  |           dir = Math.floorDiv(dir, 2)*2 + 1 - (dir % 2) | ||||||
|  |         } | ||||||
|  |         case DIAGONAL_BL_TR => { | ||||||
|  |           dir = 3 - dir | ||||||
|  |         } | ||||||
|  |         case SPLITTER_HOR => { | ||||||
|  |           if (dir % 2 == 1) { | ||||||
|  |             continue = false | ||||||
|  |             shootBeam(x+1, y, 0) | ||||||
|  |             shootBeam(x-1, y, 2) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         case SPLITTER_VER => { | ||||||
|  |           if (dir % 2 == 0) { | ||||||
|  |             continue = false | ||||||
|  |             shootBeam(x, y+1, 1) | ||||||
|  |             shootBeam(x, y-1, 3) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (continue) { | ||||||
|  |         val offset: (Int, Int) = OFFSETS(dir) | ||||||
|  |         x += offset._1 | ||||||
|  |         y += offset._2 | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (x < 0 || x >= width) continue = false | ||||||
|  |       if (y < 0 || y >= height) continue = false | ||||||
|  |     } while (continue) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def solve(path: String): Int = { | ||||||
|  |     loadInput(path) | ||||||
|  |     shootBeam(0, 0, 0) | ||||||
|  |  | ||||||
|  |     var solution: Int = 0 | ||||||
|  |  | ||||||
|  |     for (y: Int <- 0 until height) { | ||||||
|  |       for (x: Int <- 0 until width) { | ||||||
|  |         if (energized(y)(x)) solution += 1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return solution | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def main(args: Array[String]): Unit = { | ||||||
|  |     val solution: Int = solve("./res/day16/input1.txt") | ||||||
|  |     println(solution) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										136
									
								
								src/day16/Puzzle2.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/day16/Puzzle2.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | package day16 | ||||||
|  |  | ||||||
|  | import day16.Mirrors.{DIAGONAL_BL_TR, DIAGONAL_TL_BR, Mirror, NONE, SPLITTER_HOR, SPLITTER_VER} | ||||||
|  |  | ||||||
|  | import scala.collection.mutable.ArrayBuffer | ||||||
|  | import scala.io.{BufferedSource, Source} | ||||||
|  |  | ||||||
|  | object Puzzle2 { | ||||||
|  |   var mirrors: Array[Array[Mirror]] = Array.empty | ||||||
|  |   var energized: Array[Array[Boolean]] = Array.empty | ||||||
|  |   var width: Int = 0 | ||||||
|  |   var height: Int = 0 | ||||||
|  |   val OFFSETS: Array[(Int, Int)] = Array( | ||||||
|  |     (1, 0), (0, 1), (-1, 0), (0, -1) | ||||||
|  |   ) | ||||||
|  |   var path: ArrayBuffer[(Int, Int, Int)] = new ArrayBuffer() | ||||||
|  |  | ||||||
|  |   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 | ||||||
|  |     mirrors = Array.ofDim(height, width) | ||||||
|  |  | ||||||
|  |     for ((line: String, y: Int) <- lines.zipWithIndex) { | ||||||
|  |       for ((c: Char, x: Int) <- line.zipWithIndex) { | ||||||
|  |         val mirror: Mirror = Mirrors.withName(""+c) | ||||||
|  |         mirrors(y)(x) = mirror | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     source.close() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def shootBeam(startX: Int, startY: Int, startDir: Int): Unit = { | ||||||
|  |     if (startX < 0 || startX >= width || startY < 0 || startY >= height) return | ||||||
|  |     var x: Int = startX | ||||||
|  |     var y: Int = startY | ||||||
|  |     var dir: Int = startDir | ||||||
|  |     var continue: Boolean = true | ||||||
|  |     do { | ||||||
|  |       if (path.contains((x, y, dir))) return | ||||||
|  |       path.addOne((x, y, dir)) | ||||||
|  |  | ||||||
|  |       energized(y)(x) = true | ||||||
|  |  | ||||||
|  |       mirrors(y)(x) match { | ||||||
|  |         case NONE => {} | ||||||
|  |         case DIAGONAL_TL_BR => { | ||||||
|  |           dir = Math.floorDiv(dir, 2)*2 + 1 - (dir % 2) | ||||||
|  |         } | ||||||
|  |         case DIAGONAL_BL_TR => { | ||||||
|  |           dir = 3 - dir | ||||||
|  |         } | ||||||
|  |         case SPLITTER_HOR => { | ||||||
|  |           if (dir % 2 == 1) { | ||||||
|  |             continue = false | ||||||
|  |             shootBeam(x+1, y, 0) | ||||||
|  |             shootBeam(x-1, y, 2) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         case SPLITTER_VER => { | ||||||
|  |           if (dir % 2 == 0) { | ||||||
|  |             continue = false | ||||||
|  |             shootBeam(x, y+1, 1) | ||||||
|  |             shootBeam(x, y-1, 3) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (continue) { | ||||||
|  |         val offset: (Int, Int) = OFFSETS(dir) | ||||||
|  |         x += offset._1 | ||||||
|  |         y += offset._2 | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (x < 0 || x >= width) continue = false | ||||||
|  |       if (y < 0 || y >= height) continue = false | ||||||
|  |     } while (continue) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def countEnergized(): Int = { | ||||||
|  |     var count: Int = 0 | ||||||
|  |     for (y: Int <- 0 until height) { | ||||||
|  |       for (x: Int <- 0 until width) { | ||||||
|  |         if (energized(y)(x)) count += 1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return count | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def clearEnergized(): Unit = { | ||||||
|  |     energized = Array.ofDim(height, width) | ||||||
|  |     path.clear() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def solve(path: String): Int = { | ||||||
|  |     loadInput(path) | ||||||
|  |  | ||||||
|  |     var solution: Int = 0 | ||||||
|  |     val totalSteps: Int = (width + height)*2 | ||||||
|  |  | ||||||
|  |     for (y: Int <- 0 until height) { | ||||||
|  |       print(f"\r${2.0*y/totalSteps*100}%.2f%%") | ||||||
|  |       clearEnergized() | ||||||
|  |       shootBeam(0, y, 0) | ||||||
|  |       solution = Math.max(solution, countEnergized()) | ||||||
|  |  | ||||||
|  |       print(f"\r${(2.0*y+1)/totalSteps*100}%.2f%%") | ||||||
|  |       clearEnergized() | ||||||
|  |       shootBeam(width-1, y, 1) | ||||||
|  |       solution = Math.max(solution, countEnergized()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (x: Int <- 0 until width) { | ||||||
|  |       print(f"\r${(2.0*x+height*2)/totalSteps*100}%.2f%%") | ||||||
|  |       clearEnergized() | ||||||
|  |       shootBeam(x, 0, 1) | ||||||
|  |       solution = Math.max(solution, countEnergized()) | ||||||
|  |  | ||||||
|  |       print(f"\r${(2.0*x+height*2+1)/totalSteps*100}%.2f%%") | ||||||
|  |       clearEnergized() | ||||||
|  |       shootBeam(x, height-1, 3) | ||||||
|  |       solution = Math.max(solution, countEnergized()) | ||||||
|  |     } | ||||||
|  |     println("100%") | ||||||
|  |  | ||||||
|  |     return solution | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def main(args: Array[String]): Unit = { | ||||||
|  |     val solution: Int = solve("./res/day16/input1.txt") | ||||||
|  |     println(solution) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								src/day18/Painter.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/day18/Painter.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | package day18 | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										234
									
								
								src/day18/Puzzle1.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/day18/Puzzle1.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | |||||||
|  | package day18 | ||||||
|  |  | ||||||
|  | import util.Ansi | ||||||
|  |  | ||||||
|  | import scala.collection.immutable.HashMap | ||||||
|  | import scala.collection.mutable.ArrayBuffer | ||||||
|  | import scala.io.{BufferedSource, Source} | ||||||
|  |  | ||||||
|  | object Puzzle1 { | ||||||
|  |   var grid: Array[Array[Byte]] = Array.empty | ||||||
|  |   var zones: Array[Array[Int]] = Array.empty | ||||||
|  |  | ||||||
|  |   val OFFSETS: Array[(Int, Int)] = Array( | ||||||
|  |     (1, 0), (0, 1), (-1, 0), (0, -1) | ||||||
|  |   ) | ||||||
|  |   val DIRS: String = "RDLU" | ||||||
|  |  | ||||||
|  |   // bits: NWSE | ||||||
|  |   val TILES: Map[Char, Byte] = HashMap( | ||||||
|  |     '|' -> 10, | ||||||
|  |     '-' -> 5, | ||||||
|  |     'L' -> 9, | ||||||
|  |     'J' -> 12, | ||||||
|  |     '7' -> 6, | ||||||
|  |     'F' -> 3, | ||||||
|  |     '.' -> 0, | ||||||
|  |     'S' -> 15 | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   val TILE_CHARS: Map[Int, Char] = HashMap( | ||||||
|  |     10 -> '│', | ||||||
|  |     5 -> '─', | ||||||
|  |     9 -> '└', | ||||||
|  |     12 -> '┘', | ||||||
|  |     6 -> '┐', | ||||||
|  |     3 -> '┌', | ||||||
|  |     0 -> ' ', | ||||||
|  |     15 -> '█' | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   val TILE_CHARS_BOLD: Map[Int, Char] = HashMap( | ||||||
|  |     10 -> '┃', | ||||||
|  |     5 -> '━', | ||||||
|  |     9 -> '┗', | ||||||
|  |     12 -> '┛', | ||||||
|  |     6 -> '┓', | ||||||
|  |     3 -> '┏', | ||||||
|  |     0 -> ' ', | ||||||
|  |     15 -> '█' | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   var height: Int = 0 | ||||||
|  |   var width: Int = 0 | ||||||
|  |   var startX: Int = 0 | ||||||
|  |   var startY: Int = 0 | ||||||
|  |   var path: ArrayBuffer[(Int, Int)] = new ArrayBuffer() | ||||||
|  |  | ||||||
|  |   def loadInput(path: String): Unit = { | ||||||
|  |     val source: BufferedSource = Source.fromFile(path) | ||||||
|  |     val lines: Array[String] = source.getLines().toArray | ||||||
|  |  | ||||||
|  |     var x: Int = 0 | ||||||
|  |     var y: Int = 0 | ||||||
|  |  | ||||||
|  |     var minX: Int = 0 | ||||||
|  |     var minY: Int = 0 | ||||||
|  |     var maxX: Int = 0 | ||||||
|  |     var maxY: Int = 0 | ||||||
|  |  | ||||||
|  |     for (line: String <- lines) { | ||||||
|  |       val parts: Array[String] = line.split(" ") | ||||||
|  |       val dir: Int = DIRS.indexOf(parts(0)) | ||||||
|  |       val dist: Int = parts(1).toInt | ||||||
|  |       val offset: (Int, Int) = OFFSETS(dir) | ||||||
|  |       x += offset._1*dist | ||||||
|  |       y += offset._2*dist | ||||||
|  |       minX = math.min(minX, x) | ||||||
|  |       minY = math.min(minY, y) | ||||||
|  |       maxX = math.max(maxX, x) | ||||||
|  |       maxY = math.max(maxY, y) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     width = maxX - minX + 1 | ||||||
|  |     height = maxY - minY + 1 | ||||||
|  |  | ||||||
|  |     grid = Array.ofDim(height, width) | ||||||
|  |     zones = Array.ofDim(height, width) | ||||||
|  |  | ||||||
|  |     x = -minX | ||||||
|  |     y = -minY | ||||||
|  |     var prevDir: Int = DIRS.indexOf(lines.last.split(" ")(0)) | ||||||
|  |     startX = x | ||||||
|  |     startY = y | ||||||
|  |  | ||||||
|  |     for ((line: String, i: Int) <- lines.zipWithIndex) { | ||||||
|  |       val parts: Array[String] = line.split(" ") | ||||||
|  |       val dir: Int = DIRS.indexOf(parts(0)) | ||||||
|  |       val dist: Int = parts(1).toInt | ||||||
|  |       val offset: (Int, Int) = OFFSETS(dir) | ||||||
|  |  | ||||||
|  |       for (_: Int <- 0 until dist) { | ||||||
|  |         val prevBit: Int = 1 << ((prevDir + 2) % 4) | ||||||
|  |         val curBit: Int = 1 << dir | ||||||
|  |         grid(y)(x) = (prevBit| curBit).toByte | ||||||
|  |         x += offset._1 | ||||||
|  |         y += offset._2 | ||||||
|  |         prevDir = dir | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     source.close() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def display(): Unit = { | ||||||
|  |     for (y: Int <- 0 until height) { | ||||||
|  |       for (x: Int <- 0 until width) { | ||||||
|  |         val tile: Byte = grid(y)(x) | ||||||
|  |         val zone: Int = zones(y)(x) | ||||||
|  |         if (zone == 1) { | ||||||
|  |           print(Ansi.BG_RGB(163, 61, 61)) | ||||||
|  |         } else if (zone == 2) { | ||||||
|  |           print(Ansi.BG_RGB(77, 163, 61)) | ||||||
|  |         } | ||||||
|  |         if (path.contains((x, y))) { | ||||||
|  |           print(Ansi.BOLD) | ||||||
|  |           print(TILE_CHARS_BOLD(tile)) | ||||||
|  |         } else { | ||||||
|  |           print(TILE_CHARS(tile)) | ||||||
|  |         } | ||||||
|  |         print(Ansi.CLEAR) | ||||||
|  |       } | ||||||
|  |       println() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def calculateArea(): Int = { | ||||||
|  |     path = 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 changed: Boolean = true | ||||||
|  |     var exteriorZone: Int = 0 | ||||||
|  |  | ||||||
|  |     do { | ||||||
|  |       changed = false | ||||||
|  |       newZones = copyZones() | ||||||
|  |       for (y: Int <- 0 until height) { | ||||||
|  |         for (x: Int <- 0 until width) { | ||||||
|  |           if (zones(y)(x) != 0) { | ||||||
|  |             if (floodTile(x, y, newZones, path)) { | ||||||
|  |               changed = true | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       zones = newZones | ||||||
|  |     } while (changed) | ||||||
|  |     println("Flooded 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) area += 1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     println(s"Calculated area: $area") | ||||||
|  |  | ||||||
|  |     return area | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def floodTile(x: Int, y: Int, newZones: Array[Array[Int]], path: ArrayBuffer[(Int, Int)]): Boolean = { | ||||||
|  |     var changed: Boolean = false | ||||||
|  |     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 && !path.contains((x2, y2))) { | ||||||
|  |           newZones(y2)(x2) = zones(y)(x) | ||||||
|  |           changed = true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return changed | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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/day18/input1.txt") | ||||||
|  |     println(solution) | ||||||
|  |     //display() | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								src/day18/Walker.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/day18/Walker.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | package day18 | ||||||
|  |  | ||||||
|  | class Walker { | ||||||
|  |   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() | ||||||
|  |     x = startX | ||||||
|  |     y = startY | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def this(startX: Int, startY: Int, excludeX: Int, excludeY: Int) = { | ||||||
|  |     this(startX, startY) | ||||||
|  |     lastX = excludeX | ||||||
|  |     lastY = excludeY | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def getX(): Int = x | ||||||
|  |   def getY(): Int = y | ||||||
|  |   def getPos(): (Int, Int) = (x, y) | ||||||
|  |   def getDistance(): Int = distance | ||||||
|  |  | ||||||
|  |   def walk(grid: Array[Array[Byte]]): Unit = { | ||||||
|  |     val height: Int = grid.length | ||||||
|  |     val width: Int = if (height == 0) 0 else grid(0).length | ||||||
|  |     val (x2: Int, y2: Int) = getNextPos(grid, width, height) | ||||||
|  |     lastX = x | ||||||
|  |     lastY = y | ||||||
|  |     x = x2 | ||||||
|  |     y = y2 | ||||||
|  |     distance += 1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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) <- Walker.OFFSETS.zipWithIndex) { | ||||||
|  |       val x2: Int = x + dx | ||||||
|  |       val y2: Int = y + dy | ||||||
|  |       if (x2 != lastX || y2 != lastY) { | ||||||
|  |         if (0 <= x2 && x2 < width && 0 <= y2 && y2 < height) { | ||||||
|  |           val bit: Byte = (1 << i).toByte | ||||||
|  |           val bit2: Byte = (1 << ((i + 2) % 4)).toByte | ||||||
|  |           if ((curTile & bit) != 0 && (grid(y2)(x2) & bit2) != 0) { | ||||||
|  |             return (x2, y2) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     throw new Exception("Dead-end path") | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | object Walker { | ||||||
|  |   val OFFSETS: Array[(Int, Int)] = Array( | ||||||
|  |     (1, 0), (0, 1), (-1, 0), (0, -1) | ||||||
|  |   ) | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								tests/day15/Puzzle2Test.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/day15/Puzzle2Test.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package day15 | ||||||
|  |  | ||||||
|  | import org.scalatest.funsuite.AnyFunSuite | ||||||
|  |  | ||||||
|  | class Puzzle2Test extends AnyFunSuite { | ||||||
|  |   test("Puzzle2.solve") { | ||||||
|  |     assert(Puzzle2.solve("tests_res/day15/input1.txt") == 145) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								tests/day16/Puzzle1Test.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/day16/Puzzle1Test.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package day16 | ||||||
|  |  | ||||||
|  | import org.scalatest.funsuite.AnyFunSuite | ||||||
|  |  | ||||||
|  | class Puzzle1Test extends AnyFunSuite { | ||||||
|  |   test("Puzzle1.solve") { | ||||||
|  |     assert(Puzzle1.solve("tests_res/day16/input1.txt") == 46) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								tests/day16/Puzzle2Test.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/day16/Puzzle2Test.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package day16 | ||||||
|  |  | ||||||
|  | import org.scalatest.funsuite.AnyFunSuite | ||||||
|  |  | ||||||
|  | class Puzzle2Test extends AnyFunSuite { | ||||||
|  |   test("Puzzle2.solve") { | ||||||
|  |     assert(Puzzle2.solve("tests_res/day16/input1.txt") == 51) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								tests/day18/Puzzle1Test.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/day18/Puzzle1Test.scala
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package day18 | ||||||
|  |  | ||||||
|  | import org.scalatest.funsuite.AnyFunSuite | ||||||
|  |  | ||||||
|  | class Puzzle1Test extends AnyFunSuite { | ||||||
|  |   test("Puzzle1.solve") { | ||||||
|  |     assert(Puzzle1.solve("tests_res/day18/input1.txt") == 62) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								tests_res/day16/input1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests_res/day16/input1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | .|...\.... | ||||||
|  | |.-.\..... | ||||||
|  | .....|-... | ||||||
|  | ........|. | ||||||
|  | .......... | ||||||
|  | .........\ | ||||||
|  | ..../.\\.. | ||||||
|  | .-.-/..|.. | ||||||
|  | .|....-|.\ | ||||||
|  | ..//.|.... | ||||||
							
								
								
									
										14
									
								
								tests_res/day18/input1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests_res/day18/input1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | R 6 (#70c710) | ||||||
|  | D 5 (#0dc571) | ||||||
|  | L 2 (#5713f0) | ||||||
|  | D 2 (#d2c081) | ||||||
|  | R 2 (#59c680) | ||||||
|  | D 2 (#411b91) | ||||||
|  | L 5 (#8ceee2) | ||||||
|  | U 2 (#caa173) | ||||||
|  | L 1 (#1b58a2) | ||||||
|  | U 2 (#caa171) | ||||||
|  | R 2 (#7807d2) | ||||||
|  | U 3 (#a77fa3) | ||||||
|  | L 2 (#015232) | ||||||
|  | U 2 (#7a21e3) | ||||||
		Reference in New Issue
	
	Block a user