diff --git a/README.md b/README.md
index 9dc91b6..5a9ef5b 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: 15 / 50
+#### Stars: 16 / 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: | 9 | 10 |
+| 4
:star::star: | 5
:star::star: | 6
:star::star: | 7
:star::star: | 8
:star::star: | 9 | 10 |
| 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/day8/Ghost.scala b/src/day8/Ghost.scala
new file mode 100644
index 0000000..908216d
--- /dev/null
+++ b/src/day8/Ghost.scala
@@ -0,0 +1,80 @@
+package day8
+
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+
+class Ghost(val startId: String) extends Iterator[Long] {
+ var preLoopOffsets: ArrayBuffer[Int] = new ArrayBuffer()
+ var loopOffsets: ArrayBuffer[Int] = new ArrayBuffer()
+ var visited: ArrayBuffer[(String, Int)] = new ArrayBuffer()
+ private var distIterator: Iterator[Long] = Iterator.empty
+
+ def computePath(nodes: mutable.Map[String, Node], rule: Array[Int]): Unit = {
+ var id: String = startId
+ preLoopOffsets.addOne(0)
+ var ruleI: Int = 0
+
+ // While not looped
+ while (!visited.contains((id, ruleI))) {
+ //println(s" Visited $id (ruleI = $ruleI)")
+ visited.addOne((id, ruleI))
+ id = nodes(id).nextNodes(rule(ruleI))
+ preLoopOffsets(preLoopOffsets.length-1) += 1
+ ruleI = (ruleI + 1) % rule.length
+ if (id.last == 'Z') {
+ preLoopOffsets.addOne(0)
+ }
+ }
+
+ val loopStartDist: Int = visited.indexOf((id, ruleI))
+ var dist: Int = 0
+ var loopStartI: Int = 0
+ while (dist < loopStartDist) {
+ dist += preLoopOffsets(loopStartI)
+ loopStartI += 1
+ }
+ loopStartI -= 1
+ preLoopOffsets(preLoopOffsets.length-1) += preLoopOffsets(loopStartI) - loopStartDist
+ println(s" Loop from ($id, $ruleI) (index=$loopStartI)")
+ println(s" # of offsets: ${preLoopOffsets.length}")
+
+ loopOffsets.addAll(preLoopOffsets.drop(loopStartI+1))
+ preLoopOffsets = preLoopOffsets.dropRight(preLoopOffsets.length - loopStartI - 1)
+ println(preLoopOffsets.length)
+ println(loopOffsets.length)
+ distIterator = new GhostIterator(preLoopOffsets.toArray, loopOffsets.toArray)
+ }
+
+ override def hasNext: Boolean = true
+
+ override def next(): Long = distIterator.next()
+
+ private class GhostIterator(val preLoopOffsets: Array[Int], val loopOffsets: Array[Int]) extends Iterator[Long] {
+ private var inLoop: Boolean = false
+ private var offsetI: Int = 0
+ private var dist: Long = 0
+ override def hasNext: Boolean = true
+
+ override def next(): Long = {
+ var offset: Int = 0
+ if (inLoop) {
+ offset = loopOffsets(offsetI)
+ } else {
+ offset = preLoopOffsets(offsetI)
+ }
+ dist += offset
+
+ offsetI += 1
+ if (!inLoop) {
+ if (offsetI == preLoopOffsets.length) {
+ offsetI = 0
+ inLoop = true
+ }
+ } else {
+ offsetI %= loopOffsets.length
+ }
+
+ return dist
+ }
+ }
+}
diff --git a/src/day8/Puzzle2.scala b/src/day8/Puzzle2.scala
new file mode 100644
index 0000000..914f3a1
--- /dev/null
+++ b/src/day8/Puzzle2.scala
@@ -0,0 +1,56 @@
+package day8
+
+import scala.collection.mutable
+import scala.io.{BufferedSource, Source}
+import scala.util.matching.Regex
+import scala.util.matching.Regex.Match
+
+object Puzzle2 {
+ var rule: Array[Int] = Array.empty
+ val nodes: mutable.Map[String, Node] = new mutable.HashMap()
+ var ghosts: Array[Ghost] = Array.empty
+ def loadInput(path: String): Unit = {
+ val source: BufferedSource = Source.fromFile(path)
+ val lines: String = source.getLines().mkString("\n")
+ val parts: Array[String] = lines.split("\n\n")
+
+ rule = parts(0).map(c => "LR".indexOf(c)).toArray
+ val regexp: Regex = new Regex("([A-Z]{3}) = \\(([A-Z]{3}), ([A-Z]{3})\\)")
+ for (line: String <- parts(1).split("\n")) {
+ val matches: Match = regexp.findFirstMatchIn(line).get
+ val id: String = matches.group(1)
+ val left: String = matches.group(2)
+ val right: String = matches.group(3)
+ nodes(id) = new Node(id, Array(left, right))
+ }
+
+ source.close()
+ }
+
+ def solve(path: String): Long = {
+ loadInput(path)
+
+ ghosts = nodes.keys.filter(k => k.last == 'A').map(k => new Ghost(k)).toArray
+
+ val ghostDists: mutable.Map[Ghost, Long] = new mutable.HashMap()
+ for (ghost: Ghost <- ghosts) {
+ println(s"Ghost from ${ghost.startId}")
+ ghost.computePath(nodes, rule)
+ ghostDists(ghost) = ghost.next()
+ }
+
+ while (ghostDists.values.toArray.distinct.length != 1) {
+ val minimum: (Ghost, Long) = ghostDists.toArray.minBy(_._2)
+ ghostDists(minimum._1) = minimum._1.next()
+ }
+
+ val dists: Array[Long] = ghostDists.values.toArray
+
+ return dists(0)
+ }
+
+ def main(args: Array[String]): Unit = {
+ val solution: Long = solve("./res/day8/input1.txt")
+ println(solution)
+ }
+}
diff --git a/tests/day8/Puzzle2Test.scala b/tests/day8/Puzzle2Test.scala
new file mode 100644
index 0000000..12c7134
--- /dev/null
+++ b/tests/day8/Puzzle2Test.scala
@@ -0,0 +1,9 @@
+package day8
+
+import org.scalatest.funsuite.AnyFunSuite
+
+class Puzzle2Test extends AnyFunSuite {
+ test("Puzzle2.solve") {
+ assert(Puzzle2.solve("tests_res/day8/input1.txt") == 6)
+ }
+}