-module(gwg@pathfinding). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gwg/pathfinding.gleam"). -export([astar/3, flowfield/2]). -export_type([action/2, astarstate/2, flowfieldstate/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type action(DKX, DKY) :: {action, DKX, DKY, float()}. -type astarstate(DKZ, DLA) :: {astarstate, DKZ, DLA, float(), float()}. -type flowfieldstate(DLB) :: {flowfieldstate, DLB, float(), float()}. -file("src/gwg/pathfinding.gleam", 202). -spec lowest_f_score_state( gleam@set:set(DMC), gleam@dict:dict(DMC, astarstate(DMC, any())) ) -> {ok, DMC} | {error, nil}. lowest_f_score_state(Queue, Flowfield) -> _pipe = Queue, _pipe@1 = gleam@set:to_list(_pipe), gleam@list:max( _pipe@1, fun(A, B) -> case {gleam_stdlib:map_get(Flowfield, A), gleam_stdlib:map_get(Flowfield, B)} of {{ok, A@1}, {ok, B@1}} -> gleam@float:compare( erlang:element(5, B@1), erlang:element(5, A@1) ); {{ok, A@2}, {error, nil}} -> gleam@float:compare(+0.0, erlang:element(5, A@2)); {{error, nil}, {ok, B@2}} -> gleam@float:compare(erlang:element(5, B@2), +0.0); {{error, nil}, {error, nil}} -> eq end end ). -file("src/gwg/pathfinding.gleam", 218). -spec astar_actions_folder( list(action(DML, DMM)), DMM, float(), fun((DMM) -> float()), gleam@dict:dict(DMM, astarstate(DMM, DML)) ) -> gleam@dict:dict(DMM, astarstate(DMM, DML)). astar_actions_folder( Actions, From_state, From_g_score, Heuristic_fun, Flowfield ) -> _pipe = Actions, gleam@list:fold( _pipe, maps:new(), fun(Acc, Action) -> G_score = From_g_score + erlang:element(4, Action), case begin _pipe@1 = Flowfield, _pipe@2 = maps:merge(_pipe@1, Acc), gleam_stdlib:map_get(_pipe@2, erlang:element(3, Action)) end of {ok, Old} when erlang:element(4, Old) =< G_score -> Acc; _ -> F_score = G_score + Heuristic_fun(erlang:element(3, Action)), _pipe@3 = {astarstate, From_state, erlang:element(2, Action), G_score, F_score}, gleam@dict:insert(Acc, erlang:element(3, Action), _pipe@3) end end ). -file("src/gwg/pathfinding.gleam", 240). -spec closest_state(gleam@dict:dict(DMY, astarstate(DMY, any()))) -> {ok, DMY} | {error, nil}. closest_state(Flowfield) -> _pipe = Flowfield, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:max( _pipe@1, fun(A, B) -> {_, A@1} = A, {_, B@1} = B, gleam@float:compare( erlang:element(5, B@1) - erlang:element(4, B@1), erlang:element(5, A@1) - erlang:element(4, A@1) ) end ), gleam@result:map(_pipe@2, fun gleam@pair:first/1). -file("src/gwg/pathfinding.gleam", 260). -spec do_reconstruct_actions( DNN, gleam@dict:dict(DNN, astarstate(DNN, DNO)), list(DNO) ) -> list(DNO). do_reconstruct_actions(State, Flowfield, Actions) -> case begin _pipe = Flowfield, gleam_stdlib:map_get(_pipe, State) end of {ok, {astarstate, From_state, Action, _, _}} -> do_reconstruct_actions(From_state, Flowfield, [Action | Actions]); {error, nil} -> Actions end. -file("src/gwg/pathfinding.gleam", 253). -spec reconstruct_actions(DNG, gleam@dict:dict(DNG, astarstate(DNG, DNH))) -> list(DNH). reconstruct_actions(State, Flowfield) -> do_reconstruct_actions(State, Flowfield, []). -file("src/gwg/pathfinding.gleam", 165). -spec do_astar( DLP, fun((DLP, float()) -> list(action(DLQ, DLP))), fun((DLP) -> float()), gleam@set:set(DLP), gleam@dict:dict(DLP, astarstate(DLP, DLQ)) ) -> {ok, list(DLQ)} | {error, list(DLQ)}. do_astar(Init_state, Successor_fun, Heuristic_fun, Queue, Flowfield) -> case lowest_f_score_state(Queue, Flowfield) of {ok, State} -> {G_score@1, F_score@1} = case begin _pipe = Flowfield, gleam_stdlib:map_get(_pipe, State) end of {ok, {astarstate, _, _, G_score, F_score}} -> {G_score, F_score}; {error, nil} -> {+0.0, 1.0} end, gleam@bool:lazy_guard( (F_score@1 - G_score@1) =< +0.0, fun() -> {ok, begin _pipe@1 = State, reconstruct_actions( _pipe@1, begin _pipe@2 = Flowfield, gleam@dict:delete(_pipe@2, Init_state) end ) end} end, fun() -> Queue@1 = begin _pipe@3 = Queue, gleam@set:delete(_pipe@3, State) end, Actions = begin _pipe@4 = Successor_fun(State, G_score@1), astar_actions_folder( _pipe@4, State, G_score@1, Heuristic_fun, Flowfield ) end, Queue@2 = begin _pipe@5 = Actions, _pipe@6 = maps:keys(_pipe@5), _pipe@7 = gleam@set:from_list(_pipe@6), gleam@set:union(_pipe@7, Queue@1) end, Flowfield@1 = begin _pipe@8 = Flowfield, maps:merge(_pipe@8, Actions) end, do_astar( Init_state, Successor_fun, Heuristic_fun, Queue@2, Flowfield@1 ) end ); {error, nil} -> _pipe@9 = closest_state(Flowfield), _pipe@10 = gleam@result:unwrap(_pipe@9, Init_state), _pipe@12 = reconstruct_actions( _pipe@10, begin _pipe@11 = Flowfield, gleam@dict:delete(_pipe@11, Init_state) end ), {error, _pipe@12} end. -file("src/gwg/pathfinding.gleam", 151). ?DOC( " Performs an A* search to find the optimal path from an initial `state`.\n" "\n" " This function returns a the sequence of actions to reach the goal when the\n" " `heuristic_fun` returns 0.0 (or less), signaling the goal has been\n" " reached.\n" "\n" " If the goal can not be reach, it will returns the sequence of actions to\n" " the \"closest\" state reached.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let actions = [\n" " Vec2(-1, 0),\n" " Vec2(1, 0),\n" " Vec2(0, -1),\n" " Vec2(0, 1),\n" " Vec2(-1, -1),\n" " Vec2(-1, 1),\n" " Vec2(1, -1),\n" " Vec2(1, 1),\n" " ]\n" "\n" " let world0 =\n" " vec2i_dict.from_string(\n" " \"\"\n" " <> \"###########\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# ## ####\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"## ##### ##\\n\"\n" " <> \"# # # #\\n\"\n" " <> \"# #\\n\"\n" " <> \"###########\\n\",\n" " )\n" "\n" " let world1 =\n" " vec2i_dict.from_string(\n" " \"\"\n" " <> \"###########\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# ##x####\\n\"\n" " <> \"# # #\\n\"\n" " <> \"# # #\\n\"\n" " <> \"## ##### ##\\n\"\n" " <> \"# # # #\\n\"\n" " <> \"# #\\n\"\n" " <> \"###########\\n\",\n" " )\n" "\n" " let init = Vec2(2, 2)\n" " let target = Vec2(7, 2)\n" "\n" " fn successor(\n" " state: Vec2i,\n" " g_score: Float,\n" " world: Dict(Vec2i, String),\n" " ) -> List(Action(Vec2i, Vec2i)) {\n" " actions\n" " |> list.filter_map(fn(action) {\n" " let weight = action |> vec2i.length\n" " use <- bool.guard(g_score +. weight >. 32.0, Error(Nil))\n" "\n" " let state = state |> vec2i.add(action)\n" " use <- bool.guard(world |> dict.has_key(state), Error(Nil))\n" " Ok(Action(action:, state:, weight:))\n" " })\n" " }\n" "\n" " fn heuristic(state: Vec2i) -> Float {\n" " vec2i.distance(state, target)\n" " }\n" "\n" " astar(\n" " init:,\n" " successor: fn(state, g_score) {\n" " successor(state, g_score, world0)\n" " },\n" " heuristic:,\n" " )\n" " // Ok:\n" " // ###########\n" " // # # #\n" " // # @ # * #\n" " // # ↓ # ↗ #\n" " // # ↓ ##↑####\n" " // # ↓ # ↖ #\n" " // # ↓ # ↖ #\n" " // ##↘#####↑##\n" " // # ↘# #↗ #\n" " // # →→↗ #\n" " // ###########\n" "\n" " astar(\n" " init:,\n" " successor: fn(state, g_score) {\n" " successor(state, g_score, world1)\n" " },\n" " heuristic:,\n" " )\n" " // Error:\n" " // ###########\n" " // # # #\n" " // # @ # * #\n" " // # ↓ # #\n" " // # ↓ ##x####\n" " // # ↓ # #\n" " // # ↓ # ↖ #\n" " // ##↘#####↑##\n" " // # ↘# #↗ #\n" " // # →→↗ #\n" " // ###########\n" " ```\n" ). -spec astar( DLH, fun((DLH, float()) -> list(action(DLI, DLH))), fun((DLH) -> float()) ) -> {ok, list(DLI)} | {error, list(DLI)}. astar(State, Successor_fun, Heuristic_fun) -> do_astar( State, Successor_fun, Heuristic_fun, gleam@set:from_list([State]), maps:new() ). -file("src/gwg/pathfinding.gleam", 373). -spec flowfield_actions_folder( list(action(DOL, DOM)), float(), gleam@dict:dict(DOM, flowfieldstate(DOL)) ) -> gleam@dict:dict(DOM, flowfieldstate(DOL)). flowfield_actions_folder(Actions, From_g_score, Flowfield) -> _pipe = Actions, gleam@list:fold( _pipe, maps:new(), fun(Acc, Action) -> G_score = From_g_score + erlang:element(4, Action), case begin _pipe@1 = Flowfield, _pipe@2 = maps:merge(_pipe@1, Acc), gleam_stdlib:map_get(_pipe@2, erlang:element(3, Action)) end of {ok, Old} when (erlang:element(4, Old) < G_score) orelse ((erlang:element( 4, Old ) =< G_score) andalso (erlang:element(3, Old) =< erlang:element( 4, Action ))) -> Acc; _ -> _pipe@3 = {flowfieldstate, erlang:element(2, Action), erlang:element(4, Action), G_score}, gleam@dict:insert(Acc, erlang:element(3, Action), _pipe@3) end end ). -file("src/gwg/pathfinding.gleam", 340). -spec do_flowfield( DOB, fun((DOB, float()) -> list(action(DOC, DOB))), list(DOB), gleam@dict:dict(DOB, flowfieldstate(DOC)) ) -> gleam@dict:dict(DOB, DOC). do_flowfield(Start_state, Successor_fun, Queue, Flowfield) -> case Queue of [State | Queue@1] -> G_score@1 = case begin _pipe = Flowfield, gleam_stdlib:map_get(_pipe, State) end of {ok, {flowfieldstate, _, _, G_score}} -> G_score; {error, nil} -> +0.0 end, Actions = begin _pipe@1 = Successor_fun(State, G_score@1), flowfield_actions_folder(_pipe@1, G_score@1, Flowfield) end, Queue@2 = begin _pipe@2 = Actions, _pipe@4 = gleam@dict:drop( _pipe@2, begin _pipe@3 = Flowfield, maps:keys(_pipe@3) end ), _pipe@5 = maps:keys(_pipe@4), lists:append(Queue@1, _pipe@5) end, Flowfield@1 = begin _pipe@6 = Flowfield, maps:merge(_pipe@6, Actions) end, do_flowfield(Start_state, Successor_fun, Queue@2, Flowfield@1); _ -> _pipe@7 = Flowfield, _pipe@8 = gleam@dict:delete(_pipe@7, Start_state), gleam@dict:map_values( _pipe@8, fun(_, Value) -> erlang:element(2, Value) end ) end. -file("src/gwg/pathfinding.gleam", 333). ?DOC( " Generates a flowfield (also known as vector field or Dijkstra map).\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let actions = [\n" " Vec2(-1, 0),\n" " Vec2(1, 0),\n" " Vec2(0, -1),\n" " Vec2(0, 1),\n" " Vec2(-1, -1),\n" " Vec2(-1, 1),\n" " Vec2(1, -1),\n" " Vec2(1, 1),\n" " ]\n" "\n" " let world =\n" " vec2i_dict.from_string(\n" " \"\"\n" " <> \" # \\n\"\n" " <> \" ### ### \\n\"\n" " <> \" \\n\"\n" " <> \"### ##### #\\n\"\n" " <> \"# # # # #\\n\"\n" " <> \"# # # # # #\\n\"\n" " <> \"# # ### # #\\n\"\n" " <> \"# # # #\\n\"\n" " <> \"# ####### #\\n\"\n" " <> \"# #\\n\"\n" " <> \"###########\\n\",\n" " )\n" "\n" " let target = Vec2(5, 5)\n" "\n" " fn successor(state: Vec2i, g_score: Float) -> List(Action(Vec2i, Vec2i)) {\n" " actions\n" " |> list.filter_map(fn(action) {\n" " let weight = action |> vec2i.length\n" " use <- bool.guard(g_score +. weight >. 32.0, Error(Nil))\n" "\n" " let state = state |> vec2i.subtract(action)\n" " use <- bool.guard(vec2i.distance(state, target) >. 16.0, Error(Nil))\n" " use <- bool.guard(world |> dict.has_key(state), Error(Nil))\n" " Ok(Action(action:, state:, weight:))\n" " })\n" " }\n" "\n" " astar(init:, successor:)\n" " // ↓↙→↘↓#↙←←←←\n" " // ↘###↓↙←###↙\n" " // →→↘↓↙←←←←←←\n" " // ###↓#####↖#\n" " // # #↓#↓↙←#↑#\n" " // # #↓#*#↖#↑#\n" " // # #↘###↑#↑#\n" " // # #→→→↗↑#↑#\n" " // # #######↑#\n" " // #→→→→→→→↗↑#\n" " // ###########\n" " ```\n" ). -spec flowfield(DNV, fun((DNV, float()) -> list(action(DNW, DNV)))) -> gleam@dict:dict(DNV, DNW). flowfield(State, Successor_fun) -> do_flowfield(State, Successor_fun, [State], maps:new()).