Took absolutely forever to debug because I forgot about the “Elf doesn’t move if no Elves around” rule :-/
Anyway here is ugly code
main(File) ->
{ok, RawData} = file:read_file(File),
InputRaw = [ [ N =:= $# || N <- binary_to_list(Line) ]
|| Line <- binary:split(RawData, <<"\n">>, [global, trim]) ],
Input = load(InputRaw),
io:format("part 1: ~p~n", [solve1(Input)]),
io:format("part 2: ~p~n", [solve2(Input)]).
load(Data) ->
maps:from_list([ {{X, Y}, N} || {Y, Line} <- lists:enumerate(Data),
{X, N} <- lists:enumerate(Line),
N ]).
solve1(Map) ->
NewMap = simulate(Map, 10),
{Xs, Ys} = lists:unzip(maps:keys(NewMap)),
(lists:max(Xs) - lists:min(Xs) + 1)
* (lists:max(Ys) - lists:min(Ys) + 1)
- maps:size(NewMap).
solve2(Map) ->
simulate(Map, -1).
simulate(Map, N) ->
Order = [
[ {X, -1} || X <- [-1, 0, 1] ],
[ {X, 1} || X <- [-1, 0, 1] ],
[ {-1, Y} || Y <- [-1, 0, 1] ],
[ { 1, Y} || Y <- [-1, 0, 1] ]
],
iteration({Map, Order}, N).
iteration({Map, _}, 0) -> Map;
iteration({Map, Order}, N) ->
Elves = maps:keys(Map),
Proposals = [ propose(E, Order, Map) || E <- Elves ],
Moves = maps:groups_from_list(fun ({To, _From}) -> To end, Proposals),
NewMap = new_map(Moves),
case N < 0 andalso NewMap =:= Map of
true -> -N;
false -> iteration({NewMap, tl(Order) ++ [hd(Order)]}, N - 1)
end.
new_map(List) ->
lists:foldl(fun new_map/2, #{}, maps:to_list(List)).
new_map({C, [_]}, Map) -> Map#{C => true};
new_map({_, [_, _|_] = List}, Map) ->
lists:foldl(fun ({_, From}, Acc) -> Acc#{From => true} end, Map, List).
propose(C, Order, Map) ->
S = lists:seq(-1, 1),
Around = [ {X, Y} || X <- S, Y <- S, {X, Y} =/= {0, 0} ],
case have_elves(C, Around, Map) of
false -> {C, C};
true -> do_propose(C, Order, Map)
end.
do_propose(C, [], _Map) -> {C, C};
do_propose(C, [O|Order], Map) ->
case have_elves(C, O, Map) of
false -> {plus(C, lists:nth(2, O)), C};
true -> do_propose(C, Order, Map)
end.
have_elves(C, List, Map) when is_list(List) ->
lists:any(fun (D) -> maps:is_key(plus(C, D), Map) end, List).
plus({X, Y}, {A, B}) ->
{X + A, Y + B}.
print(Map0) ->
Map = [ K || {K, _} <- maps:to_list(Map0) ],
MinX = lists:min([ X || {X, _} <- Map ]),
MaxX = lists:max([ X || {X, _} <- Map ]),
MinY = lists:min([ Y || {_, Y} <- Map ]),
MaxY = lists:max([ Y || {_, Y} <- Map ]),
[ print(Map, MinX, MaxX, Y) || Y <- lists:seq(MinY - 1, MaxY + 1) ],
io:format("~n").
print(Map, MinX, MaxX, Y) ->
Dots = [ X || {X, Y0} <- Map, Y0 =:= Y ],
[ case lists:member(X, Dots) of
true -> io:format("#");
false -> io:format(".")
end || X <- lists:seq(MinX, MaxX) ],
io:format("~n").