Day 17 - part2
Mix.install([{:kino, "~> 0.5.0"}])
use Bitwise
Section
input = Kino.Input.textarea("paste input here:")
jets = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"
jets = input |> Kino.Input.read() |> String.trim()
blocks = [
[{0, 0}, {1, 0}, {2, 0}, {3, 0}],
[{1, 0}, {0, 1}, {1, 1}, {2, 1}, {1, 2}],
[{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}],
[{0, 0}, {0, 1}, {0, 2}, {0, 3}],
[{0, 0}, {1, 0}, {0, 1}, {1, 1}]
]
defmodule Tetris do
def height(tower) do
tower
|> Enum.map(&elem(&1, 1))
|> Enum.max(&>=/2, fn -> 0 end)
|> Kernel.+(1)
end
def pprint({bx, by}, block, tower) do
for y <- max(height(tower), by + 4)..0 do
for x <- 0..6 do
cond do
MapSet.member?(tower, {x, y}) -> ?#
{x - bx, y - by} in block -> ?@
true -> ?\s
end
end
|> then(&('|' ++ &1 ++ '|'))
|> IO.puts()
end
IO.puts('+-------+')
end
# can be case like -----
#
# -----
# so can't just check lowest y in each column, 1000 below lowest y is hacky heuristic
# works in non-pathological cases
def wrong_elim(tower) do
new_floor =
0..6
|> Enum.map(fn x ->
for {^x, y} <- tower do
y
end
|> Enum.max(&>=/2, fn -> -1 end)
end)
|> Enum.min()
new_floor = new_floor - 1000
if new_floor >= 0 do
# IO.puts("eliminating row up to:")
# IO.inspect(new_floor)
old_floor = Process.get(:floor, 0)
Process.put(:floor, old_floor + new_floor + 1)
for {x, y} <- tower,
y > new_floor,
into: MapSet.new() do
{x, y - new_floor - 1}
end
else
tower
end
end
def elim_full_rows(tower) do
new_floor =
height(tower)..0
|> Enum.find(fn y ->
Enum.all?(0..6, &MapSet.member?(tower, {&1, y}))
end)
if new_floor do
IO.puts("eliminating row up to:")
IO.inspect(new_floor)
old_floor = Process.get(:floor, 0)
Process.put(:floor, old_floor + new_floor + 1)
for {x, y} <- tower,
y > new_floor,
into: MapSet.new() do
{x, y - new_floor - 1}
end
else
tower
end
end
def has_full_row?(tower) do
for y <- 0..height(tower) do
Enum.all?(0..6, &MapSet.member?(tower, {&1, y}))
end
|> Enum.any?()
end
def free?({x, y}, block, tower) do
# IO.puts("free?")
# IO.inspect([{x, y}, block, tower])
for {dx, dy} <- block do
{bx, by} = {x + dx, y + dy}
not MapSet.member?(tower, {bx, by}) and
bx >= 0 and bx < 7 and
by >= 0
end
|> Enum.all?()
end
def add_block({x, y}, block, tower) do
for {bx, by} <- block, reduce: tower do
tower ->
MapSet.put(tower, {x + bx, y + by})
end
end
def move(_, {tower, coord, [block | _], counter}) when counter == 1_000_000_000_000 do
pprint(coord, block, tower)
IO.inspect(has_full_row?(tower))
{:halt, nil}
end
def move({jet, i}, {tower, {x, y}, [block | blocks], counter}) do
seen = Process.get(:seen, Map.new())
hash = :erlang.phash2({i, tower, x, y, block}, 1 <<< 32)
counter =
if Map.has_key?(seen, hash) and counter <= 500_000 do
IO.puts("cycle!")
{old_counter, old_floor} = seen[hash]
new_floor = Process.get(:floor, 0)
extra_floor =
(new_floor - old_floor) * div(1_000_000_000_000 - counter, counter - old_counter)
Process.put(:floor, new_floor + extra_floor)
1_000_000_000_000 - rem(1_000_000_000_000 - counter, counter - old_counter)
else
Process.put(:seen, Map.put(seen, hash, {counter, Process.get(:floor, 0)}))
counter
end
dx = if jet == ?>, do: 1, else: -1
{x, y} =
if free?({x + dx, y}, block, tower) do
{x + dx, y}
else
{x, y}
end
if(free?({x, y - 1}, block, tower)) do
{
[height(tower)],
{tower, {x, y - 1}, [block | blocks], counter}
}
else
tower = add_block({x, y}, block, tower)
# tower = elim_full_rows(tower)
tower = wrong_elim(tower)
height = height(tower)
{
[height],
{
tower,
{2, height + 3},
blocks ++ [block],
counter + 1
}
}
end
end
end
defmodule Fast do
def fast(jets, blocks) do
jets
|> to_charlist()
|> Stream.cycle()
|> Stream.zip(Stream.cycle(1..length(to_charlist(jets))))
|> Stream.transform({MapSet.new(), {2, 3}, blocks, 0}, &Tetris.move/2)
# |> Stream.take(100000)
|> Enum.at(-1)
# |> Enum.take(100)
# |> Enum.to_list()
# |> Enum.join(" ")
# |> IO.puts()
end
end
Process.delete(:floor)
Process.delete(:seen)
Fast.fast(jets, blocks) + Process.get(:floor)