You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

74 lines
3.2 KiB

-module(day8).
-export([solve/1]).
solve(Input) ->
Grid = parse_grid(Input),
{map_size(visible_trees(Grid)), scenery(Grid)}.
scenery({_, H, W} = Grid) ->
Visible0 = #{},
%Left
Visible1 = range_fold(0, H-1, fun(Row, Acc) -> element(2, grid_traverse(fun scenery_score/3, {[], Acc}, Grid, {0, Row}, {1, 0})) end, Visible0),
%Right
Visible2 = range_fold(0, H-1, fun(Row, Acc) -> element(2, grid_traverse(fun scenery_score/3, {[], Acc}, Grid, {W-1, Row}, {-1, 0})) end, Visible1),
%Top
Visible3 = range_fold(0, W-1, fun(Col, Acc) -> element(2, grid_traverse(fun scenery_score/3, {[], Acc}, Grid, {Col, 0}, {0, 1})) end, Visible2),
%Bottom
Visible4 = range_fold(0, W-1, fun(Col, Acc) -> element(2, grid_traverse(fun scenery_score/3, {[], Acc}, Grid, {Col, H-1}, {0, -1})) end, Visible3),
lists:max(maps:values(Visible4)).
visible_trees({_, H, W} = Grid) ->
%top
Visible0 = #{},
%Left
Visible1 = range_fold(0, H-1, fun(Row, Acc) -> element(2, grid_traverse(fun visible_path/3, {-1, Acc}, Grid, {0, Row}, {1, 0})) end, Visible0),
%Right
Visible2 = range_fold(0, H-1, fun(Row, Acc) -> element(2, grid_traverse(fun visible_path/3, {-1, Acc}, Grid, {W-1, Row}, {-1, 0})) end, Visible1),
%Top
Visible3 = range_fold(0, W-1, fun(Col, Acc) -> element(2, grid_traverse(fun visible_path/3, {-1, Acc}, Grid, {Col, 0}, {0, 1})) end, Visible2),
%Bottom
range_fold(0, W-1, fun(Col, Acc) -> element(2, grid_traverse(fun visible_path/3, {-1, Acc}, Grid, {Col, H-1}, {0, -1})) end, Visible3).
visible_path(Tree, Position, {MaxHeight, Visible}) when Tree > MaxHeight -> {Tree, Visible#{Position => []}};
visible_path(_Tree, _Position, State) -> State.
% horrible name, calculate the view distance in one direction
tree_stack(_Tree, []) -> 0;
tree_stack(Tree, [Other|_Rest]) when Other >= Tree -> 1;
tree_stack(Tree, [_Other|Rest]) -> tree_stack(Tree, Rest) + 1.
scenery_score(Tree, Position, {TreeStack, Trees}) ->
Distance = tree_stack(Tree, TreeStack),
NewTrees = Trees#{Position => Distance * maps:get(Position, Trees, 1)},
{[Tree|TreeStack], NewTrees}.
grid_traverse(Fun, AccIn, Grid, {XPos, YPos}, {XVec, YVec}) ->
case grid_at(Grid, XPos, YPos) of
error -> AccIn;
{ok, Item} -> grid_traverse(Fun, Fun(Item, {XPos, YPos}, AccIn), Grid, {XPos+XVec, YPos+YVec}, {XVec, YVec})
end.
parse_grid(Data) ->
{W, H} = dimensions(Data),
{Data, H, W}.
dimensions(Grid) -> dimensions(Grid, 1, 0).
dimensions(<<>>, Height, Width) -> {Width, Height};
dimensions(<<$\n>>, Height, Width) -> {Width, Height};
dimensions(<<$\n, Rest/binary>>, Height, _Width) ->
dimensions(Rest, Height+1, 0);
dimensions(<<_Digit, Rest/binary>>, Height, Width) ->
dimensions(Rest, Height, Width+1).
grid_at({_Data, _H, _W}, X, Y) when X < 0 orelse Y < 0 -> error;
grid_at({_Data, H, W}, X, Y) when X >= W orelse Y >= H -> error;
grid_at({Data, _H, W}, X, Y) ->
Index = X + Y * (W+1),
<<_:Index/binary, Tree, _/binary>> = Data,
{ok, Tree-$0}.
range_fold(End, End, Fun, Acc) -> Fun(End, Acc);
range_fold(Start, End, Fun, Acc) when Start < End -> range_fold(Start+1, End, Fun, Fun(Start, Acc));
range_fold(Start, End, Fun, Acc) when Start > End -> range_fold(Start-1, End, Fun, Fun(Start, Acc)).