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

Day 10

day10.livemd

Day 10

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 Day10 do
  def display_grid(grid, text \\ nil) do
    text && IO.puts("--- #{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)

    # |> IO.puts()
  end

  def move_up(grid, path, up) do
    # IO.inspect(path, label: "up to #{up} #{grid[up]}")

    case grid[up] do
      "S" -> path
      "|" -> move_up(grid, [up | path], up - grid.grid_width)
      "7" -> move_left(grid, [up | path], up - 1)
      "F" -> move_right(grid, [up | path], up + 1)
      _ -> nil
    end
  end

  def move_left(grid, path, left) do
    # IO.inspect(path, label: "left to #{left} #{grid[left]}")

    case grid[left] do
      "S" -> path
      "-" -> move_left(grid, [left | path], left - 1)
      "L" -> move_up(grid, [left | path], left - grid.grid_width)
      "F" -> move_down(grid, [left | path], left + grid.grid_width)
      _ -> nil
    end
  end

  def move_right(grid, path, right) do
    # IO.inspect(path, label: "right to #{right} #{grid[right]}")

    case grid[right] do
      "S" -> path
      "-" -> move_right(grid, [right | path], right + 1)
      "7" -> move_down(grid, [right | path], right + grid.grid_width)
      "J" -> move_up(grid, [right | path], right - grid.grid_width)
      _ -> nil
    end
  end

  def move_down(grid, path, down) do
    # IO.inspect(path, label: "down to #{down} #{grid[down]}")

    case grid[down] do
      "S" -> path
      "|" -> move_down(grid, [down | path], down + grid.grid_width)
      "L" -> move_right(grid, [down | path], down + 1)
      "J" -> move_left(grid, [down | path], down - 1)
      _ -> nil
    end
  end

  def solve(text) do
    grid = AOC.as_grid(text)
    # IO.puts(AOC.display_grid(grid))

    start =
      0..grid.last_cell
      |> Enum.find(fn cell -> grid[cell] == "S" end)

    path =
      move_up(grid, [start], start - grid.grid_width) ||
        (rem(start, grid.grid_width) != 0 && move_left(grid, [start], start - 1)) ||
        (rem(start + 1, grid.grid_width) != 0 && move_right(grid, [start], start + 1)) ||
        move_down(grid, [start], start + grid.grid_width)

    # IO.inspect([grid.grid_width, path], label: "path")
    _result = Enum.count(path) / 2
  end

  def flood_fill(grid, cell_id, char) do
    compass = AOC.build_compass(grid)

    [:north, :south, :east, :west]
    |> Enum.reduce(grid, fn dir, acc ->
      case {acc[cell_id], AOC.on_edge_of_board?(acc, cell_id, dir)} do
        {"x", true} -> Map.put(acc, cell_id, char)
        {"x", false} -> Map.put(acc, cell_id, char) |> flood_fill(cell_id + compass[dir], char)
        _ -> acc
      end
      |> AOC.display_grid()
    end)
  end

  def solve2(text) do
    grid = AOC.as_grid(text)
    # IO.puts(AOC.display_grid(grid))

    start =
      0..grid.last_cell
      |> Enum.find(fn cell -> grid[cell] == "S" end)

    path =
      move_up(grid, [start], start - grid.grid_width) ||
        (rem(start, grid.grid_width) != 0 && move_left(grid, [start], start - 1)) ||
        (rem(start + 1, grid.grid_width) != 0 && move_right(grid, [start], start + 1)) ||
        move_down(grid, [start], start + grid.grid_width)

    # IO.inspect([grid.grid_width, path], label: "path")

    grid =
      AOC.grid_cells(grid)
      |> Enum.reduce(grid, fn cell_id, acc ->
        if cell_id in path do
          acc
        else
          Map.put(acc, cell_id, "x")
        end
      end)

    results =
      AOC.grid_rows(grid)
      |> Enum.reduce(0, fn row, total_inside ->
        Enum.reduce(
          row,
          %{
            num_inside: 0,
            state: "outside",
            last_turn: nil
          },
          fn cell_id, acc ->
            case {acc.state, acc.last_turn, grid[cell_id]} do
              {"outside", _, "F"} -> %{acc | state: "from_outside", last_turn: "F"}
              {"outside", _, "L"} -> %{acc | state: "from_outside", last_turn: "L"}
              {"outside", _, "|"} -> %{acc | state: "inside"}
              {"from_inside", "F", "J"} -> %{acc | state: "outside", last_turn: nil}
              {"from_inside", "F", "7"} -> %{acc | state: "inside", last_turn: nil}
              {"from_inside", "L", "J"} -> %{acc | state: "inside", last_turn: nil}
              {"from_inside", "L", "7"} -> %{acc | state: "outside", last_turn: nil}
              {"from_outside", "F", "J"} -> %{acc | state: "inside", last_turn: nil}
              {"from_outside", "F", "7"} -> %{acc | state: "outside", last_turn: nil}
              {"from_outside", "L", "J"} -> %{acc | state: "outside", last_turn: nil}
              {"from_outside", "L", "7"} -> %{acc | state: "inside", last_turn: nil}
              {"inside", _, "F"} -> %{acc | state: "from_inside", last_turn: "F"}
              {"inside", _, "L"} -> %{acc | state: "from_inside", last_turn: "L"}
              {"inside", _, "|"} -> %{acc | state: "outside"}
              {"inside", _, "x"} -> %{acc | num_inside: 1 + acc.num_inside}
              _ -> acc
            end
          end
        ).num_inside + total_inside
      end)
  end
end

# p1data.()
# |> Day10.solve()
# |> IO.inspect(label: "\n*** Part 1 solution (example: 8)")

# 6613

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

# 511