Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 16

day16.livemd

Day 16

Mix.install([
  {:kino, "~> 0.7.0"}
])

IEx.Helpers.c("/Users/johnb/dev/2023adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/03/04/' > day04.livemd`
#

Installation and Data

input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_p1example)) ||
    Kino.Input.read(input_p1puzzleInput)
end

Part 1

defmodule Day16 do
  def grid_cells(grid) do
    0..grid.last_cell
  end

  def grid_rows(grid) do
    grid_cells(grid)
    |> Enum.chunk_every(grid.grid_width)
  end

  def grid_x(grid, cell), do: rem(cell, grid.grid_width)
  def grid_y(grid, cell), do: div(cell, grid.grid_width)

  def to_text_grid(grid) do
    grid_rows(grid)
    |> Enum.map(fn row ->
      Enum.map(row, fn x -> grid[x] end)
      |> Enum.join("")
    end)
    |> Enum.join("\n")
  end

  def invert(grid) do
    grid_cells(grid)
    |> Enum.reduce(
      %{
        grid
        | grid_width: grid.grid_height,
          grid_height: grid.grid_width,
          last_cell: grid.last_cell
      },
      fn cell, acc ->
        Map.put(acc, grid_x(grid, cell) * grid.grid_height + grid_y(grid, cell), grid[cell])
      end
    )
  end

  def display_grid(grid, text \\ nil) do
    text && IO.puts("\n--- #{text}")

    0..grid.last_cell
    |> Enum.chunk_every(grid.grid_width)
    # |> IO.inspect(label: "Grid chunks")
    |> Enum.map(fn indexes ->
      indexes
      |> Enum.map(fn index ->
        # For a known-printable grid:
        grid[index]
        # For a somewhat-printable grid:
        # (grid[index] >= @max_display) && "." || (@ascii_zero + grid[index])
      end)
      |> Enum.join("")
      |> IO.puts()
    end)

    grid
  end

  def on_edge_of_board?(grid, cell_id, direction) do
    case direction do
      :north -> grid_y(grid, cell_id) == 0
      :west -> grid_x(grid, cell_id) == 0
      :south -> grid_y(grid, cell_id) == grid.grid_height - 1
      :east -> grid_x(grid, cell_id) == grid.grid_width - 1
    end
  end

  def build_compass(grid) do
    %{
      north: -grid.grid_width,
      east: 1,
      south: grid.grid_width,
      west: -1
    }
  end

  # TODO: add the above ^^^ to AOC

  def next_cell(grid, {cell_id, direction}) do
    if on_edge_of_board?(grid, cell_id, direction) do
      # this beam falls off edge of world
      nil
    else
      compass = build_compass(grid)
      cell2 = cell_id + compass[direction]

      case {grid[cell2], direction} |> IO.inspect() do
        {"/", :east} -> {cell2, :north}
        {"/", :west} -> {cell2, :south}
        {"\\", :east} -> {cell2, :south}
        {"\\", :west} -> {cell2, :north}
        {"-", :south} -> [{cell2, :east}, {cell2, :west}]
        {"-", :north} -> [{cell2, :east}, {cell2, :west}]
        {"|", :east} -> [{cell2, :north}, {cell2, :south}]
        {"|", :west} -> [{cell2, :north}, {cell2, :south}]
        _ -> {cell2, direction}
      end
    end
  end

  def solve(text) do
    grid = AOC.as_grid(text)
    visited = MapSet.new()

    Stream.cycle([1, 2])
    |> Enum.reduce_while({[{0, :east}], visited}, fn _x, {beams, visits} ->
      if beams == [] do
        {:halt, visits}
      else
        {:cont,
         beams
         |> Enum.reduce({[], visits}, fn beam, {beams2, visits2} ->
           if MapSet.member?(visits2, beam) do
             # same cell and direction - this beam is done
             {beams2, visits2}
           else
             # does this beam have a future? is next step off board?
             beam3 = next_cell(grid, beam)

             {case beam3 do
                nil -> beams2
                {cell_id, dir} -> [{cell_id, dir}] ++ beams2
                [_a, _b] = pair -> pair ++ beams2
              end, MapSet.put(visits2, beam)}
           end
           |> IO.inspect()
         end)}
      end
    end)
  end

  def solve2(text) do
    text
  end
end

p1data.()
|> Day16.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 46)")

#

# p1data.()
# |> Day16.solve2()
# |> IO.inspect(label: "\n*** Part 2 solution (example: )")

#