Skip to content

Commit

Permalink
feat: 2015 day 18
Browse files Browse the repository at this point in the history
  • Loading branch information
scarf005 committed Oct 3, 2024
1 parent 0ac8fa7 commit 46ba248
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 0 deletions.
90 changes: 90 additions & 0 deletions 2015/day18.scala
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)))
115 changes: 115 additions & 0 deletions 2015/day18.test.scala
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)
46 changes: 46 additions & 0 deletions 2015/grid.scala
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

0 comments on commit 46ba248

Please sign in to comment.