Skip to content

Commit

Permalink
[GH-404] lowest and highest stat targeting strategies (#647)
Browse files Browse the repository at this point in the history
* WIP: lowest and highest strategy with tests

* WIP: lowest and highest strategy tests

* Finish implementing tests. Fix some minor issues with the implementation

* Fix bug and test

* Fix merge conflict

* Support multiple targets

* Improve tests naming

* Revert unnecessary atom to string conversion

* Randomly choose units when two or more have same stat amount

* Simplify code

* Refactor sorting function

* Remove duplicate code

* improve pattern-matching for the choose_targets_by_strategy function

* improve pattern-matching for all the choose_targets_by_strategy functions

* Fix format

* Improve function naming
  • Loading branch information
ncontinanza authored Jun 4, 2024
1 parent 03d0b49 commit 0f8cec5
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 27 deletions.
119 changes: 94 additions & 25 deletions apps/champions/lib/champions/battle/simulator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ defmodule Champions.Battle.Simulator do
[x] Frontline - Heroes in slots 1 and 2
[x] Backline - Heroes in slots 2 to 4
[x] All
[ ] Self
[x] Self
[ ] Factions
[ ] Classes
[ ] Min (STAT)
[ ] Max (STAT)
[x] Lowest (STAT)
[x] Highest (STAT)
It can also be chosen how many targets are affected by the effect, and if they are allies or enemies.
Expand Down Expand Up @@ -654,49 +654,49 @@ defmodule Champions.Battle.Simulator do

# Choose the targets for an effect with "random" as the strategy. Returns the target ids.
# The `== target_allies` works as a negation operation when `target_allies` is `false`, and does nothing when `true`.
defp choose_targets_by_strategy(caster, %{count: count, type: "random", target_allies: target_allies}, state),
defp choose_targets_by_strategy(caster, %{type: "random"} = targeting_strategy, state),
do:
state.units
|> Enum.filter(fn {_id, unit} -> unit.team == caster.team == target_allies end)
|> Enum.take_random(count)
|> Enum.filter(fn {_id, unit} -> unit.team == caster.team == targeting_strategy.target_allies end)
|> Enum.take_random(targeting_strategy.count)
|> Enum.map(fn {id, _unit} -> id end)

defp choose_targets_by_strategy(caster, %{count: count, type: "nearest", target_allies: target_allies}, state) do
config_name = if target_allies, do: :ally_proximities, else: :enemy_proximities
defp choose_targets_by_strategy(caster, %{type: "nearest"} = targeting_strategy, state) do
config_name = if targeting_strategy.target_allies, do: :ally_proximities, else: :enemy_proximities

state.units
|> Enum.map(fn {_id, unit} -> unit end)
|> Enum.filter(fn unit -> unit.team == caster.team == target_allies and unit.id != caster.id end)
|> Enum.filter(fn unit -> unit.team == caster.team == targeting_strategy.target_allies and unit.id != caster.id end)
|> find_by_proximity(
Application.get_env(:champions, :"slot_#{caster.slot}_proximities")[config_name],
count
targeting_strategy.count
)
|> Enum.map(& &1.id)
end

defp choose_targets_by_strategy(caster, %{count: count, type: "furthest", target_allies: target_allies}, state) do
config_name = if target_allies, do: :ally_proximities, else: :enemy_proximities
defp choose_targets_by_strategy(caster, %{type: "furthest"} = targeting_strategy, state) do
config_name = if targeting_strategy.target_allies, do: :ally_proximities, else: :enemy_proximities

state.units
|> Enum.map(fn {_id, unit} -> unit end)
|> Enum.filter(fn unit -> unit.team == caster.team == target_allies and unit.id != caster.id end)
|> Enum.filter(fn unit -> unit.team == caster.team == targeting_strategy.target_allies and unit.id != caster.id end)
|> find_by_proximity(
Application.get_env(:champions, :"slot_#{caster.slot}_proximities")[config_name] |> Enum.reverse(),
count
targeting_strategy.count
)
|> Enum.map(& &1.id)
end

defp choose_targets_by_strategy(caster, %{type: "backline", target_allies: target_allies}, state) do
defp choose_targets_by_strategy(caster, %{type: "backline"} = targeting_strategy, state) do
target_team =
Enum.filter(state.units, fn {_id, unit} -> unit.team == caster.team == target_allies end)
Enum.filter(state.units, fn {_id, unit} -> unit.team == caster.team == targeting_strategy.target_allies end)

take_unit_ids_by_slots(target_team, [3, 4, 5, 6])
end

defp choose_targets_by_strategy(caster, %{type: "frontline", target_allies: target_allies}, state) do
defp choose_targets_by_strategy(caster, %{type: "frontline"} = targeting_strategy, state) do
target_team =
Enum.filter(state.units, fn {_id, unit} -> unit.team == caster.team == target_allies end)
Enum.filter(state.units, fn {_id, unit} -> unit.team == caster.team == targeting_strategy.target_allies end)

take_unit_ids_by_slots(target_team, [1, 2])
end
Expand All @@ -705,10 +705,32 @@ defmodule Champions.Battle.Simulator do
[caster.id]
end

defp choose_targets_by_strategy(caster, %{type: "all", target_allies: target_allies}, state),
defp choose_targets_by_strategy(caster, %{type: %{"lowest" => stat}} = targeting_strategy, state) do
choose_units_by_stat_and_team(
state.units,
stat,
targeting_strategy.count,
caster,
targeting_strategy.target_allies,
:desc
)
end

defp choose_targets_by_strategy(caster, %{type: %{"highest" => stat}} = targeting_strategy, state) do
choose_units_by_stat_and_team(
state.units,
stat,
targeting_strategy.count,
caster,
targeting_strategy.target_allies,
:asc
)
end

defp choose_targets_by_strategy(caster, %{type: "all"} = targeting_strategy, state),
do:
state.units
|> Enum.filter(fn {_id, unit} -> unit.team == caster.team == target_allies end)
|> Enum.filter(fn {_id, unit} -> unit.team == caster.team == targeting_strategy.target_allies end)
|> Enum.map(fn {id, _unit} -> id end)

defp find_by_proximity(units, slots_priorities, amount) do
Expand All @@ -720,6 +742,16 @@ defmodule Champions.Battle.Simulator do
Enum.take(sorted_units, amount)
end

defp choose_units_by_stat_and_team(units, stat, count, caster, target_allies, order) do
target_team =
Enum.filter(units, fn {_id, unit} -> unit.team == caster.team == target_allies end)

Enum.map(target_team, fn {_id, unit} -> unit end)
|> sort_units_by_stat(stat, order)
|> Enum.take(count)
|> Enum.map(fn unit -> unit.id end)
end

defp take_unit_ids_by_slots(units, slots) do
slots_units = Enum.filter(units, fn {_id, unit} -> unit.slot in slots end)

Expand Down Expand Up @@ -1162,20 +1194,29 @@ defmodule Champions.Battle.Simulator do
"all",
"frontline",
"backline",
"self"
"self",
"lowest",
"highest"
]

defp create_mechanics_map(%Mechanic{} = mechanic, skill_id, caster_id) do
targeting_strategy_type = mechanic.apply_effects_to.targeting_strategy.type

apply_effects_to = %{
effects: Enum.map(mechanic.apply_effects_to.effects, &create_effect_map(&1, skill_id)),
targeting_strategy: %{
# TODO: replace random for the corresponding target type name (CHoM #325)
# type: mechanic.apply_effects_to.targeting_strategy.type,
type:
if mechanic.apply_effects_to.targeting_strategy.type in @implemented_targeting_strategies do
mechanic.apply_effects_to.targeting_strategy.type
else
"random"
cond do
is_binary(targeting_strategy_type) && targeting_strategy_type in @implemented_targeting_strategies ->
targeting_strategy_type

hd(Map.keys(targeting_strategy_type)) in @implemented_targeting_strategies ->
targeting_strategy_type

true ->
"random"
end,
count: mechanic.apply_effects_to.targeting_strategy.count || 1,
target_allies: mechanic.apply_effects_to.targeting_strategy.target_allies || false
Expand Down Expand Up @@ -1259,6 +1300,34 @@ defmodule Champions.Battle.Simulator do
}
end

defp sort_units_by_stat(units, stat, order) do
Enum.sort(
units,
fn unit_1, unit_2 ->
unit_1_stat = calculate_unit_stat(unit_1, String.to_atom(stat))
unit_2_stat = calculate_unit_stat(unit_2, String.to_atom(stat))

decide_order(unit_1_stat, unit_2_stat, order)
end
)
end

defp decide_order(unit_1_stat, unit_2_stat, :asc) do
cond do
unit_1_stat > unit_2_stat -> true
unit_1_stat == unit_2_stat -> Enum.random([true, false])
true -> false
end
end

defp decide_order(unit_1_stat, unit_2_stat, :desc) do
cond do
unit_1_stat > unit_2_stat -> false
unit_1_stat == unit_2_stat -> Enum.random([true, false])
true -> true
end
end

defp string_to_atom("type"), do: :type
defp string_to_atom("duration"), do: :duration
defp string_to_atom("period"), do: :period
Expand Down
Loading

0 comments on commit 0f8cec5

Please sign in to comment.