type Board = List[Pos]
val width: Int = 5
val height: Int = 5
def showcells(b: Board): IO[Unit] = seqn(b.map(p => writeat(p)("○")))
def isAlive(b: Board)(p: Pos): Boolean = b.contains(p)
def isEmpty(b: Board)(p: Pos): Boolean = !isAlive(b)(p)
def neighbs(p: Pos) = List(
(p._1 - 1, p._2 - 1), (p._1, p._2 - 1),
(p._1 + 1, p._2 - 1), (p._1 - 1, p._2),
(p._1 + 1, p._2), (p._1 - 1, p._2 + 1),
(p._1, p._2 + 1), (p._1 + 1, p._2 + 1)
).map(wrap)
def wrap(p: Pos): Pos =
(((p._1 - 1) % width) + 1, ((p._2 - 1) % height) + 1)
def liveneigbs(b: Board)(p: Pos): Int =
neighbs(p).count(isAlive(b))
def survivers(b: Board): List[Pos] =
b.filter(p => List(2, 3).contains(liveneigbs(b)(p)))
def births(b: Board): List[Pos] =
b.flatMap(p => neighbs(p))
.distinct
.filter(p => isEmpty(b)(p))
.filter(p => liveneigbs(b)(p) == 3)
def nextgen(b: Board): List[Pos] =
survivers(b) ++ births(b)
def life(b: Board): IO[Unit] =
for {
_ <- cls
_ <- showcells(b)
_ <- IO(Thread.sleep(500L))
_ <- life(nextgen(b))
} yield ()
life(List((4, 2), (2, 3), (4, 3), (3, 4), (4, 4))).unsafeRunSync()