-module(day23). -export([solve/1]). -compile(export_all). solve(InputData) -> {part1(InputData), none}. part1(Input) -> State = parse_input(Input), EndState = step(10, State), {XMin, XMax, YMin, YMax} = Bounds = get_bounds(EndState), %io:format("~p~n", [Bounds]), (XMax - XMin + 1) * (YMax - YMin + 1) - num_elves(State). parse_input(Input) -> parse_input(Input, 0, 0, 1, {#{}, #{}}). parse_input(<<>>, _X, _Y, _NumElves, State) -> State; parse_input(<<$., Rest/binary>>, X, Y, ElfId, State) -> parse_input(Rest, X+1, Y, ElfId, State); parse_input(<<$\n, Rest/binary>>, X, Y, ElfId, State) -> parse_input(Rest, 0, Y+1, ElfId, State); parse_input(<<$#, Rest/binary>>, X, Y, ElfId, {ActiveElves, Positions}) -> parse_input(Rest, X+1, Y, ElfId+1, {ActiveElves#{ElfId => {X, Y}}, Positions#{{X, Y} => ElfId}}). num_elves({_ActiveElves, Positions}) -> map_size(Positions). board_put(X, Y, Char, {W, Data}) -> Index = Y * (W+1) + X, Before = binary_part(Data, {0, Index}), After = binary_part(Data, {Index + 1, byte_size(Data) - Index - 1}), {W, <>}. mkboard(W, H) -> mkboard(W, H, <<>>). mkboard(W, 0, Acc) -> {W, Acc}; mkboard(W, H, Acc) -> MkRow = fun MkRow(0, R) -> R; MkRow(N, R) -> MkRow(N-1, <>) end, WithRow = MkRow(W, Acc), mkboard(W, H-1, <>). render({_Active, Positions} = State) -> {MinX, MaxX, MinY, MaxY} = get_bounds(State), {_, Data} = maps:fold(fun ({X, Y}, Id, Board) -> board_put(X - MinX, Y - MinY, (Id rem 10) + $0, Board) end, mkboard(MaxX - MinX + 1, MaxY - MinY + 1), Positions), Data. step(N, State) -> step(0, N, State). step(N, N, State) -> State; step(I, N, State) when N - I > 0 -> step(I+1, N, do_step(State, I)). do_step({ActiveElves, PositionsIn}, Round) -> %io:format(" Stepping ~n", []), %io:format("~s~n", [render({ActiveElves, PositionsIn})]), {PlannedMoves, _Inactive} = plan_moves(ActiveElves, PositionsIn, Round), maps:fold(fun (NewPos, ElfId, {Active, Positions}) -> #{ElfId := OldPos} = Active, Updooted = maps:remove(OldPos, Positions), {Active#{ElfId := NewPos}, Updooted#{NewPos => ElfId}} end, {ActiveElves, PositionsIn}, PlannedMoves). plan_moves(ActiveElves, Positions, Round) -> {_Collisions, Moves, Inactive} = maps:fold( fun (ElfId, Position, {Collisions, MoveSet, Inactive}) -> Move = get_move(Position, Positions, Round), %io:format(" ~p ~p ~n", [ElfId, Position]), %io:format(" ~p ~p ~n", [ElfId, Move]), % Todo: only mark this unit as inactive if it exited because it had no neighbors (update the signature of get_move/2) if Position == Move -> {Collisions, MoveSet, Inactive#{ElfId => Position}}; is_map_key(Move, Collisions) -> #{Move := Elves} = Collisions, {Collisions#{Move:=[ElfId|Elves]}, MoveSet, Inactive}; is_map_key(Move, MoveSet) -> #{Move := CollidingElf} = MoveSet, {Collisions#{Move => [ElfId, CollidingElf]}, maps:remove(Move, MoveSet), Inactive}; true -> {Collisions, MoveSet#{Move => ElfId}, Inactive} end end, {#{}, #{}, #{}}, ActiveElves), {Moves, Inactive}. open(Position, Positions) when is_map_key(Position, Positions) -> 0; open(_Position, _Positions) -> 1. % N S W NW SW E NE SE get_move({ElfX, ElfY}, Positions, Round) -> [_|Adjacent] = [{X, Y} || X <- [ElfX, ElfX-1, ElfX+1], Y <- [ElfY, ElfY-1, ElfY+1]], OpenByte= lists:foldl(fun (Pos, Acc) -> (Acc bsl 1) + open(Pos, Positions) end, 0, Adjacent), %io:format("~.2B~n", [OpenByte]), case Round rem 4 of 0 -> if 2#11111111 == OpenByte -> {ElfX, ElfY}; 2#10010010 == (OpenByte band 2#10010010) -> {ElfX, ElfY-1}; 2#01001001 == (OpenByte band 2#01001001) -> {ElfX, ElfY+1}; 2#00111000 == (OpenByte band 2#00111000) -> {ElfX-1, ElfY}; 2#00000111 == (OpenByte band 2#00000111) -> {ElfX+1, ElfY}; true -> {ElfX, ElfY} end; 1 -> if 2#11111111 == OpenByte -> {ElfX, ElfY}; 2#01001001 == (OpenByte band 2#01001001) -> {ElfX, ElfY+1}; 2#00111000 == (OpenByte band 2#00111000) -> {ElfX-1, ElfY}; 2#00000111 == (OpenByte band 2#00000111) -> {ElfX+1, ElfY}; 2#10010010 == (OpenByte band 2#10010010) -> {ElfX, ElfY-1}; true -> {ElfX, ElfY} end; 2 -> if 2#11111111 == OpenByte -> {ElfX, ElfY}; 2#00111000 == (OpenByte band 2#00111000) -> {ElfX-1, ElfY}; 2#00000111 == (OpenByte band 2#00000111) -> {ElfX+1, ElfY}; 2#10010010 == (OpenByte band 2#10010010) -> {ElfX, ElfY-1}; 2#01001001 == (OpenByte band 2#01001001) -> {ElfX, ElfY+1}; true -> {ElfX, ElfY} end; 3 -> if 2#11111111 == OpenByte -> {ElfX, ElfY}; 2#00000111 == (OpenByte band 2#00000111) -> {ElfX+1, ElfY}; 2#10010010 == (OpenByte band 2#10010010) -> {ElfX, ElfY-1}; 2#01001001 == (OpenByte band 2#01001001) -> {ElfX, ElfY+1}; 2#00111000 == (OpenByte band 2#00111000) -> {ElfX-1, ElfY}; true -> {ElfX, ElfY} end end. mark_inactive(ElfId, {ActiveElves, Positions}) -> maps:remove(ElfId, ActiveElves). mark_active(ElfId, ElfPosition, {ActiveElves, Positions}) -> ActiveElves#{ElfId => ElfPosition}. get_bounds({_ActiveElves, Positions}) -> maps:fold(fun ({X, Y}, _Value, {MinX, MaxX, MinY, MaxY}) -> {min(X, MinX), max(X, MaxX), min(Y, MinY), max(Y, MaxY)} end, {50000000, 0, 50000000, 0}, Positions).