Powered by AppSignal & Oban Pro

--- Day 1: Secret Entrance ---

2025/day_1.livemd

— Day 1: Secret Entrance —

Mix.install([{:kino_aoc, "~> 0.1"}])

Part 1

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2025", "1", System.fetch_env!("LB_AOC_SESSION_COOKIE"))
defmodule SecretEntrance do
  @starting_pos 50
  @dial_range 0..99
  @dial_max Enum.max(@dial_range)
  @dial_min Enum.min(@dial_range)
  @dial_size Range.size(@dial_range)

  def part_1(input) do
    input
    |> String.split()
    |> Enum.map(&parse_clicks/1)
    |> Enum.reduce([@starting_pos], &map_positions_1/2)
    |> Enum.sum_by(&add_zero/1)
  end

  defp parse_clicks("L" <> clicks), do: {"L", String.to_integer(clicks)}
  defp parse_clicks("R" <> clicks), do: {"R", String.to_integer(clicks)}

  defp map_positions_1({dir, clicks}, [current_pos | _rest] = positions) do
    clicks = rem(clicks, @dial_size)
    new_position = dir |> turn_dial(current_pos, clicks) |> new_position()
    [new_position | positions]
  end

  defp turn_dial("L", current_pos, clicks), do: current_pos - clicks
  defp turn_dial("R", current_pos, clicks), do: current_pos + clicks

  defp new_position(turn) when turn < @dial_min, do: @dial_size + turn
  defp new_position(turn) when turn > @dial_max, do: abs(@dial_size - turn)
  defp new_position(turn), do: turn

  defp add_zero(0), do: 1
  defp add_zero(_), do: 0

  def part_2(input) do
    input
    |> String.split()
    |> Enum.map(&amp;parse_clicks/1)
    |> Enum.reduce([@starting_pos], &amp;map_positions_2/2)
    |> Enum.sum_by(&amp;add_zero/1)
  end

  defp map_positions_2({dir, clicks}, [current_pos | rest]) when clicks > @dial_max do
    zeros = List.duplicate(0, div(clicks, @dial_size))
    clicks = rem(clicks, @dial_size)
    positions = [current_pos | zeros] ++ rest
    map_positions_2({dir, clicks}, positions)
  end

  defp map_positions_2({dir, clicks}, [current_pos | _rest] = positions) do
    clicks = rem(clicks, @dial_size)
    turn = turn_dial(dir, current_pos, clicks)
    new_position = new_position(turn)

    if turn not in @dial_range and 0 not in [new_position, current_pos] do
      [new_position, 0] ++ positions
    else
      [new_position | positions]
    end
  end
end
test_input = "L68\nL30\nR48\nL5\nR60\nL55\nL1\nL99\nR14\nL82"

SecretEntrance.part_1(puzzle_input)

Part 2

# SecretEntrance.part_2(test_input)
SecretEntrance.part_2(puzzle_input)