defmodule AOC.Day11 do @x_size 95 @y_size 93 def parse_input do AOC.Util.input_lines(11, 1) |> Enum.with_index() |> Enum.reduce(%{}, fn {line, y}, acc -> line |> String.split("", trim: true) |> Enum.with_index() |> Enum.reject(&(elem(&1, 0) == ".")) |> Enum.map(fn {_, i} -> {false, i} end) |> Enum.into(acc, fn {c, x} -> {{x, y}, c} end) end) end def count_direct_adj(seats, {x, y}) do [ {x - 1, y - 1}, {x - 1, y}, {x - 1, y + 1}, {x, y - 1}, {x, y + 1}, {x + 1, y - 1}, {x + 1, y}, {x + 1, y + 1} ] |> Enum.count(fn coords -> Map.get(seats, coords) end) end def look_direction(seats, coords, next_fun) do {x, y} = coords = next_fun.(coords) if x not in 0..@x_size or y not in 0..@y_size do false else case Map.get(seats, coords) do nil -> look_direction(seats, coords, next_fun) x -> x end end end def count_sight_adj(seats, {x, y}) do [ fn {x, y} -> {x + 1, y + 1} end, fn {x, y} -> {x + 1, y - 1} end, fn {x, y} -> {x - 1, y + 1} end, fn {x, y} -> {x - 1, y - 1} end, fn {x, y} -> {x + 1, y} end, fn {x, y} -> {x - 1, y} end, fn {x, y} -> {x, y + 1} end, fn {x, y} -> {x, y - 1} end ] |> Enum.count(&look_direction(seats, {x, y}, &1)) end def simulate_round(seats, count_adj, min) do Enum.reduce(seats, {%{}, false}, fn {coords, x}, {acc, changed} -> new = case {x, count_adj.(seats, coords)} do {false, 0} -> true {true, count} when count >= min -> false _ -> x end {Map.put(acc, coords, new), changed or new != x} end) end def find_stable(seats, count_adj, min) do {seats, changed} = simulate_round(seats, count_adj, min) if changed do find_stable(seats, count_adj, min) else seats end end def parts do input = parse_input() stable = find_stable(input, &count_direct_adj/2, 4) part1 = Enum.count(stable, &elem(&1, 1)) stable = find_stable(input, &count_sight_adj/2, 5) part2 = Enum.count(stable, &elem(&1, 1)) {part1, part2} end end