https://adventofcode.com/2021/day/11
Mix.install([:kino, :vega_lite])
input = Kino.Input.textarea("Please input data:")
data =
input
|> Kino.Input.read()
|> String.split("\n", trim: true)
|> Enum.map(&String.to_charlist/1)
|> Enum.map(fn line ->
Enum.map(line, fn char -> List.to_integer([char]) end)
end)
coords =
data
|> Enum.with_index()
|> Enum.map(fn {line, row_idx} ->
Enum.with_index(line)
|> Enum.map(fn {col, col_idx} ->
{{col_idx, row_idx}, col}
end)
end)
|> Enum.concat()
|> Enum.into(%{})
defmodule Octopus do
defp update({coords, flashes}, {x, y} = key) do
case coords[key] do
nil ->
{coords, flashes}
height when height >= 9 ->
{%{coords | key => 0}, flashes + 1}
# diagonals
|> update({x - 1, y - 1})
|> update({x + 1, y + 1})
|> update({x - 1, y + 1})
|> update({x + 1, y - 1})
# vertical
|> update({x, y - 1})
|> update({x, y + 1})
# horizontal
|> update({x - 1, y})
|> update({x + 1, y})
height when height == 0 ->
{coords, flashes}
height ->
{%{coords | key => height + 1}, flashes}
end
end
defp increment_9s({coords, flashes}) do
max_x = Map.keys(coords) |> Enum.map(&elem(&1, 0)) |> Enum.max()
max_y = Map.keys(coords) |> Enum.map(&elem(&1, 1)) |> Enum.max()
for x <- 0..max_x, y <- 0..max_y, coords[{x, y}] > 9, reduce: {coords, flashes} do
{coords, flashes} ->
update({coords, flashes}, {x, y})
end
end
def next(coords, flashes \\ 0) do
{coords, flashes}
|> then(fn {coords, flashes} ->
{Map.map(coords, fn {_point, h} -> h + 1 end), flashes}
end)
|> increment_9s()
end
# part 1
def flash(coords, steps \\ 100) do
Enum.reduce(1..steps, {coords, 0}, fn _step, {coords, flashes} ->
next(coords, flashes)
end)
end
# part 2
def synchronize(coords) do
# infinite stream counting from 1
Stream.unfold(1, &{&1, &1 + 1})
|> Enum.reduce_while({coords, 0}, fn step, {coords, flashes} ->
{new_coords, _flashes} = new_state = next(coords, flashes)
if Enum.all?(new_coords, &match?({_point, 0}, &1)) do
{:halt, step}
else
{:cont, new_state}
end
end)
end
end
Octopus.flash(coords, 100)
|> elem(1)
Octopus.synchronize(coords)
alias VegaLite, as: Vl
graph =
Vl.new(height: 100, width: 100)
|> Vl.mark(:circle, size: 60, opacity: 0.8)
|> Vl.encode_field(:x, "x", type: :quantitative, axis: false)
|> Vl.encode_field(:y, "y", type: :quantitative, axis: false)
|> Vl.encode_field(:color, "h",
type: :quantitative,
scale: [domain: [0, 9], range: ["yellow", "red", "brown"]]
)
|> Vl.encode_field(:tooltip, "h", format_type: "number")
|> Kino.VegaLite.new()
|> Kino.render()
Kino.VegaLite.periodically(
graph,
100,
{coords, 1},
fn {coords, t} ->
{coords, _flashes} = Octopus.next(coords)
data =
Enum.map(coords, fn {{x, y}, h} ->
%{"x" => x, "y" => y, "h" => h}
end)
Kino.VegaLite.clear(graph)
Kino.VegaLite.push_many(graph, data)
if t < 256 do
{:cont, {coords, t + 1}}
else
:halt
end
end
)