Skip to content

Instantly share code, notes, and snippets.

@majk-p
Last active November 5, 2022 20:48
Show Gist options
  • Save majk-p/b944530e2b3e8cf00bf9313429bc66c8 to your computer and use it in GitHub Desktop.
Save majk-p/b944530e2b3e8cf00bf9313429bc66c8 to your computer and use it in GitHub Desktop.
ArraySeq based GoL grid model
//> using scala "3"
//> using lib "org.typelevel::cats-effect:3.3.12"
//> using lib "co.fs2::fs2-core:3.3.0"
package net.michalp.conway
import cats.effect.IO
import cats.effect.IOApp
import fs2.Stream
import scala.collection.immutable.ArraySeq
import scala.concurrent.duration._
object Main extends IOApp.Simple {
val initialState: Coordinates => Boolean =
List((1, 1), (1, 2), (1, 3)).contains(_)
val grid: Grid = Grid.instance(5, initialState)
val gameLogic: Cell => Boolean = cell => {
val activeNeighbors = cell.neighbors.count(_ == true)
if (cell.isActive)
activeNeighbors == 2 || activeNeighbors == 3
else
activeNeighbors == 3
}
def run: IO[Unit] =
Stream
.iterate(grid)(g => g.map(gameLogic))
.evalTap(g => Render.render(g)("⬛", "⬜"))
.metered(1.second)
.compile
.drain
}
trait Grid {
def value: ArraySeq[ArraySeq[Cell]]
protected val size: Int
}
type Coordinates = (Int, Int)
object Grid {
def instance(n: Int, initialState: Coordinates => Boolean): Grid = new Grid {
val size = n
val value: ArraySeq[ArraySeq[Cell]] = ArraySeq.tabulate[Cell](n, n) { (x, y) =>
Cell(initialState(x, y), this.neighborCoordinates(x, y).map(initialState))
}
}
extension (grid: Grid) {
def neighborCoordinates(x: Int, y: Int): Seq[Coordinates] =
// format: off
Seq(
(x-1, y-1), (x, y-1), (x+1,y-1),
(x-1, y), (x+1,y),
(x-1, y+1), (x, y+1), (x+1,y+1),
).filterNot(
(i, j) => i < 0 || i >= grid.size || j < 0 || j >= grid.size
)
def map(f: Cell => Boolean): Grid = new Grid {
val size = grid.size
val mappedState: ArraySeq[ArraySeq[Boolean]] = grid.value.map(_.map(f))
val value = mappedState.zipWithIndex.map { (row, i) =>
row.zipWithIndex.map { (cellActivity, j) =>
Cell(
isActive = cellActivity,
grid.neighborCoordinates(i, j).map((x, y) => mappedState(x)(y))
)
}
}
}
}
}
case class Cell(isActive: Boolean, neighbors: Seq[Boolean])
object Render {
def render(grid: Grid)(active: String, inactive: String): IO[Unit] = {
val text: String = grid.value.map {row =>
row.map { cell =>
if(cell.isActive) active else inactive
}.mkString
}.mkString("\n")
IO.println(text + separator)
}
private val separator = "\n"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment