object GameOfLife extends App {
case class Point(x: Int, y: Int)
type World = Set[Point]
val startingWorld = Set(Point(0, 0), Point(0, 1), Point(0, -1), Point(1, 0), Point(-1, 0))
allGenerations(startingWorld).take(10).map(worldToString).foreach(println)
def allGenerations(w: World) = Stream.iterate(w)(nextGeneration).takeWhile(_.nonEmpty)
def nextGeneration(w: World) = w.flatMap(nearbyCluster).filter(isAlive(_, w))
def nearbyCluster(p: Point) = for {
x: Int <- (p.x - 1 to p.x + 1).toSet
y: Int <- (p.y - 1 to p.y + 1).toSet
} yield Point(x, y)
def isAlive(p: Point, w: World) = rules(w.contains(p), neighbourCount(p, w))
def rules(alive: Boolean, nc: Int) = nc == 3 || (alive && nc == 2)
def neighbourCount(p: Point, w: World) = nearbyCluster(p).intersect(w).filterNot(p.equals).size
def worldToString(w: World) =
(w.map(_.y).max to w.map(_.y).min by -1)
.map(y => (w.map(_.x).min to w.map(_.x).max)
.map(x => if (w.contains(Point(x, y))) 'x' else '.'))
.map(_.mkString(" "))
.mkString("\n") + "\n"
}