Day 9: Rope Bridge
Mix.install([
:kino,
:kino_vega_lite
])
Input
input = Kino.Input.textarea("Input")
Part 1
defmodule Parser do
def parse(text) do
text
|> String.split("\n")
|> Enum.map(fn s -> String.split(s, " ") end)
|> Enum.map(fn [direction, distance] ->
List.duplicate(trans_direction(direction), String.to_integer(distance))
end)
|> Enum.concat()
end
defp trans_direction(direction) do
case direction do
"R" -> :right
"D" -> :down
"L" -> :left
"U" -> :up
end
end
end
defmodule Simulator do
@direction %{left: {0, -1}, right: {0, 1}, up: {1, 0}, down: {-1, 0}}
@neighbours [{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, -1}, {-1, 1}, {1, 1}, {-1, -1}]
defstruct head: {0, 0}, tail: {0, 0}
def new() do
%__MODULE__{}
end
def adjacent?(self) do
if self.head == self.tail do
true
else
Enum.any?(@neighbours, fn {offset_row, offset_col} ->
{row, col} = self.head
{row + offset_row, col + offset_col} == self.tail
end)
end
end
def move(self, direction) do
new_self = move_head(self, direction)
if adjacent?(new_self) do
new_self
else
move_tail(new_self, self.head)
end
end
defp move_head(self, direction) do
{offset_row, offset_col} = @direction[direction]
{row, col} = self.head
%__MODULE__{self | head: {row + offset_row, col + offset_col}}
end
defp move_tail(self, position) do
%{self | tail: position}
end
end
positions =
input
|> Kino.Input.read()
|> Parser.parse()
|> Enum.scan(Simulator.new(), fn direction, position -> Simulator.move(position, direction) end)
|> Enum.map(fn position -> position.tail end)
positions
|> Enum.uniq()
|> Enum.count()
defmodule Display do
alias VegaLite, as: Vl
def display(positions) do
{y, x} = Enum.unzip(positions)
Vl.new(width: 500, height: 500)
|> Vl.data_from_values(x: x, y: y)
|> Vl.mark(:circle)
|> Vl.encode_field(:x, "x")
|> Vl.encode_field(:y, "y", sort: "-y")
|> Vl.encode_field(:size, "", value: 50)
end
end
Display.display(positions)
|> Enum.uniq() |> Enum.count()