From 422930816d49a29e461571c49145f289939752d5 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Wed, 26 Mar 2025 17:30:18 -0300 Subject: [PATCH 01/11] feat: add spline post processing to astar path --- apps/bot_manager/lib/bot_state_machine.ex | 1 + apps/bot_manager/lib/math/vector.ex | 14 +++ apps/bot_manager/lib/spline_path.ex | 104 ++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 apps/bot_manager/lib/spline_path.ex diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 33e917df4..6dc16ca4a 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -325,6 +325,7 @@ defmodule BotManager.BotStateMachine do to = %{x: position_to_move_to.x, y: position_to_move_to.y} shortest_path = AStarNative.a_star_shortest_path(from, to, bot_state_machine.collision_grid) + |> SplinePath.smooth_path() # If we don't have a path, retry finding new position in map if Enum.empty?(shortest_path) do diff --git a/apps/bot_manager/lib/math/vector.ex b/apps/bot_manager/lib/math/vector.ex index 7ac0aaaaa..36e071219 100644 --- a/apps/bot_manager/lib/math/vector.ex +++ b/apps/bot_manager/lib/math/vector.ex @@ -3,6 +3,20 @@ defmodule BotManager.Math.Vector do Module to handle math operations with vectors """ + def add(vector, value) when is_integer(value) or is_float(value) do + %{ + x: vector.x + value, + y: vector.y + value + } + end + + def add(first_vector, second_vector) do + %{ + x: first_vector.x + second_vector.x, + y: first_vector.y + second_vector.y + } + end + def sub(vector, value) when is_integer(value) or is_float(value) do %{ x: vector.x - value, diff --git a/apps/bot_manager/lib/spline_path.ex b/apps/bot_manager/lib/spline_path.ex new file mode 100644 index 000000000..db580bed8 --- /dev/null +++ b/apps/bot_manager/lib/spline_path.ex @@ -0,0 +1,104 @@ +defmodule SplinePath do + @moduledoc """ + This module defines methods to generate a spline path out of a waypoint path + """ +alias BotManager.Math.Vector + + @segment_point_amount 5 + @tension 0 + @alpha 0.5 + + def smooth_path(waypoints) when length(waypoints) < 3 do + waypoints + end + + def smooth_path(waypoints) do + first_point = Enum.at(waypoints, 0) + second_point = Enum.at(waypoints, 1) + last_point = Enum.at(waypoints, -1) + second_to_last_point = Enum.at(waypoints, -2) + + first_control_point = Vector.add(first_point, Vector.sub(first_point, second_point)) + last_control_point = Vector.add(last_point, Vector.sub(last_point, second_to_last_point)) + control_points = [first_control_point] ++ waypoints ++ [last_control_point] + + generate_spline_from_control_points(control_points) ++ [last_point] + end + + defp generate_spline_from_control_points(control_points) do + Enum.chunk_every(control_points, 4, 1, :discard) + |> Enum.map(fn cps -> build_points_for_spline(cps) end) + |> List.flatten() + end + + # float t01 = pow(distance(p0, p1), alpha); + # float t12 = pow(distance(p1, p2), alpha); + # float t23 = pow(distance(p2, p3), alpha); + + # vec2 m1 = (1.0f - tension) * + # (p2 - p1 + t12 * ( + # (p1 - p0) / t01 - (p2 - p0) / (t01 + t12) + # ) + # ); + # vec2 m2 = (1.0f - tension) * + # (p2 - p1 + t12 * ((p3 - p2) / t23 - (p3 - p1) / (t12 + t23))); + # + # Segment segment; + # segment.a = 2.0f * (p1 - p2) + m1 + m2; + # segment.b = -3.0f * (p1 - p2) - m1 - m1 - m2; + # segment.c = m1; + # segment.d = p1; + # + # vec2 point = segment.a * t * t * t + + # segment.b * t * t + + # segment.c * t + + # segment.d; + + defp build_points_for_spline([p0, p1, p2, p3]) do + t01 = :math.pow(Vector.distance(p0, p1), @alpha) + t12 = :math.pow(Vector.distance(p1, p2), @alpha) + t23 = :math.pow(Vector.distance(p2, p3), @alpha) + + m1 = Vector.sub( + Vector.mult(Vector.sub(p1, p0), 1 / t01), + Vector.mult(Vector.sub(p1, p0), 1 / (t01 + t12)) + ) + |> Vector.mult(t12) + |> Vector.add(p2) + |> Vector.sub(p1) + |> Vector.mult(1.0 - @tension) + + m2 = Vector.sub( + Vector.mult(Vector.sub(p3, p2), 1 / t23), + Vector.mult(Vector.sub(p3, p1), 1 / (t12 + t23)) + ) + |> Vector.mult(t12) + |> Vector.add(p2) + |> Vector.sub(p1) + |> Vector.mult(1.0 - @tension) + + a = Vector.sub(p1, p2) + |> Vector.mult(2.0) + |> Vector.add(m1) + |> Vector.add(m2) + + b = Vector.sub(p1, p2) + |> Vector.mult(-3.0) + |> Vector.sub(m1) + |> Vector.sub(m1) + |> Vector.sub(m2) + + c = m1 + d = p1 + + # last point will be the next part start so do not add it + Enum.map(0..(@segment_point_amount - 1), fn segment_num -> + t = segment_num / @segment_point_amount + + d + |> Vector.add(Vector.mult(c, t)) + |> Vector.add(Vector.mult(b, t * t)) + |> Vector.add(Vector.mult(a, t * t * t)) + end) + end +end From db7b5c32fe6f7b15f2090ad8225236cf2644e469 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Wed, 26 Mar 2025 18:29:00 -0300 Subject: [PATCH 02/11] feat: add path simplification --- apps/bot_manager/lib/astar_native.ex | 2 ++ apps/bot_manager/lib/bot_state_machine.ex | 1 + .../lib/bot_state_machine_checker.ex | 12 +++++--- apps/bot_manager/lib/spline_path.ex | 6 ++-- .../bot_manager/native/astarnative/src/lib.rs | 28 ++++++++++++++++++- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/apps/bot_manager/lib/astar_native.ex b/apps/bot_manager/lib/astar_native.ex index e3011d5a3..6650f04ee 100644 --- a/apps/bot_manager/lib/astar_native.ex +++ b/apps/bot_manager/lib/astar_native.ex @@ -8,5 +8,7 @@ defmodule AStarNative do # When your NIF is loaded, it will override this function. def a_star_shortest_path(_from, _to, _collision_grid), do: :erlang.nif_error(:nif_not_loaded) + def simplify_path(path, obstacles), do: :erlang.nif_error(:nif_not_loaded) + def build_collision_grid(_obstacles), do: :erlang.nif_error(:nif_not_loaded) end diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 6dc16ca4a..8645fe16e 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -325,6 +325,7 @@ defmodule BotManager.BotStateMachine do to = %{x: position_to_move_to.x, y: position_to_move_to.y} shortest_path = AStarNative.a_star_shortest_path(from, to, bot_state_machine.collision_grid) + |> AStarNative.simplify_path(bot_state_machine.obstacles) |> SplinePath.smooth_path() # If we don't have a path, retry finding new position in map diff --git a/apps/bot_manager/lib/bot_state_machine_checker.ex b/apps/bot_manager/lib/bot_state_machine_checker.ex index a019b29d2..dccdec218 100644 --- a/apps/bot_manager/lib/bot_state_machine_checker.ex +++ b/apps/bot_manager/lib/bot_state_machine_checker.ex @@ -6,7 +6,7 @@ defmodule BotManager.BotStateMachineChecker do alias BotManager.Math.Vector @time_stuck_in_position 400 - @distance_threshold 100 + @distance_threshold 150 # Bots will track a player for at most @tracking_timeout_ms milliseconds # after which it will transition to another state @@ -45,7 +45,8 @@ defmodule BotManager.BotStateMachineChecker do collision_grid: binary() | nil, last_time_state_changed: integer(), last_time_tracking_exited: integer(), - last_time_attacking_exited: integer() + last_time_attacking_exited: integer(), + obstacles: list() | nil } defstruct [ @@ -92,7 +93,9 @@ defmodule BotManager.BotStateMachineChecker do # The last time the bot exited the tracking state :last_time_tracking_exited, # The last time the bot exited the tracking state - :last_time_attacking_exited + :last_time_attacking_exited, + # The obstacles on the level + :obstacles ] @spec new() :: BotManager.BotStateMachineChecker.t() @@ -119,7 +122,8 @@ defmodule BotManager.BotStateMachineChecker do collision_grid: nil, last_time_state_changed: 0, last_time_tracking_exited: 0, - last_time_attacking_exited: 0 + last_time_attacking_exited: 0, + obstacles: nil } end diff --git a/apps/bot_manager/lib/spline_path.ex b/apps/bot_manager/lib/spline_path.ex index db580bed8..42fc6cc38 100644 --- a/apps/bot_manager/lib/spline_path.ex +++ b/apps/bot_manager/lib/spline_path.ex @@ -4,7 +4,7 @@ defmodule SplinePath do """ alias BotManager.Math.Vector - @segment_point_amount 5 + @segment_point_amount 15 @tension 0 @alpha 0.5 @@ -18,8 +18,8 @@ alias BotManager.Math.Vector last_point = Enum.at(waypoints, -1) second_to_last_point = Enum.at(waypoints, -2) - first_control_point = Vector.add(first_point, Vector.sub(first_point, second_point)) - last_control_point = Vector.add(last_point, Vector.sub(last_point, second_to_last_point)) + first_control_point = Vector.add(first_point, Vector.sub(first_point, second_point) |> Vector.normalize()) + last_control_point = Vector.add(last_point, Vector.sub(last_point, second_to_last_point) |> Vector.normalize()) control_points = [first_control_point] ++ waypoints ++ [last_control_point] generate_spline_from_control_points(control_points) ++ [last_point] diff --git a/apps/bot_manager/native/astarnative/src/lib.rs b/apps/bot_manager/native/astarnative/src/lib.rs index e10edb0be..20ea015ae 100644 --- a/apps/bot_manager/native/astarnative/src/lib.rs +++ b/apps/bot_manager/native/astarnative/src/lib.rs @@ -80,6 +80,32 @@ fn build_collision_grid<'a>(env: Env<'a>, obstacles: HashMap) -> Re return Ok(grid.release(env)); } +#[rustler::nif()] +fn simplify_path(path: Vec, obstacles: HashMap) -> Vec { + if path.len() < 3 { + return path; + } + + let obstacles = obstacles.into_values().collect::>(); + let mut final_path = vec![path[0]]; + + let mut checkpoint_index = 1; + while checkpoint_index < path.len() - 1 { + let mut line = Entity::new_line(0, vec![final_path[final_path.len() - 1], path[checkpoint_index + 1]]); + + if !line.collides_with(&obstacles).is_empty() { + final_path.push(path[checkpoint_index]); + } + + checkpoint_index += 1; + } + + final_path.push(path[path.len() - 1]); + + return final_path; +} + + #[derive(Clone, Copy, Eq, PartialEq)] struct NodeEntry { node: (i64, i64), @@ -175,4 +201,4 @@ fn grid_to_world(grid_pos: &(i64, i64)) -> Position { } } -rustler::init!("Elixir.AStarNative", [a_star_shortest_path, build_collision_grid]); +rustler::init!("Elixir.AStarNative", [a_star_shortest_path, build_collision_grid, simplify_path]); From c913692c669dce374f98209e70ec1feb1e8b059e Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Thu, 27 Mar 2025 18:49:14 -0300 Subject: [PATCH 03/11] fix twitching during path following and path recalculation --- apps/bot_manager/lib/bot_state_machine.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 8645fe16e..b1a2c2366 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -325,14 +325,20 @@ defmodule BotManager.BotStateMachine do to = %{x: position_to_move_to.x, y: position_to_move_to.y} shortest_path = AStarNative.a_star_shortest_path(from, to, bot_state_machine.collision_grid) - |> AStarNative.simplify_path(bot_state_machine.obstacles) - |> SplinePath.smooth_path() # If we don't have a path, retry finding new position in map if Enum.empty?(shortest_path) do Map.put(bot_state_machine, :path_towards_position, nil) |> Map.put(:position_to_move_to, nil) else + # Replacing first and last points with the actual start and end points + shortest_path = ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) + |> AStarNative.simplify_path(bot_state_machine.obstacles) + |> SplinePath.smooth_path() + + # The first point should only be necessary to simplify the path + shortest_path = tl(shortest_path) + Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) |> Map.put( :path_towards_position, From b10216125955ae907bd0cac47f780a708726f51e Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Fri, 28 Mar 2025 15:21:15 -0300 Subject: [PATCH 04/11] change splines params --- apps/bot_manager/lib/spline_path.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/bot_manager/lib/spline_path.ex b/apps/bot_manager/lib/spline_path.ex index 42fc6cc38..f3c7cb92f 100644 --- a/apps/bot_manager/lib/spline_path.ex +++ b/apps/bot_manager/lib/spline_path.ex @@ -4,8 +4,8 @@ defmodule SplinePath do """ alias BotManager.Math.Vector - @segment_point_amount 15 - @tension 0 + @segment_point_amount 5 + @tension 0.2 @alpha 0.5 def smooth_path(waypoints) when length(waypoints) < 3 do From 52b0f2ac12225f5a71ef01ddfa3c8a7ab80553e9 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Fri, 28 Mar 2025 15:24:07 -0300 Subject: [PATCH 05/11] fix: handle edge case of path of length 1 --- apps/bot_manager/lib/bot_state_machine.ex | 44 +++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index b1a2c2366..e77b50538 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -327,24 +327,32 @@ defmodule BotManager.BotStateMachine do shortest_path = AStarNative.a_star_shortest_path(from, to, bot_state_machine.collision_grid) # If we don't have a path, retry finding new position in map - if Enum.empty?(shortest_path) do - Map.put(bot_state_machine, :path_towards_position, nil) - |> Map.put(:position_to_move_to, nil) - else - # Replacing first and last points with the actual start and end points - shortest_path = ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) - |> AStarNative.simplify_path(bot_state_machine.obstacles) - |> SplinePath.smooth_path() - - # The first point should only be necessary to simplify the path - shortest_path = tl(shortest_path) - - Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) - |> Map.put( - :path_towards_position, - shortest_path - ) - |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) + cond do + Enum.empty?(shortest_path) -> + Map.put(bot_state_machine, :path_towards_position, nil) + |> Map.put(:position_to_move_to, nil) + length(shortest_path) == 1 -> + Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) + |> Map.put( + :path_towards_position, + [to] + ) + |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) + true -> + # Replacing first and last points with the actual start and end points + shortest_path = ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) + |> AStarNative.simplify_path(bot_state_machine.obstacles) + |> SplinePath.smooth_path() + + # The first point should only be necessary to simplify the path + shortest_path = tl(shortest_path) + + Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) + |> Map.put( + :path_towards_position, + shortest_path + ) + |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) end end end From 37cab25be8e4660e9f572f28a0d1cfe258c797ec Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Fri, 28 Mar 2025 15:24:44 -0300 Subject: [PATCH 06/11] put path smoothing with splines under env var flag --- apps/bot_manager/lib/bot_state_machine.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index e77b50538..1c8389e6b 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -342,7 +342,13 @@ defmodule BotManager.BotStateMachine do # Replacing first and last points with the actual start and end points shortest_path = ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) |> AStarNative.simplify_path(bot_state_machine.obstacles) - |> SplinePath.smooth_path() + + shortest_path = if System.get_env("TEST_PATHFINDING_SPLINES") == "true" do + shortest_path + |> SplinePath.smooth_path() + else + shortest_path + end # The first point should only be necessary to simplify the path shortest_path = tl(shortest_path) From 1eb64a0062ef5a25b846e28f10fe8ea8e3bfc61c Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Thu, 3 Apr 2025 18:29:42 -0300 Subject: [PATCH 07/11] fix: restore obstacle setup before it was being added in game_socket_handler from the BotManager but that file was deleted --- apps/arena/lib/arena/bots/bot.ex | 59 ++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/apps/arena/lib/arena/bots/bot.ex b/apps/arena/lib/arena/bots/bot.ex index 3771b57cb..f72907bef 100644 --- a/apps/arena/lib/arena/bots/bot.ex +++ b/apps/arena/lib/arena/bots/bot.ex @@ -92,6 +92,48 @@ defmodule Arena.Bots.Bot do defp maybe_update_state_params(state, game_state) do state |> Map.put_new(:bot_player_id, get_in(game_state, [:client_to_player_map, state.bot_id])) + |> maybe_set_obstacles(game_state) + end + + defp maybe_set_obstacles(%{bot_state_machine: %{obstacles: nil}} = state, %{obstacles: obstacles}) when not is_nil(obstacles) do + obstacles = + obstacles + |> Enum.map(fn {obstacle_id, obstacle} -> + obstacle = + obstacle + |> Map.take([ + :id, + :shape, + :position, + :radius, + :vertices, + :speed, + :category, + :direction, + :is_moving, + :name + ]) + + obstacle = + obstacle + |> Map.put(:position, %{x: obstacle.position.x, y: obstacle.position.y}) + |> Map.put( + :vertices, + Enum.map(obstacle.vertices.positions, fn position -> %{x: position.x, y: position.y} end) + ) + |> Map.put(:direction, %{x: obstacle.direction.x, y: obstacle.direction.y}) + |> Map.put(:shape, get_shape(obstacle.shape)) + |> Map.put(:category, get_category(obstacle.category)) + + {obstacle_id, obstacle} + end) + |> Map.new() + + %{state | bot_state_machine: %{state.bot_state_machine | obstacles: obstacles}} + end + + defp maybe_set_obstacles(state, _game_state) do + state end defp generate_bot_name(bot_id), do: {:via, Registry, {BotRegistry, bot_id}} @@ -164,6 +206,19 @@ defmodule Arena.Bots.Bot do Logger.error("Bot #{state.bot_id} terminating: #{inspect(reason)}") end - defp min_decision_delay_ms(), do: 40 - defp max_decision_delay_ms(), do: 60 + defp min_decision_delay_ms(), do: 100 + defp max_decision_delay_ms(), do: 150 + + defp get_shape("polygon"), do: :polygon + defp get_shape("circle"), do: :circle + defp get_shape("line"), do: :line + defp get_shape("point"), do: :point + defp get_shape(_), do: nil + defp get_category("player"), do: :player + defp get_category("projectile"), do: :projectile + defp get_category("obstacle"), do: :obstacle + defp get_category("power_up"), do: :power_up + defp get_category("pool"), do: :pool + defp get_category("item"), do: :item + defp get_category("bush"), do: :bush end From c4cfe43bab25b4b0615dabbe525fe20550619570 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Mon, 21 Apr 2025 17:02:42 -0300 Subject: [PATCH 08/11] format: underscore unused vars --- apps/bot_manager/lib/astar_native.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bot_manager/lib/astar_native.ex b/apps/bot_manager/lib/astar_native.ex index 6650f04ee..6aced4033 100644 --- a/apps/bot_manager/lib/astar_native.ex +++ b/apps/bot_manager/lib/astar_native.ex @@ -8,7 +8,7 @@ defmodule AStarNative do # When your NIF is loaded, it will override this function. def a_star_shortest_path(_from, _to, _collision_grid), do: :erlang.nif_error(:nif_not_loaded) - def simplify_path(path, obstacles), do: :erlang.nif_error(:nif_not_loaded) + def simplify_path(_path, _obstacles), do: :erlang.nif_error(:nif_not_loaded) def build_collision_grid(_obstacles), do: :erlang.nif_error(:nif_not_loaded) end From 5150caf11eac400449080e7c5c5808216d2292b3 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Mon, 21 Apr 2025 17:05:41 -0300 Subject: [PATCH 09/11] remove commented lines and add mention to source article --- apps/bot_manager/lib/spline_path.ex | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/apps/bot_manager/lib/spline_path.ex b/apps/bot_manager/lib/spline_path.ex index f3c7cb92f..48b766499 100644 --- a/apps/bot_manager/lib/spline_path.ex +++ b/apps/bot_manager/lib/spline_path.ex @@ -1,6 +1,8 @@ defmodule SplinePath do @moduledoc """ This module defines methods to generate a spline path out of a waypoint path + + Based on https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html """ alias BotManager.Math.Vector @@ -31,29 +33,6 @@ alias BotManager.Math.Vector |> List.flatten() end - # float t01 = pow(distance(p0, p1), alpha); - # float t12 = pow(distance(p1, p2), alpha); - # float t23 = pow(distance(p2, p3), alpha); - - # vec2 m1 = (1.0f - tension) * - # (p2 - p1 + t12 * ( - # (p1 - p0) / t01 - (p2 - p0) / (t01 + t12) - # ) - # ); - # vec2 m2 = (1.0f - tension) * - # (p2 - p1 + t12 * ((p3 - p2) / t23 - (p3 - p1) / (t12 + t23))); - # - # Segment segment; - # segment.a = 2.0f * (p1 - p2) + m1 + m2; - # segment.b = -3.0f * (p1 - p2) - m1 - m1 - m2; - # segment.c = m1; - # segment.d = p1; - # - # vec2 point = segment.a * t * t * t + - # segment.b * t * t + - # segment.c * t + - # segment.d; - defp build_points_for_spline([p0, p1, p2, p3]) do t01 = :math.pow(Vector.distance(p0, p1), @alpha) t12 = :math.pow(Vector.distance(p1, p2), @alpha) From 3d8be76fed19cfd5ab893dfd1aba2429ffe3d30b Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Mon, 21 Apr 2025 17:24:30 -0300 Subject: [PATCH 10/11] run formatter --- apps/arena/lib/arena/bots/bot.ex | 65 ++++++++++++----------- apps/bot_manager/lib/bot_state_machine.ex | 22 ++++---- apps/bot_manager/lib/spline_path.ex | 60 +++++++++++---------- 3 files changed, 78 insertions(+), 69 deletions(-) diff --git a/apps/arena/lib/arena/bots/bot.ex b/apps/arena/lib/arena/bots/bot.ex index f72907bef..d9071a4a2 100644 --- a/apps/arena/lib/arena/bots/bot.ex +++ b/apps/arena/lib/arena/bots/bot.ex @@ -95,39 +95,40 @@ defmodule Arena.Bots.Bot do |> maybe_set_obstacles(game_state) end - defp maybe_set_obstacles(%{bot_state_machine: %{obstacles: nil}} = state, %{obstacles: obstacles}) when not is_nil(obstacles) do + defp maybe_set_obstacles(%{bot_state_machine: %{obstacles: nil}} = state, %{obstacles: obstacles}) + when not is_nil(obstacles) do obstacles = - obstacles - |> Enum.map(fn {obstacle_id, obstacle} -> - obstacle = - obstacle - |> Map.take([ - :id, - :shape, - :position, - :radius, - :vertices, - :speed, - :category, - :direction, - :is_moving, - :name - ]) - - obstacle = - obstacle - |> Map.put(:position, %{x: obstacle.position.x, y: obstacle.position.y}) - |> Map.put( - :vertices, - Enum.map(obstacle.vertices.positions, fn position -> %{x: position.x, y: position.y} end) - ) - |> Map.put(:direction, %{x: obstacle.direction.x, y: obstacle.direction.y}) - |> Map.put(:shape, get_shape(obstacle.shape)) - |> Map.put(:category, get_category(obstacle.category)) - - {obstacle_id, obstacle} - end) - |> Map.new() + obstacles + |> Enum.map(fn {obstacle_id, obstacle} -> + obstacle = + obstacle + |> Map.take([ + :id, + :shape, + :position, + :radius, + :vertices, + :speed, + :category, + :direction, + :is_moving, + :name + ]) + + obstacle = + obstacle + |> Map.put(:position, %{x: obstacle.position.x, y: obstacle.position.y}) + |> Map.put( + :vertices, + Enum.map(obstacle.vertices.positions, fn position -> %{x: position.x, y: position.y} end) + ) + |> Map.put(:direction, %{x: obstacle.direction.x, y: obstacle.direction.y}) + |> Map.put(:shape, get_shape(obstacle.shape)) + |> Map.put(:category, get_category(obstacle.category)) + + {obstacle_id, obstacle} + end) + |> Map.new() %{state | bot_state_machine: %{state.bot_state_machine | obstacles: obstacles}} end diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 1c8389e6b..47170736b 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -331,6 +331,7 @@ defmodule BotManager.BotStateMachine do Enum.empty?(shortest_path) -> Map.put(bot_state_machine, :path_towards_position, nil) |> Map.put(:position_to_move_to, nil) + length(shortest_path) == 1 -> Map.put(bot_state_machine, :position_to_move_to, position_to_move_to) |> Map.put( @@ -338,17 +339,20 @@ defmodule BotManager.BotStateMachine do [to] ) |> Map.put(:last_time_position_changed, :os.system_time(:millisecond)) + true -> # Replacing first and last points with the actual start and end points - shortest_path = ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) - |> AStarNative.simplify_path(bot_state_machine.obstacles) - - shortest_path = if System.get_env("TEST_PATHFINDING_SPLINES") == "true" do - shortest_path - |> SplinePath.smooth_path() - else - shortest_path - end + shortest_path = + ([from] ++ Enum.slice(shortest_path, 1, Enum.count(shortest_path) - 2) ++ [to]) + |> AStarNative.simplify_path(bot_state_machine.obstacles) + + shortest_path = + if System.get_env("TEST_PATHFINDING_SPLINES") == "true" do + shortest_path + |> SplinePath.smooth_path() + else + shortest_path + end # The first point should only be necessary to simplify the path shortest_path = tl(shortest_path) diff --git a/apps/bot_manager/lib/spline_path.ex b/apps/bot_manager/lib/spline_path.ex index 48b766499..29ed8870d 100644 --- a/apps/bot_manager/lib/spline_path.ex +++ b/apps/bot_manager/lib/spline_path.ex @@ -4,7 +4,7 @@ defmodule SplinePath do Based on https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html """ -alias BotManager.Math.Vector + alias BotManager.Math.Vector @segment_point_amount 5 @tension 0.2 @@ -37,41 +37,45 @@ alias BotManager.Math.Vector t01 = :math.pow(Vector.distance(p0, p1), @alpha) t12 = :math.pow(Vector.distance(p1, p2), @alpha) t23 = :math.pow(Vector.distance(p2, p3), @alpha) - - m1 = Vector.sub( - Vector.mult(Vector.sub(p1, p0), 1 / t01), - Vector.mult(Vector.sub(p1, p0), 1 / (t01 + t12)) - ) - |> Vector.mult(t12) - |> Vector.add(p2) - |> Vector.sub(p1) - |> Vector.mult(1.0 - @tension) - m2 = Vector.sub( - Vector.mult(Vector.sub(p3, p2), 1 / t23), - Vector.mult(Vector.sub(p3, p1), 1 / (t12 + t23)) - ) - |> Vector.mult(t12) - |> Vector.add(p2) - |> Vector.sub(p1) - |> Vector.mult(1.0 - @tension) + m1 = + Vector.sub( + Vector.mult(Vector.sub(p1, p0), 1 / t01), + Vector.mult(Vector.sub(p1, p0), 1 / (t01 + t12)) + ) + |> Vector.mult(t12) + |> Vector.add(p2) + |> Vector.sub(p1) + |> Vector.mult(1.0 - @tension) - a = Vector.sub(p1, p2) - |> Vector.mult(2.0) - |> Vector.add(m1) - |> Vector.add(m2) + m2 = + Vector.sub( + Vector.mult(Vector.sub(p3, p2), 1 / t23), + Vector.mult(Vector.sub(p3, p1), 1 / (t12 + t23)) + ) + |> Vector.mult(t12) + |> Vector.add(p2) + |> Vector.sub(p1) + |> Vector.mult(1.0 - @tension) - b = Vector.sub(p1, p2) - |> Vector.mult(-3.0) - |> Vector.sub(m1) - |> Vector.sub(m1) - |> Vector.sub(m2) + a = + Vector.sub(p1, p2) + |> Vector.mult(2.0) + |> Vector.add(m1) + |> Vector.add(m2) + + b = + Vector.sub(p1, p2) + |> Vector.mult(-3.0) + |> Vector.sub(m1) + |> Vector.sub(m1) + |> Vector.sub(m2) c = m1 d = p1 # last point will be the next part start so do not add it - Enum.map(0..(@segment_point_amount - 1), fn segment_num -> + Enum.map(0..(@segment_point_amount - 1), fn segment_num -> t = segment_num / @segment_point_amount d From f0c1206dc61ae3260712da9d3992c7ad8d68f920 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Mon, 12 May 2025 15:33:03 -0300 Subject: [PATCH 11/11] restore old decision time values --- apps/arena/lib/arena/bots/bot.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/arena/lib/arena/bots/bot.ex b/apps/arena/lib/arena/bots/bot.ex index d9071a4a2..3d372d544 100644 --- a/apps/arena/lib/arena/bots/bot.ex +++ b/apps/arena/lib/arena/bots/bot.ex @@ -207,8 +207,8 @@ defmodule Arena.Bots.Bot do Logger.error("Bot #{state.bot_id} terminating: #{inspect(reason)}") end - defp min_decision_delay_ms(), do: 100 - defp max_decision_delay_ms(), do: 150 + defp min_decision_delay_ms(), do: 40 + defp max_decision_delay_ms(), do: 60 defp get_shape("polygon"), do: :polygon defp get_shape("circle"), do: :circle