1
0
Fork 0
advent-of-code/2021/lib/2021/4.ex
2022-09-21 09:19:53 -04:00

154 lines
3.4 KiB
Elixir

import AOC
defmodule BingoPlayer do
use GenServer
defstruct [:board, :uncalled_numbers, :positions]
@impl GenServer
def init(board) do
numbers =
board
|> List.flatten()
|> MapSet.new()
{board, positions} =
for {row, x} <- Enum.with_index(board), {cell, y} <- Enum.with_index(row) do
{{{x, y}, {cell, false}}, {cell, {x, y}}}
end
|> Enum.unzip()
board = Map.new(board)
positions = Map.new(positions)
state = %__MODULE__{
board: board,
uncalled_numbers: numbers,
positions: positions
}
{:ok, state}
end
def call_number(pid, n) do
GenServer.call(pid, {:number_called, n})
end
@impl GenServer
def handle_call(
{:number_called, n},
_from,
%__MODULE__{board: board, uncalled_numbers: uncalled_numbers, positions: positions} =
state
) do
if MapSet.member?(uncalled_numbers, n) do
uncalled_numbers = MapSet.delete(uncalled_numbers, n)
board = Map.put(board, Map.get(positions, n), {n, true})
state = %{state | uncalled_numbers: uncalled_numbers, board: board}
if is_winning_board?(board) do
# Prevent winning boards from continuing to play
uncalled_numbers = MapSet.new()
state = %{state | uncalled_numbers: uncalled_numbers}
{:reply, {:win, score_winning_board(board, n)}, state}
else
{:reply, :hit, state}
end
else
{:reply, :noop, state}
end
end
defp is_winning_board?(board) do
Enum.any?(0..4, fn x ->
Enum.all?(0..4, fn y ->
Map.get(board, {x, y}) |> then(&elem(&1, 1))
end)
end) or
Enum.any?(0..4, fn y ->
Enum.all?(0..4, fn x ->
Map.get(board, {x, y}) |> then(&elem(&1, 1))
end)
end)
end
defp score_winning_board(board, last_called) do
sum =
board
|> Map.values()
|> Enum.reject(&elem(&1, 1))
|> Enum.map(&elem(&1, 0))
|> Enum.sum()
sum * last_called
end
end
aoc 2021, 4 do
def parse_input() do
[calls | boards] = input_string() |> String.split("\n\n", trim: true)
calls = String.split(calls, ",", trim: true) |> Enum.map(&String.to_integer/1)
boards =
for board <- boards do
for row <- String.split(board, "\n") do
String.split(row, ~r/\W+/, trim: true)
|> Enum.map(&String.to_integer/1)
end
end
{calls, boards}
end
def p1 do
{calls, boards} = parse_input()
players =
for board <- boards do
{:ok, pid} = GenServer.start_link(BingoPlayer, board)
pid
end
{:halt, score} =
for call <- calls, player <- players, reduce: :cont do
{:halt, _} = resp ->
resp
:cont ->
case BingoPlayer.call_number(player, call) do
{:win, score} -> {:halt, score}
_ -> :cont
end
end
Enum.each(players, &GenServer.stop/1)
score
end
def p2 do
{calls, boards} = parse_input()
players =
for board <- boards do
{:ok, pid} = GenServer.start_link(BingoPlayer, board)
pid
end
[last_winning_score | _scores] =
for call <- calls, player <- players, reduce: [] do
acc ->
case BingoPlayer.call_number(player, call) do
{:win, 0} -> acc
{:win, score} -> [score | acc]
_ -> acc
end
end
Enum.each(players, &GenServer.stop/1)
last_winning_score
end
end