Powered by AppSignal & Oban Pro

Day 17

2021/day-17.livemd

Day 17

Setup

Mix.install([{:kino, "~> 0.4.1"}])
example_input =
  Kino.Input.text("example input:")
  |> Kino.render()

real_input = Kino.Input.text("real input:")
[{x_range, _}, {y_range, _}] =
  real_input
  |> Kino.Input.read()
  |> String.split(["target area: ", ", ", "x=", "y="], trim: true)
  |> Enum.map(&Code.eval_string/1)

x_min = Enum.min(x_range)
vx_min = 1..x_min |> Enum.find(&(Enum.sum(1..&1) >= x_min))

y_min = Enum.min(y_range)
vy_max = -1 - y_min

Part 1

Stream.iterate({vx_min, vy_max}, fn {x, y} -> {Enum.max([x - 1, 0]), y - 1} end)
|> Enum.reduce_while({0, {0, 0}}, fn {xd, yd}, {y_max, {x, y}} ->
  {_, y} = coord = {x + xd, y + yd}
  y_max = Enum.max([y_max, y])
  if y <= Enum.min(y_range), do: {:halt, {y_max, coord}}, else: {:cont, {y_max, coord}}
end)

Part 2

defmodule Launcher do
  def hits_target?({vx, vy}, x_range, y_range) do
    Stream.iterate({vx, vy}, fn {x, y} -> {Enum.max([x - 1, 0]), y - 1} end)
    |> Enum.reduce_while({0, 0}, fn {xd, yd}, {x, y} ->
      {x, y} = coord = {x + xd, y + yd}
      in_target? = in_target?({x, y}, x_range, y_range)
      if y <= Enum.min(y_range) or in_target?, do: {:halt, in_target?}, else: {:cont, coord}
    end)
  end

  def in_target?({x, y}, x_range, y_range), do: x in x_range and y in y_range
end
for vx <- vx_min..Enum.max(x_range),
    vy <- y_min..vy_max,
    Launcher.hits_target?({vx, vy}, x_range, y_range),
    into: [] do
  {vx, vy}
end
|> Enum.count()