Skip to content

Instantly share code, notes, and snippets.

@zorbash
Created November 10, 2021 13:51

Revisions

  1. zorbash created this gist Nov 10, 2021.
    145 changes: 145 additions & 0 deletions animations.livemd
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,145 @@
    # Animating Outputs with Kino.animate

    ## The Grid

    This is an experimentation notebook to try out `Kino.animate/3`

    See https://github.com/livebook-dev/kino/pull/49

    <!-- livebook:{"break_markdown":true} -->

    The following Life implementation is adapted from [this gist](https://gist.github.com/sasa1977/6877c52c3c35c2c03c82).

    ```elixir
    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
    ```

    Define patterns as lists.

    `TODO` Write a parser for the [RLE](https://www.conwaylife.com/wiki/Run_Length_Encoded) format
    and read the pattern from an input.

    ```elixir
    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
    ```

    ```elixir
    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
    ```

    ```elixir
    Kino.animate(100, Life.Grid.new(pattern), fn grid ->
    {:cont, Life.Svg.render(grid), Life.Grid.next(grid)}
    end)
    ```

    The following snippet expects [NervesLivebook](https://github.com/livebook-dev/nerves_livebook)
    running in embedded mode with the picam package installed on a raspberry pi with a camera module.

    ```elixir
    Picam.set_img_effect(:sketch)

    Kino.animate(50, 0, fn acc ->
    {:cont, Kino.Image.new(Picam.next_frame(), :jpeg), acc}
    end)
    ```