diff --git a/day12.erl b/day12.erl new file mode 100644 index 0000000..9a8aaa9 --- /dev/null +++ b/day12.erl @@ -0,0 +1,88 @@ +-module(day12). + +-export([solve/1]). + +solve(Input) -> + Board = parse_board(Input), + Result = find_path(find_start(Board), find_goal(Board), Board), + {Result, none}. + +find_start({W, H, _Data} = Board) -> + [Pos] = lists:filter(fun(P) -> raw_value(Board, P) == $S end, [ {X, Y} || X <- lists:seq(0, W-1), Y <- lists:seq(0, H-1) ]), + Pos. +find_goal({W, H, _Data} = Board) -> + [Pos] = lists:filter(fun(P) -> raw_value(Board, P) == $E end, [ {X, Y} || X <- lists:seq(0, W-1), Y <- lists:seq(0, H-1) ]), + Pos. + +parse_board(Input) -> + Parsed = binary:split(Input, <<"\n">>, [global, trim_all]), + {byte_size(hd(Parsed)), length(Parsed), Input}. + +raw_value({W, _H, Data}, {X, Y}) -> + Pos = Y * (W + 1) + X, + <<_Before:Pos/binary, RawValue, _After/binary>> = Data, + RawValue. + +value(Board, Pos) -> + case raw_value(Board, Pos) of + $S -> $a; + $E -> $z; + Letter -> Letter + end. + +valid_position({_W, _H, _Data}, {X, Y}) when X < 0 orelse Y < 0 -> false; +valid_position({W, H, _Data}, {X, Y}) when X >= W orelse Y >= H -> false; +valid_position({_W, _H, _Data}, {_X, _Y}) -> true. + +valid_move(State, {StartX, StartY}=Start, {MoveX, MoveY} = Move) -> + StartValue = value(State, Start), + MoveValue = value(State, Move), + if + abs(StartX - MoveX) + abs(StartY - MoveY) > 1 -> false; + MoveValue - StartValue > 1 -> false; + true -> true + end. + +neighbors(State, Pos) -> + PossibleNeighbors0 = [ vec_add(Pos, Dir) || Dir <- [{0,-1}, {0, 1}, {1, 0}, {-1, 0}]], + PossibleNeighbors1 = lists:filter(fun(Move) -> valid_position(State, Move) end, PossibleNeighbors0), + lists:filter(fun(Move) -> valid_move(State, Pos, Move) end, PossibleNeighbors1). + +vec_add({X1, Y1}, {X2, Y2}) -> {X1+X2, Y1+Y2}. +vec_sub({X1, Y1}, {X2, Y2}) -> {X1-X2, Y1-Y2}. +vec_norm({X1, Y1}) -> abs(X1)+abs(Y1). + +cartesian(P1, P2) -> vec_norm(vec_sub(P1, P2)). + +enqueue({_Priority, _Distance, _Parent, Pos}, {Map, Priq}) when is_map_key(Pos, Map) -> {Map, Priq}; +enqueue({Priority, Distance, Parent, Pos}, {Map, Priq}) -> + {Map#{Pos => {Distance, Parent}}, priq:insert({Priority, Pos}, Priq)}. + +dequeue({_Map, empty}) -> error; +dequeue({Map, Priq}) -> + {ok, {Priority, Pos}} = priq:peek_min(Priq), + {ok, Rest} = priq:delete_min(Priq), + #{Pos := {Distance, Parent}} = Map, + {ok, {Priority, Distance, Parent, Pos}, {Map, Rest}}. + +find_path(Start, Goal, Board) -> + a_star({cartesian(Start, Goal), 0, start, Start}, Goal, {#{}, empty}, #{}, Board). + +% Closed: A map of Node -> Node (parent). Used to check for visitation. Forms the path. +a_star({_Heuristic, Distance, _Parent, Goal}, Goal, _Open, _Closed, _State) -> + Distance; +a_star({_Heuristic, _Distance, _Parent, Curr}, Goal, Open, Closed, State) when is_map_key(Curr, Closed) -> + case dequeue(Open) of + error -> {error, no_path}; + {ok, NextCurrNode, NextOpen} -> a_star(NextCurrNode, Goal, NextOpen, Closed, State) + end; +a_star({_Heuristic, Distance, Parent, Curr}, Goal, Open, Closed, State) -> + OpenNeighbors = neighbors(State, Curr), + OpenNeighborNodes = lists:map(fun(Neighbor) -> {cartesian(Neighbor, Goal)+Distance+1, Distance+1, Curr, Neighbor} end, OpenNeighbors), + AddedOpen = lists:foldl(fun enqueue/2, Open, OpenNeighborNodes), + case dequeue(AddedOpen) of + error -> {error, no_path}; + {ok, NextCurrNode, NextOpen} -> a_star(NextCurrNode, Goal, NextOpen, Closed#{Curr=>{Distance, Parent}}, State) + end. + +%calculate_path(Goal, Closed) -> {Goal, Closed}.