Conway’s Game of Life
Mix.install([
{:kino, "~> 0.14.1"}
])
import IEx.Helpers
Rules of CGL
-
Life is a game played on a grid
-
Grids are made up of cells
-
The number of neighbors for a cell (includes above/below/left/right and corners)
-
If a cell has fewer than 2 neighbors, it will die of loneliness
-
If a cell has more than 3 neighbors, it will die of overcrowding
-
If a cell has 2 neighbors, it will stay the same.
-
If a cell has 3 neighbors, conditions are perfect and it will spring to life
Plan
-
Cell (create: new, reduce: evolve, convert: show, data: x, y, width, alive)
-
Board (create: new, reduce: evolve, convert: show, data: width, height, cells)
nil
Cell
defmodule Cell do
defstruct [x: 0, y: 0, width: 10, alive: true]
def new(opts \\ []) do
__struct__(opts)
end
def evolve(cell, neighbor_count) do
next_cell_state =
cond do
neighbor_count < 2 ->
false
neighbor_count > 3 ->
false
neighbor_count == 2 ->
cell.alive
neighbor_count == 3 ->
true
end
%{cell| alive: next_cell_state}
end
def show(cell) do
x = cell.x * cell.width
y = cell.y * cell.width
"""
"""
end
defp color(cell) do
case cell.alive do
true -> "green"
false -> "black"
end
end
end
box = Cell.new([x: 2, y: 8])
|> Cell.show()
svg =
"""
#{box}
"""
|> Kino.Image.new(:svg)
Cell.new([x: 10, y: 10, alive: false]) |> Cell.evolve(2)
Grid
defmodule Grid do
defstruct [width: 10, height: 10, cells: %{}]
def new(width, height, live_cell_locations) do
cells =
for x <- 0..(width-1), y <- 0..(height-1), into: %{} do
{{x, y}, Cell.new([x: x, y: y, alive: {x, y} in live_cell_locations])}
end
%__MODULE__{width: width, height: height, cells: cells}
end
def evolve(grid) do
cells =
for x <- 0..(grid.width-1), y <- 0..(grid.height-1), into: %{} do
old_cell = grid.cells[{x, y}]
new_cell = Cell.evolve(old_cell, neighbor_count(grid, old_cell))
{{x, y}, new_cell}
end
%{grid | cells: cells}
end
defp neighbor_count(grid, cell) do
for x <- (cell.x - 1)..(cell.x + 1),
y <- (cell.y - 1)..(cell.y + 1), {x, y} != {cell.x, cell.y} do
Map.get(grid.cells, {x, y}, Cell.new([alive: false]))
end
|> Enum.count(fn x -> x.alive end)
end
def show(grid) do
cells =
for x <- 0..(grid.width-1), y <- 0..(grid.height-1) do
Cell.show(grid.cells[{x, y}])
end
"""
#{cells}
"""
end
end
grid = Grid.new(3, 3, [{0, 1}, {1, 1}, {2, 1}])
grid
|> Grid.evolve
|> Grid.evolve
|> Grid.evolve
|> Grid.evolve
|> Grid.show
|> Kino.Image.new(:svg)
# Grid.neighbor_count(grid, grid.cells[{1, 1}])
Processes and State
me = self()
send(me, :hi)
self()
|> i
receive do
m -> m
end
defmodule Life do
def start(height, width, cells) do
grid = Grid.new(height, width, cells)
spawn(fn -> loop(grid) end)
end
def loop(grid) do
grid
|> listen()
|> loop()
end
def listen(grid) do
receive do
:evolve ->
Grid.evolve(grid)
{:show, from_pid} ->
send(from_pid, Grid.show(grid))
grid
end
end
def evolve(pid) do
send(pid, :evolve)
end
def show(pid) do
send(pid, {:show, self()})
receive do
m -> m
end
|> Kino.Image.new(:svg)
end
end
life = Life.start(3, 3, [{0, 1}, {1, 1}, {2, 1}])
send(life, {:show, self()})
receive do
m -> m
end
|> Kino.Image.new(:svg)
Life.evolve(life)
Life.show(life)
GenServers
defmodule LifeServer do
use GenServer
# API
def start_link({name, _h, _w, _cells}=args) do
GenServer.start_link(__MODULE__, args, name: name)
end
def evolve(pid \\ :life) do
GenServer.call(pid, :evolve)
|> Kino.Image.new(:svg)
end
def boom(pid \\ :life) do
GenServer.cast(pid, :boom)
end
# callbacks
def init({_name, h, w, cells}) do
grid = Grid.new(h, w, cells)
{:ok, grid}
end
def handle_cast(:boom, _state) do
raise "boom"
end
def handle_call(:evolve, _from, grid) do
new_grid = Grid.evolve(grid)
{:reply, Grid.show(new_grid), new_grid}
end
# functions
def child_spec({name, _, _, _}=attrs) do
%{id: name, start: {LifeServer, :start_link, [attrs]}}
end
end
LifeServer.boom(:life)
init_values = {:life, 3, 3, [{0, 1}, {1, 1}, {2, 1}]}
init_values2 = {:la_vida_loca, 3, 3, [{1, 0}, {1, 1}, {1, 2}]}
childspecs = [{LifeServer, init_values}, {LifeServer, init_values2}]
Enum.each(childspecs, &Kino.start_child!/1)
[GenServer.whereis(:life), GenServer.whereis(:la_vida_loca)]
GenServer.call(:life, :evolve) |> Kino.Image.new(:svg)
LifeServer.evolve(:la_vida_loca)
LifeServer.child_spec({:life, 3, 3, [{0, 1}, {1, 1}, {2, 1}]})
# bad things man
LifeServer.boom()
The Fly in the Ointment
i LifeServer
i :sys
:sys.get_state :life
Lifecycle