Powered by AppSignal & Oban Pro

Day 11

day_11.livemd

Day 11

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])
input = Kino.Input.textarea("Please provide your input: ")
lines =
  input
  |> Kino.Input.read()
  |> String.split("\n")

grid =
  for {line, row} <- Enum.with_index(lines),
      {level, col} <- Enum.with_index(String.to_charlist(line)),
      into: %{} do
    {{row, col}, %{level: level - ?0, state: :not_flashed}}
  end

Part 1

defmodule AdventOfCode.DayEleven do
  @baseline 0
  @flash_level 9
  @octopodes 100

  def solve_part_one(grid) do
    {_grid, flashes} =
      0..99
      |> Enum.reduce({grid, 0}, fn _step, {grid, flashes} ->
        grid =
          grid
          |> increase_all_levels()
          |> flash()

        flashes =
          flashes +
            Enum.count(grid, fn {coord, %{level: _l, state: state}} -> state == :flashed end)

        grid = grid |> reset_all()

        {grid, flashes}
      end)

    flashes
  end

  def solve_part_two(grid) do
    Stream.iterate(1, &(&1 + 1))
    |> Enum.reduce_while(grid, fn step, grid ->
      grid =
        grid
        |> increase_all_levels()
        |> flash()
        |> reset_all()

      case Enum.count(grid, fn {_c, %{level: level, state: _state}} -> level == @baseline end) do
        @octopodes -> {:halt, step}
        _ -> {:cont, grid}
      end
    end)

    # get_synchro_step(grid, 0)
  end

  def get_synchro_step(grid, step) do
    grid =
      grid
      |> increase_all_levels()
      |> flash()
      |> reset_all()

    case Enum.count(grid, fn {_c, %{level: level, state: _state}} -> level == @baseline end) do
      @octopodes -> step
      _ -> get_synchro_step(grid, step + 1)
    end
  end

  defp increase_all_levels(grid) do
    grid
    |> Enum.map(fn {coords, octo} -> {coords, increase_level(octo)} end)
    |> Enum.into(%{})
  end

  defp increase_level(octo) do
    Map.update!(octo, :level, &(&1 + 1))
  end

  defp about_to_flash(grid) do
    about_to_flash =
      grid
      |> Enum.reject(fn {_c, %{level: _level, state: state}} -> state == :flashed end)
      |> Enum.filter(fn {_c, %{level: level, state: _state}} -> level > @flash_level end)
  end

  defp flash(grid) do
    about_to_flash = about_to_flash(grid)

    if Enum.count(about_to_flash) == 0 do
      grid
    else
      about_to_flash
      |> Enum.reduce(grid, fn {{x, y} = coords, octo}, grid ->
        neighbours =
          grid
          |> Enum.filter(fn {coords, %{}} ->
            coords in [
              {x + 1, y},
              {x + 1, y + 1},
              {x + 1, y - 1},
              {x - 1, y},
              {x - 1, y + 1},
              {x - 1, y - 1},
              {x, y + 1},
              {x, y - 1}
            ]
          end)
          |> increase_all_levels()

        grid
        |> set_flashed(coords, octo)
        |> Map.merge(neighbours)
      end)
      |> flash()
    end
  end

  defp set_flashed(grid, coords, octo) do
    octo = Map.put(octo, :state, :flashed)
    Map.replace(grid, coords, octo)
  end

  defp reset_all(grid) do
    grid
    |> Enum.map(fn {coords, octo} ->
      if Map.get(octo, :state) == :flashed do
        octo = %{level: @baseline, state: :not_flashed}
        {coords, octo}
      else
        {coords, octo}
      end
    end)
    |> Enum.into(%{})
  end
end

# AdventOfCode.DayEleven.solve_part_one(grid)
AdventOfCode.DayEleven.solve_part_two(grid)