Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-404] lowest and highest stat targeting strategies #647

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 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 @@ -621,6 +621,26 @@ defmodule Champions.Battle.Simulator do
[caster.id]
end

defp choose_targets_by_strategy(caster, %{type: %{"lowest" => stat}, target_allies: target_allies}, state) do
lotuuu marked this conversation as resolved.
Show resolved Hide resolved
target_team =
Enum.filter(state.units, fn {_id, unit} -> unit.team == caster.team == target_allies end)

{_, target} =
Enum.min_by(target_team, fn {_id, unit} -> calculate_unit_stat(unit, String.to_atom(stat)) end)

[target.id]
end

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

{_, target} =
Enum.max_by(target_team, fn {_id, unit} -> calculate_unit_stat(unit, String.to_atom(stat)) end)

[target.id]
end

defp choose_targets_by_strategy(caster, %{type: "all", target_allies: target_allies}, state),
do:
state.units
Expand Down Expand Up @@ -1069,20 +1089,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"
Nico-Sanchez marked this conversation as resolved.
Show resolved Hide resolved
end,
count: mechanic.apply_effects_to.targeting_strategy.count || 1,
target_allies: mechanic.apply_effects_to.targeting_strategy.target_allies || false
Expand Down
200 changes: 200 additions & 0 deletions apps/champions/test/battle_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,206 @@ defmodule Champions.Test.BattleTest do
assert "team_2" ==
Champions.Battle.Simulator.run_battle([self_damage_unit], [target_dummy], maximum_steps: maximum_steps).result
end

test "Lowest Health" do
# This test pairs a unit with a basic skill that deals damage to the enemy with the lowest health, against two target dummies.
# The first target dummy has 10 health points and the second one has 9 health points. The dummy with lowest health can kill the opponent in one hit, so if the lowest health targeting strategy fails, the battle will end in a victory for the team_2.
lotuuu marked this conversation as resolved.
Show resolved Hide resolved
# The second dummy will hit team_1 unit once, for half of its health points, so if it hits twice the battle will end in a victory for the team_2.

attacker_cooldown = 1
attacking_character_hp = 20
attacking_character_base_attack = 10
highest_hp_target_dummy_hp = attacking_character_base_attack
lowest_hp_target_dummy_hp = attacking_character_base_attack - 1

# Create a character with a basic skill that will deal 10 damage to all the enemies
basic_skill_params =
TestUtils.build_skill(%{
name: "DealDamage Lowest HP Enemy",
mechanics: [
%{
trigger_delay: 0,
apply_effects_to:
TestUtils.build_apply_effects_to_mechanic(%{
effects: [
TestUtils.build_effect(%{
executions: [
%{
type: "DealDamage",
attack_ratio: 1,
energy_recharge: 0
}
]
})
],
targeting_strategy: %{
type: %{"lowest" => "health"},
target_allies: false
}
})
}
],
cooldown: attacker_cooldown * @miliseconds_per_step
})

{:ok, character} =
TestUtils.build_character(%{
name: "Lowest HP Attacking Character",
basic_skill: basic_skill_params,
ultimate_skill: TestUtils.build_skill(%{name: "Lowest target Skill"}),
base_attack: attacking_character_base_attack,
base_health: attacking_character_hp
})
|> Characters.insert_character()

{:ok, attacking_unit} = TestUtils.build_unit(%{character_id: character.id}) |> Units.insert_unit()
{:ok, attacking_unit} = Units.get_unit(attacking_unit.id)

{:ok, target_dummy_character_lowest_health} =
TestUtils.build_character(%{
base_health: lowest_hp_target_dummy_hp,
base_attack: attacking_character_hp,
name: "Lowest HP Target Dummy",
basic_skill:
TestUtils.build_skill(%{
name: "Lowest HP Target Dummy Basic",
cooldown: 1 + attacker_cooldown * @miliseconds_per_step
}),
ultimate_skill: TestUtils.build_skill(%{name: "Lowest HP Target Dummy Ultimate"})
})
|> Characters.insert_character()

{:ok, target_dummy_lowest_health} =
TestUtils.build_unit(%{character_id: target_dummy_character_lowest_health.id}) |> Units.insert_unit()

{:ok, target_dummy_lowest_health} = Units.get_unit(target_dummy_lowest_health.id)

{:ok, target_dummy_character_highest_health} =
TestUtils.build_character(%{
base_health: highest_hp_target_dummy_hp,
base_attack: trunc(attacking_character_hp / 2),
name: "Highest HP Target Dummy",
basic_skill:
TestUtils.build_skill(%{
name: "Highest HP Target Dummy Basic",
cooldown: 1 + attacker_cooldown * @miliseconds_per_step
}),
ultimate_skill: TestUtils.build_skill(%{name: "Highest HP Target Dummy Ultimate"})
})
|> Characters.insert_character()

{:ok, target_dummy_highest_health} =
TestUtils.build_unit(%{character_id: target_dummy_character_highest_health.id}) |> Units.insert_unit()

{:ok, target_dummy_highest_health} = Units.get_unit(target_dummy_highest_health.id)

assert "team_1" ==
Champions.Battle.Simulator.run_battle(
[attacking_unit],
[target_dummy_lowest_health, target_dummy_highest_health],
maximum_steps: 5
).result
end

test "Highest Health" do
# This test pairs a unit with a basic skill that deals damage to the enemy with the highest health, against two target dummies.
# The first target dummy has 10 health points and the second one has 9 health points. The dummy with highest health can kill the opponent in one hit, so if the highest health targeting strategy fails, the battle will end in a victory for the team_2.
# The second dummy will hit team_1 unit once, for half of its health points, so if it hits twice the battle will end in a victory for the team_2.

attacker_cooldown = 1
attacking_character_hp = 20
attacking_character_base_attack = 10
highest_hp_target_dummy_hp = attacking_character_base_attack
lowest_hp_target_dummy_hp = attacking_character_base_attack - 1

# Create a character with a basic skill that will deal 10 damage to all the enemies
basic_skill_params =
TestUtils.build_skill(%{
name: "DealDamage Highest HP Enemy",
mechanics: [
%{
trigger_delay: 0,
apply_effects_to:
TestUtils.build_apply_effects_to_mechanic(%{
effects: [
TestUtils.build_effect(%{
executions: [
%{
type: "DealDamage",
attack_ratio: 1,
energy_recharge: 0
}
]
})
],
targeting_strategy: %{
type: %{"highest" => "health"},
target_allies: false
}
})
}
],
cooldown: attacker_cooldown * @miliseconds_per_step
})

{:ok, character} =
TestUtils.build_character(%{
name: "Highest HP Attacking Character",
basic_skill: basic_skill_params,
ultimate_skill: TestUtils.build_skill(%{name: "Highest target ultimate Skill"}),
base_attack: attacking_character_base_attack,
base_health: attacking_character_hp
})
|> Characters.insert_character()

{:ok, attacking_unit} = TestUtils.build_unit(%{character_id: character.id}) |> Units.insert_unit()
{:ok, attacking_unit} = Units.get_unit(attacking_unit.id)

{:ok, target_dummy_character_lowest_health} =
TestUtils.build_character(%{
base_health: lowest_hp_target_dummy_hp,
base_attack: trunc(attacking_character_hp / 2),
name: "Lowest HP Dummy",
basic_skill:
TestUtils.build_skill(%{
name: "Lowest HP Dummy Basic",
cooldown: 1 + attacker_cooldown * @miliseconds_per_step
}),
ultimate_skill: TestUtils.build_skill(%{name: "Lowest HP Dummy Ultimate"})
})
|> Characters.insert_character()

{:ok, target_dummy_lowest_health} =
TestUtils.build_unit(%{character_id: target_dummy_character_lowest_health.id}) |> Units.insert_unit()

{:ok, target_dummy_lowest_health} = Units.get_unit(target_dummy_lowest_health.id)

{:ok, target_dummy_character_highest_health} =
TestUtils.build_character(%{
base_health: highest_hp_target_dummy_hp,
base_attack: attacking_character_hp,
name: "Highest HP Dummy",
basic_skill:
TestUtils.build_skill(%{
name: "Highest HP Dummy Basic",
cooldown: 1 + attacker_cooldown * @miliseconds_per_step
}),
ultimate_skill: TestUtils.build_skill(%{name: "Highest HP Dummy Ultimate"})
})
|> Characters.insert_character()

{:ok, target_dummy_highest_health} =
TestUtils.build_unit(%{character_id: target_dummy_character_highest_health.id}) |> Units.insert_unit()

{:ok, target_dummy_highest_health} = Units.get_unit(target_dummy_highest_health.id)

assert "team_1" ==
Champions.Battle.Simulator.run_battle(
[attacking_unit],
[target_dummy_lowest_health, target_dummy_highest_health],
maximum_steps: 5
).result
end
end

describe "Items" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ defmodule GameBackend.Units.Skills.Mechanics.TargetingStrategies.Type do

def cast(%{factions: factions}), do: {:ok, %{factions: factions}}
def cast(%{classes: classes}), do: {:ok, %{classes: classes}}
def cast(%{lowest: attribute}), do: {:ok, %{lowest: attribute}}
def cast(%{highest: attribute}), do: {:ok, %{highest: attribute}}
def cast(%{lowest: attribute}), do: {:ok, %{"lowest" => attribute}}
lotuuu marked this conversation as resolved.
Show resolved Hide resolved
def cast(%{highest: attribute}), do: {:ok, %{"highest" => attribute}}

# Needed for the case in which we decode from the DB (Further explanation in: https://hexdocs.pm/ecto/Ecto.Schema.html#embeds_one/3-encoding-and-decoding)
def cast(%{"factions" => factions}), do: {:ok, {"factions", factions}}
def cast(%{"classes" => classes}), do: {:ok, {"classes", classes}}
def cast(%{"lowest" => attribute}), do: {:ok, %{lowest: attribute}}
def cast(%{"highest" => attribute}), do: {:ok, %{highest: attribute}}
def cast(%{"lowest" => attribute}), do: {:ok, %{"lowest" => attribute}}
def cast(%{"highest" => attribute}), do: {:ok, %{"highest" => attribute}}

def cast(_), do: :error

Expand Down
Loading