input_text = Kino.Input.textarea("Put your data here")
input_text =
case Kino.Input.read(input_text) do
"" ->
"""
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi
"""
input ->
input
end
input =
input_text
|> String.split("\n", trim: true)
|> Enum.map(&String.to_charlist/1)
|> Enum.reduce({%{}, 0}, fn row, {map, row_num} ->
{map, _} =
Enum.reduce(row, {map, 0}, fn value, {map, col_num} ->
{Map.put(map, {row_num, col_num}, {value, nil}), col_num + 1}
end)
{map, row_num + 1}
end)
|> then(&elem(&1, 0))
defmodule Renderer do
def render(input, width, height, visited, frame) do
images =
for x <- 0..(height - 1) do
for y <- 0..(width - 1) do
case Map.has_key?(visited, {x, y}) do
true -> create_svg_visited()
_ -> create_svg_unvisited(input, x, y)
end
end
end
|> List.flatten()
grid = Kino.Layout.grid(images, columns: width, gap: 0)
Kino.Frame.render(frame, grid)
:timer.sleep(100)
end
defp create_svg_unvisited(input, x, y) do
interval = 255 / 26
code =
case Map.get(input, {x, y}) do
{?S, _} -> 255
{?E, _} -> 0
{h, _} -> round((?z - h) * interval)
end
"""
<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg">
<rect height="100" width="100" style="fill:rgb(#{code},#{code},#{code})" />
</svg>
"""
|> Kino.Image.new(:svg)
end
defp create_svg_visited() do
"""
<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg">
<rect height="100" width="100" fill="red" />
</svg>
"""
|> Kino.Image.new(:svg)
end
end
defmodule Day12 do
require Logger
def find_path(_map, {pos, {_, wc}}, {:position, end_cond}, visited, _render_options)
when pos == end_cond do
_visited = Map.put(visited, end_cond, wc)
end
def find_path(_map, {pos, {hc, wc}}, {:value, end_cond}, visited, _render_options)
when hc == end_cond do
_visited = Map.put(visited, pos, wc)
end
def find_path(_map, {_pos, {_, nil}}, _end_cond, _visited, _render_options) do
:no_path_found
end
def find_path(
map,
{k, {_hc, wc}} = current,
end_cond,
visited,
{frame, width, height} = render_options
) do
map =
map
|> update_neighbors(current, visited)
|> Enum.reduce(map, fn {k, v}, acc -> Map.put(acc, k, v) end)
|> Map.delete(k)
next =
map
|> Enum.min(fn {_k1, {_h1, w1}}, {_k2, {_h2, w2}} -> w1 < w2 end)
visited = Map.put(visited, k, wc)
if render_options do
Renderer.render(map, width, height, visited, frame)
end
find_path(map, next, end_cond, visited, render_options)
end
def update_neighbors(map, {{x, y}, value}, visited) do
[{x, y + 1}, {x + 1, y}, {x, y - 1}, {x - 1, y}]
|> Enum.reject(&Map.has_key?(visited, &1))
|> Enum.map(&update_neighbor(map, &1, value))
|> Enum.reject(&(&1 == :no_update))
end
def update_neighbor(map, pos, {hc, wc}) do
case Map.get(map, pos) do
{h, nil} when h <= hc + 1 -> {pos, {h, wc + 1}}
{h, w} when h <= hc + 1 and w < wc -> {pos, {h, wc + 1}}
_ -> :no_update
end
end
def get_path(visited, {x, y} = position, weight) do
case Map.get(visited, position) do
0 ->
position
_ ->
next_pos =
[{x, y + 1}, {x + 1, y}, {x, y - 1}, {x - 1, y}]
|> Enum.find(&(Map.get(visited, &1) == weight - 1))
[position | get_path(visited, next_pos, weight - 1)]
end
end
end
split =
input_text
|> String.split("\n", trim: true)
height = Enum.count(split)
width = String.length(Enum.at(split, 0))
frame = Kino.Frame.new()
import Day12
render_options = {frame, width, height}
{start_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?S end)
{end_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?E end)
start_value = {?a, 0}
map =
input
|> Map.put(start_pos, start_value)
|> Map.put(end_pos, {?z, nil})
Day12.find_path(map, {start_pos, start_value}, {:position, end_pos}, %{}, render_options)
# |> Enum.sort(fn {_,v1},{_,v2} -> v1 > v2 end)
|> Map.get(end_pos)
{start_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?S end)
{end_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?E end)
map =
input
|> Map.put(start_pos, {?a, nil})
|> Map.put(end_pos, {?z, 0})
|> Enum.map(fn {k, {h, w}} -> {k, {?z - h, w}} end)
|> Enum.into(%{})
Day12.find_path(map, {end_pos, start_value}, {:value, ?z - ?a}, %{}, render_options)
|> Enum.sort(fn {_, v1}, {_, v2} -> v1 > v2 end)
|> Enum.at(0)
Kino.Frame.render(frame, 2)