AdventOfCode/2018/day9/day9.exs

103 lines
2.7 KiB
Elixir

defmodule Marble do
defstruct [:cw, :ccw]
end
defmodule Game do
defstruct [:current, :next, :circle, :players, :active, :scores, :marbles]
def start(players, marbles) do
game = %Game{
current: 0,
next: 1,
circle: %{0 => %Marble{ccw: 0, cw: 0}},
players: players,
active: 1,
scores: Map.new(1..players, &{&1, 0}),
marbles: marbles
}
play(game)
end
def next_turn(%Game{next: next, marbles: marbles} = game) when next > marbles do
game.scores |> Map.values() |> Enum.max()
end
def next_turn(%Game{players: players, active: active} = game) do
next = active + 1
next = if next <= players, do: next, else: 1
play(%Game{game | active: next})
end
def insert(circle, val, ccw, cw) do
circle
|> Map.put(val, %Marble{ccw: ccw, cw: cw})
|> Map.update!(ccw, &%Marble{&1 | cw: val})
|> Map.update!(cw, &%Marble{&1 | ccw: val})
end
def remove(circle, val, ccw, cw) do
circle
|> Map.delete(val)
|> Map.update!(ccw, &%Marble{&1 | cw: cw})
|> Map.update!(cw, &%Marble{&1 | ccw: ccw})
end
def find_remove(cw, circle, 1) do
remove = circle[cw].ccw
ccw = circle[remove].ccw
{remove, ccw, cw}
end
def find_remove(current, circle, dec) do
find_remove(circle[current].ccw, circle, dec - 1)
end
def play(%Game{current: current, next: val, circle: circle} = game) when rem(val, 23) == 0 do
# Find and remove the item 7 items over
{remove, ccw, cw} = find_remove(current, circle, 7)
circle = remove(circle, remove, ccw, cw)
# Update the player's score
scores = Map.update!(game.scores, game.active, &(&1 + val + remove))
game = %Game{game | current: cw, next: val + 1, circle: circle, scores: scores}
next_turn(game)
end
def play(%Game{current: current, next: val, circle: circle} = game) do
one = circle[current].cw
two = circle[one].cw
game = %Game{game | current: val, next: val + 1, circle: insert(circle, val, one, two)}
next_turn(game)
end
end
defmodule Day9 do
def part1 do
input = File.read!("input")
pattern = ~r/(\d+) players; last marble is worth (\d+) points/
[players, marbles] =
Regex.run(pattern, input, capture: :all_but_first)
|> Enum.map(&String.to_integer/1)
Game.start(players, marbles)
end
# Probably supposed to make this more efficient, but it still runs in 30 seconds...
def part2 do
input = File.read!("input")
pattern = ~r/(\d+) players; last marble is worth (\d+) points/
[players, marbles] =
Regex.run(pattern, input, capture: :all_but_first)
|> Enum.map(&String.to_integer/1)
Game.start(players, marbles * 100)
end
end
IO.puts(Day9.part1())
IO.puts(Day9.part2())