Regolith Reservoir
Mix.install([
{:kino, "~> 0.8.0"},
{:kino_vega_lite, "~> 0.1.7"},
{:vega_lite, "~> 0.1.6"}
])
alias VegaLite, as: Vl
:ok
Input
input = Kino.Input.textarea("Input:")
input =
Kino.Input.read(input)
|> String.trim()
|> String.split("\n")
|> Enum.map(fn s ->
String.split(s, " -> ", trim: true)
|> Enum.map(fn t ->
String.split(t, ",") |> Enum.map(&String.to_integer/1)
end)
|> Enum.map(&List.to_tuple/1)
end)
create_grid = fn input ->
Enum.reduce(input, %{{500, 0} => :hole}, fn path, acc ->
Enum.chunk_every(path, 2, 1, :discard)
|> Enum.reduce(acc, fn
[{x0, y}, {x1, y}], acc when x0 <= x1 ->
Enum.reduce(x0..x1, acc, &Map.put(&2, {&1, y}, :rock))
[{x0, y}, {x1, y}], acc when x0 > x1 ->
Enum.reduce(x0..x1//-1, acc, &Map.put(&2, {&1, y}, :rock))
[{x, y0}, {x, y1}], acc when y0 <= y1 ->
Enum.reduce(y0..y1, acc, &Map.put(&2, {x, &1}, :rock))
[{x, y0}, {x, y1}], acc when y0 > y1 ->
Enum.reduce(y0..y1//-1, acc, &Map.put(&2, {x, &1}, :rock))
end)
end)
end
grid = create_grid.(input)
:ok
render = fn grid, {{x_min, x_max}, {y_min, y_max}} ->
for y <- y_min..y_max do
line = ""
line =
for x <- x_min..x_max do
case Map.get(grid, {x, y}) do
nil -> ?.
:hole -> ?+
:rock -> ?#
:sand -> ?o
end
end
IO.puts(line)
end
:ok
end
bounds = Physics.bounds(grid)
render.(grid, bounds)
defmodule Physics do
def drop(sand = {_x, y}, grid, y_max) when y <= y_max do
case next(sand, grid) do
{:cont, sand} -> drop(sand, grid, y_max)
{:halt, sand} -> {:cont, sand}
end
end
def drop(_sand, _grid, _y_max) do
:halt
end
def next(_sand = {x, y}, grid) do
cond do
Map.get(grid, {x, y + 1}) == nil -> {:cont, {x, y + 1}}
Map.get(grid, {x - 1, y + 1}) == nil -> {:cont, {x - 1, y + 1}}
Map.get(grid, {x + 1, y + 1}) == nil -> {:cont, {x + 1, y + 1}}
true -> {:halt, {x, y}}
end
end
def bounds(grid) do
{{{x_min, _}, _}, {{x_max, _}, _}} = Enum.min_max_by(grid, fn {{x, _}, _} -> x end)
{{{_, y_min}, _}, {{_, y_max}, _}} = Enum.min_max_by(grid, fn {{_, y}, _} -> y end)
{{x_min, x_max}, {y_min, y_max}}
end
def pour(grid) do
{{_, _}, {_, y_max}} = bounds(grid)
Stream.iterate(1, &(&1 + 1))
|> Enum.reduce_while(grid, fn count, grid ->
sand = {500, 0}
case drop(sand, grid, y_max) do
:halt -> {:halt, {count - 1, grid}}
# part 2
{:cont, _sand = {500, 0}} -> {:halt, {count, grid}}
# part 1
{:cont, sand} -> {:cont, Map.put(grid, sand, :sand)}
end
end)
end
end
Part 1
{count, grid} = Physics.pour(grid)
render.(grid, bounds)
count
Part 2
add_floor = fn grid ->
{{x_min, x_max}, {y_min, y_max}} = Physics.bounds(grid)
height = y_max - y_min
Enum.reduce((x_min - height)..(x_max + height), grid, fn x, grid ->
Map.put(grid, {x, y_max + 2}, :rock)
end)
end
grid = create_grid.(input)
grid = add_floor.(grid)
bounds = Physics.bounds(grid)
render.(grid, bounds)
{count, grid} = Physics.pour(grid)
render.(grid, bounds)
count