Day 9: Rope Bridge
Mix.install([{:kino, "~> 0.7.0"}])
Day 9
sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule SimpleRope do
defstruct head: {0, 0}, tail: {0, 0}, tail_log: MapSet.new([{0, 0}])
def move(%{head: {x, y}} = rope, "U"), do: move_tail(%{rope | head: {x, y + 1}})
def move(%{head: {x, y}} = rope, "D"), do: move_tail(%{rope | head: {x, y - 1}})
def move(%{head: {x, y}} = rope, "L"), do: move_tail(%{rope | head: {x - 1, y}})
def move(%{head: {x, y}} = rope, "R"), do: move_tail(%{rope | head: {x + 1, y}})
defp move_tail(%{tail: {x, y}} = rope) do
new_tail =
case differential(rope) do
{2, 0} -> {x + 1, y}
{-2, 0} -> {x - 1, y}
{0, 2} -> {x, y + 1}
{0, -2} -> {x, y - 1}
{2, 1} -> {x + 1, y + 1}
{-2, 1} -> {x - 1, y + 1}
{1, 2} -> {x + 1, y + 1}
{1, -2} -> {x + 1, y - 1}
{2, -1} -> {x + 1, y - 1}
{-2, -1} -> {x - 1, y - 1}
{-1, 2} -> {x - 1, y + 1}
{-1, -2} -> {x - 1, y - 1}
_ -> {x, y}
end
%{rope | tail: new_tail, tail_log: MapSet.put(rope.tail_log, new_tail)}
end
defp differential(%{head: {hx, hy}, tail: {tx, ty}}), do: {hx - tx, hy - ty}
end
parse_input = fn input ->
input
|> Kino.Input.read()
|> String.split("\n")
|> Enum.map(fn <> <> " " <> distance ->
1..String.to_integer(distance) |> Enum.map(fn _ -> direction end)
end)
|> List.flatten()
end
apply_simple = fn input ->
input
|> parse_input.()
|> Enum.reduce(%SimpleRope{}, fn move, rope -> SimpleRope.move(rope, move) end)
end
apply_simple.(sample_input) |> then(fn rope -> MapSet.size(rope.tail_log) end)
apply_simple.(real_input) |> then(fn rope -> MapSet.size(rope.tail_log) end)
defmodule Rope do
defstruct knots: %{}, size: 0, tail_log: MapSet.new([{0, 0}])
def new(size) do
knots = Enum.into(0..(size - 1), %{}, fn index -> {index, {0, 0}} end)
%__MODULE__{knots: knots, size: size}
end
def move(%{knots: %{0 => {x, y}}} = rope, "U"),
do: propagate_moves(%{rope | knots: Map.put(rope.knots, 0, {x, y + 1})})
def move(%{knots: %{0 => {x, y}}} = rope, "D"),
do: propagate_moves(%{rope | knots: Map.put(rope.knots, 0, {x, y - 1})})
def move(%{knots: %{0 => {x, y}}} = rope, "L"),
do: propagate_moves(%{rope | knots: Map.put(rope.knots, 0, {x - 1, y})})
def move(%{knots: %{0 => {x, y}}} = rope, "R"),
do: propagate_moves(%{rope | knots: Map.put(rope.knots, 0, {x + 1, y})})
defp propagate_moves(rope, prev_index \\ 0)
defp propagate_moves(%{size: size} = rope, prev_index) when prev_index >= size - 1,
do: %{rope | tail_log: MapSet.put(rope.tail_log, rope.knots[prev_index])}
defp propagate_moves(rope, prev_index) do
next_index = prev_index + 1
{x, y} = rope.knots[next_index]
next_knot =
case differential(rope, prev_index) do
{2, 0} -> {x + 1, y}
{-2, 0} -> {x - 1, y}
{0, 2} -> {x, y + 1}
{0, -2} -> {x, y - 1}
{2, diff_y} when diff_y > 0 -> {x + 1, y + 1}
{-2, diff_y} when diff_y > 0 -> {x - 1, y + 1}
{diff_x, 2} when diff_x > 0 -> {x + 1, y + 1}
{diff_x, -2} when diff_x > 0 -> {x + 1, y - 1}
{2, diff_y} when diff_y < 0 -> {x + 1, y - 1}
{-2, diff_y} when diff_y < 0 -> {x - 1, y - 1}
{diff_x, 2} when diff_x < 0 -> {x - 1, y + 1}
{diff_x, -2} when diff_x < 0 -> {x - 1, y - 1}
_ -> {x, y}
end
propagate_moves(%{rope | knots: Map.put(rope.knots, next_index, next_knot)}, next_index)
end
defp differential(%{knots: knots}, prev_index) do
{hx, hy} = knots[prev_index]
{tx, ty} = knots[prev_index + 1]
{hx - tx, hy - ty}
end
end
apply_input = fn input, rope_length ->
input
|> parse_input.()
|> Enum.reduce(Rope.new(rope_length), fn move, rope -> Rope.move(rope, move) end)
end
apply_input.(sample_input, 2) |> then(fn rope -> MapSet.size(rope.tail_log) end)
apply_input.(real_input, 2) |> then(fn rope -> MapSet.size(rope.tail_log) end)
larger_sample_input = Kino.Input.textarea("Paste Larger Sample")
apply_input.(larger_sample_input, 10) |> then(fn rope -> MapSet.size(rope.tail_log) end)
apply_input.(real_input, 10) |> then(fn rope -> MapSet.size(rope.tail_log) end)