Mix.install([:kino])
This is an experimentation notebook to try out Kino.animate/3
The following Life implementation is adapted from this gist.
defmodule Life.Grid do
defstruct data: nil
def new(data) when is_list(data) do
%Life.Grid{data: list_to_data(data)}
end
def size(%Life.Grid{data: data}), do: tuple_size(data)
def cell_status(grid, x, y) do
grid.data
|> elem(y)
|> elem(x)
end
def next(grid) do
%Life.Grid{grid | data: new_data(size(grid), &next_cell_status(grid, &1, &2))}
end
defp new_data(size, fun) do
for y <- 0..(size - 1) do
for x <- 0..(size - 1) do
fun.(x, y)
end
end
|> list_to_data
end
defp list_to_data(data) do
data
|> Enum.map(&List.to_tuple/1)
|> List.to_tuple()
end
def next_cell_status(grid, x, y) do
case {cell_status(grid, x, y), alive_neighbours(grid, x, y)} do
{1, 2} -> 1
{1, 3} -> 1
{0, 3} -> 1
{_, _} -> 0
end
end
defp alive_neighbours(grid, cell_x, cell_y) do
for x <- (cell_x - 1)..(cell_x + 1),
y <- (cell_y - 1)..(cell_y + 1),
x in 0..(size(grid) - 1) and
y in 0..(size(grid) - 1) and
(x != cell_x or y != cell_y) and
cell_status(grid, x, y) == 1 do
1
end
|> Enum.sum()
end
end
Below there are a few interesting patterns.
queen_bee = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
]
pattern = queen_bee
Now, we'll define a module which turns the grid into an SVG string so that we can visualise it.
defmodule Life.Svg do
@cell_size 10
def render(grid) do
size = Life.Grid.size(grid)
cells =
for y <- 0..(size - 1), x <- 0..(size - 1), into: "" do
status = Life.Grid.cell_status(grid, x, y)
fill = if status == 0, do: "#EEE", else: "purple"
"<rect x=\"#{x * @cell_size}\" y=\"#{y * @cell_size}\" width=\"10\" height=\"10\" fill=\"#{fill}\" />\n"
end
"""
<svg viewBox="0 0 #{@cell_size * size} #{@cell_size * size}" xmlns="http://www.w3.org/2000/svg">
#{cells}
</svg>
"""
|> Kino.Image.new(:svg)
end
end
We'll define a function which returns a randomised 2-dimensional array which will be our grid.
randomize = fn size ->
for _ <- 1..size, do: Enum.map(1..size, fn _ -> Enum.random([0, 1]) end)
end
We'll display a button, which when pressed it'll generate and preview a few randomised initial configurations for Life.
button = Kino.Control.button("randomize")
Kino.Control.subscribe(button, :randomize)
button
widget = Kino.Frame.new() |> Kino.render()
loop = fn f ->
receive do
{:randomize, _} ->
Kino.Frame.render(widget, Life.Svg.render(Life.Grid.new(randomize.(22))))
f.(f)
_ ->
:ok
end
end
loop.(loop)
We can finally animate Life!
Kino.animate(100, Life.Grid.new(pattern), fn grid ->
{:cont, Life.Svg.render(grid), Life.Grid.next(grid)}
end)