-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package `2015`.day18 | ||
|
||
import prelude.* | ||
import utils.* | ||
|
||
enum Cell: | ||
case On, Off | ||
override def toString = if this == Cell.On then "#" else "." | ||
|
||
extension (sc: StringContext) | ||
def gol(args: Any*): GameOfLifeGrid = | ||
sc.s(args*).trim.stripMargin.pipe(GameOfLifeGrid.of) | ||
|
||
class GameOfLifeGrid(override val s: Size) extends Grid[Cell](s): | ||
def apply(buf: Grid[Cell]) = | ||
for | ||
y <- 0 until s.height | ||
x <- 0 until s.width | ||
do this(Point(y, x)) = buf(Point(y, x)) | ||
|
||
def neighbors(pos: Point): Int = | ||
val coords = for | ||
dy <- -1 to 1 | ||
dx <- -1 to 1 | ||
if dy != 0 || dx != 0 | ||
yield pos + Point(dy, dx) | ||
coords.map(this.get).flatten.count(_ == Cell.On) | ||
|
||
def next(pos: Point): Cell = | ||
val n = neighbors(pos) | ||
apply(pos) match | ||
case Cell.On if n == 2 || n == 3 => Cell.On | ||
case Cell.Off if n == 3 => Cell.On | ||
case _ => Cell.Off | ||
|
||
object GameOfLifeGrid: | ||
def of(other: String): GameOfLifeGrid = | ||
other.split("\n").toVector.pipe(of) | ||
|
||
def of(other: Seq[String]): GameOfLifeGrid = | ||
val s = Size(other.size, other.head.size) | ||
val g = new GameOfLifeGrid(s) | ||
for | ||
y <- 0 until s.height | ||
x <- 0 until s.width | ||
do g(Point(y, x)) = if other(y)(x) == '#' then Cell.On else Cell.Off | ||
g | ||
|
||
extension (g: GameOfLifeGrid) | ||
def part1Next: GameOfLifeGrid = | ||
val nextGrid = new GameOfLifeGrid(g.s) | ||
for | ||
y <- 0 until g.s.height | ||
x <- 0 until g.s.width | ||
do nextGrid(Point(y, x)) = g.next(Point(y, x)) | ||
nextGrid | ||
|
||
def stuckCorners(): GameOfLifeGrid = | ||
Set( | ||
Point(0, 0), | ||
Point(0, g.s.width - 1), | ||
Point(g.s.height - 1, 0), | ||
Point(g.s.height - 1, g.s.width - 1), | ||
).foreach { g.update(_, Cell.On) } | ||
g | ||
|
||
def part2Next: GameOfLifeGrid = | ||
val nextGrid = new GameOfLifeGrid(g.s) | ||
g.stuckCorners() | ||
for | ||
y <- 0 until g.s.height | ||
x <- 0 until g.s.width | ||
do nextGrid(Point(y, x)) = g.next(Point(y, x)) | ||
nextGrid.stuckCorners() | ||
nextGrid | ||
|
||
def solution(grid: GameOfLifeGrid, f: GameOfLifeGrid => GameOfLifeGrid) = | ||
Iterator.iterate(grid)(f).drop(100).next.raw.count(_ == Cell.On) | ||
|
||
def part1(grid: GameOfLifeGrid) = | ||
solution(grid, _.part1Next) | ||
|
||
def part2(grid: GameOfLifeGrid) = | ||
solution(grid, _.part2Next) | ||
|
||
@main def main() = | ||
val raw = fromFile(".cache/2015/18.txt").mkString | ||
|
||
println(part1(GameOfLifeGrid.of(raw))) | ||
println(part2(GameOfLifeGrid.of(raw))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package `2015`.day18 | ||
|
||
import prelude.* | ||
import munit.FunSuite | ||
|
||
class Tests extends FunSuite: | ||
test("part1"): | ||
val grid = gol""" | ||
|.#.#.# | ||
|...##. | ||
|#....# | ||
|..#... | ||
|#.#..# | ||
|####.. | ||
""" | ||
val steps = Iterator | ||
.iterate(grid)(_.part1Next) | ||
.take(5) | ||
.drop(1) | ||
.toSeq | ||
|
||
val expected = Seq( | ||
gol""" | ||
|..##.. | ||
|..##.# | ||
|...##. | ||
|...... | ||
|#..... | ||
|#.##.. | ||
""", | ||
gol""" | ||
|..###. | ||
|...... | ||
|..###. | ||
|...... | ||
|.#.... | ||
|.#.... | ||
""", | ||
gol""" | ||
|...#.. | ||
|...... | ||
|...#.. | ||
|..##.. | ||
|...... | ||
|...... | ||
""", | ||
gol""" | ||
|...... | ||
|...... | ||
|..##.. | ||
|..##.. | ||
|...... | ||
|...... | ||
""", | ||
) | ||
assertEquals(steps, expected) | ||
|
||
test("part2"): | ||
val grid = gol""" | ||
|##.#.# | ||
|...##. | ||
|#....# | ||
|..#... | ||
|#.#..# | ||
|####.# | ||
""" | ||
val steps = Iterator | ||
.iterate(grid)(_.part2Next) | ||
.take(6) | ||
.drop(1) | ||
.toSeq | ||
|
||
val expected = Seq( | ||
gol""" | ||
|#.##.# | ||
|####.# | ||
|...##. | ||
|...... | ||
|#...#. | ||
|#.#### | ||
""", | ||
gol""" | ||
|#..#.# | ||
|#....# | ||
|.#.##. | ||
|...##. | ||
|.#..## | ||
|##.### | ||
""", | ||
gol""" | ||
|#...## | ||
|####.# | ||
|..##.# | ||
|...... | ||
|##.... | ||
|####.# | ||
""", | ||
gol""" | ||
|#.#### | ||
|#....# | ||
|...#.. | ||
|.##... | ||
|#..... | ||
|#.#..# | ||
""", | ||
gol""" | ||
|##.### | ||
|.##..# | ||
|.##... | ||
|.##... | ||
|#.#... | ||
|##...# | ||
""", | ||
) | ||
assertEquals(steps, expected) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package utils | ||
|
||
import scala.reflect.ClassTag | ||
|
||
trait GridOps[A: ClassTag]: | ||
def s: Size | ||
def raw: Array[A] | ||
|
||
def apply(pos: Point): A | ||
|
||
def update(pos: Point, a: A): Unit | ||
|
||
override def toString(): String = | ||
val sb = new StringBuilder | ||
for y <- 0 until s.height do | ||
for x <- 0 until s.width do | ||
sb.append(apply(Point(y, x)) match | ||
case null => ' ' | ||
case a => a.toString.head, | ||
) | ||
sb.append('\n') | ||
sb.toString | ||
|
||
def data: Array[Array[A]] = raw.grouped(s.width).toArray | ||
def get(pos: Point): Option[A] = | ||
if pos.y >= 0 && pos.y < s.height && pos.x >= 0 && pos.x < s.width then | ||
Some(apply(pos)) | ||
else None | ||
|
||
def of(grid: Seq[Seq[A]]): Grid[A] = | ||
val s = Size(grid(0).length, grid.length) | ||
val g = new Grid[A](s) | ||
for | ||
y <- 0 until s.height | ||
x <- 0 until s.width | ||
do g(Point(y, x)) = grid(y)(x) | ||
g | ||
|
||
class Grid[A: ClassTag](val s: Size) extends GridOps[A]: | ||
val raw = new Array[A](s.width * s.height) | ||
def apply(pos: Point): A = raw(pos.y * s.width + pos.x) | ||
def update(pos: Point, a: A): Unit = raw(pos.y * s.width + pos.x) = a | ||
|
||
override def equals(that: Any): Boolean = that match | ||
case that: Grid[?] => this.s == that.s && this.raw.sameElements(that.raw) | ||
case _ => false |