Skip to content

Commit

Permalink
Add captain diff penalty to respect avoids (beyond-all-reason#509)
Browse files Browse the repository at this point in the history
* Add captain diff penalty to respect avoids

* Add stdev diff penalty to brute_force and split_noobs
  • Loading branch information
jauggy authored Oct 21, 2024
1 parent b1f6595 commit 4c0f904
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 112 deletions.
59 changes: 35 additions & 24 deletions lib/teiserver/battle/balance/brute_force.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule Teiserver.Battle.Balance.BruteForce do
import Teiserver.Helper.NumberHelper, only: [format: 1]
require Integer

@stdev_diff_importance 4
@party_importance 7
@splitter "------------------------------------------------------"

Expand Down Expand Up @@ -110,45 +111,55 @@ defmodule Teiserver.Battle.Balance.BruteForce do
players_with_index = Enum.with_index(players)

# Go through every possibility and get the combination with the lowest score
result =
Enum.map(combos, fn x ->
get_players_from_indexes(x, players_with_index)
end)
|> Enum.map(fn team ->
result = score_combo(team, players, parties)
Map.put(result, :first_team, team)
end)
|> Enum.min_by(fn z ->
z.score
end)

first_team = result.first_team
Enum.map(combos, fn x ->
get_players_from_indexes(x, players_with_index)
end)
|> Enum.map(fn team ->
score_combo(team, players, parties)
end)
|> Enum.min_by(fn z ->
z.score
end)
end

second_team =
players
|> Enum.filter(fn x ->
!Enum.any?(first_team, fn y ->
y.id == x.id
end)
end)
@spec get_second_team([BF.player()], [BF.player()]) :: [BF.player()]
def get_second_team(first_team, all_players) do
all_players
|> Enum.filter(fn player -> !Enum.any?(first_team, fn x -> x.id == player.id end) end)
end

Map.put(result, :second_team, second_team)
@spec get_st_dev([BF.player()]) :: any()
def get_st_dev(team) do
if(length(team) > 0) do
ratings = Enum.map(team, fn player -> player.rating end)
Statistics.stdev(ratings)
else
0
end
end

@spec score_combo([BF.player()], [BF.player()], [String.t()]) :: any()
@spec score_combo([BF.player()], [BF.player()], [String.t()]) :: BF.combo_result()
def score_combo(first_team, all_players, parties) do
second_team = get_second_team(first_team, all_players)
first_team_rating = get_team_rating(first_team)
both_team_rating = get_team_rating(all_players)

rating_diff_penalty = abs(both_team_rating - first_team_rating * 2)
broken_party_penalty = count_broken_parties(first_team, parties) * @party_importance

score = rating_diff_penalty + broken_party_penalty
stdev_diff_penalty =
abs(get_st_dev(first_team) - get_st_dev(second_team)) *
@stdev_diff_importance

score = rating_diff_penalty + broken_party_penalty + stdev_diff_penalty

%{
score: score,
rating_diff_penalty: rating_diff_penalty,
broken_party_penalty: broken_party_penalty
broken_party_penalty: broken_party_penalty,
stdev_diff_penalty: stdev_diff_penalty,
first_team: first_team,
second_team: second_team
}
end

Expand Down
62 changes: 38 additions & 24 deletions lib/teiserver/battle/balance/brute_force_avoid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule Teiserver.Battle.Balance.BruteForceAvoid do
@max_team_diff_importance 10000
@party_importance 1000
@avoid_importance 10
@captain_diff_importance 1

def get_best_combo(players, avoids, parties) do
potential_teams = potential_teams(length(players))
Expand All @@ -34,27 +35,13 @@ defmodule Teiserver.Battle.Balance.BruteForceAvoid do
players_with_index = Enum.with_index(players)

# Go through every possibility and get the combination with the lowest score
result =
Enum.map(combos, fn x ->
team = get_players_from_indexes(x, players_with_index)
result = score_combo(team, players, avoids, parties)
Map.put(result, :first_team, team)
end)
|> Enum.min_by(fn z ->
z.score
end)

first_team = result.first_team

second_team =
players
|> Enum.filter(fn x ->
!Enum.any?(first_team, fn y ->
y.id == x.id
end)
end)

Map.put(result, :second_team, second_team)
Enum.map(combos, fn x ->
team = get_players_from_indexes(x, players_with_index)
score_combo(team, players, avoids, parties)
end)
|> Enum.min_by(fn z ->
z.score
end)
end

@spec potential_teams(integer()) :: any()
Expand All @@ -71,11 +58,34 @@ defmodule Teiserver.Battle.Balance.BruteForceAvoid do
max(total_lobby_rating / num_teams * percentage_of_team, @max_team_diff_abs)
end

@spec score_combo([BF.player()], [BF.player()], [[number()]], [[number()]]) :: any()
@spec get_second_team([BF.player()], [BF.player()]) :: [BF.player()]
def get_second_team(first_team, all_players) do
all_players
|> Enum.filter(fn player -> !Enum.any?(first_team, fn x -> x.id == player.id end) end)
end

@spec get_captain_rating([BF.player()]) :: any()
def get_captain_rating(team) do
if(length(team) > 0) do
captain = Enum.max_by(team, fn player -> player.rating end, &>=/2)
captain.rating
else
0
end
end

@spec score_combo([BF.player()], [BF.player()], [[number()]], [[number()]]) :: BF.combo_result()
def score_combo(first_team, all_players, avoids, parties) do
second_team = get_second_team(first_team, all_players)
first_team_rating = get_team_rating(first_team)

both_team_rating = get_team_rating(all_players)
rating_diff_penalty = abs(both_team_rating - first_team_rating * 2)

captain_diff_penalty =
abs(get_captain_rating(first_team) - get_captain_rating(second_team)) *
@captain_diff_importance

num_teams = 2

max_team_diff_penalty =
Expand All @@ -100,13 +110,17 @@ defmodule Teiserver.Battle.Balance.BruteForceAvoid do
end

score =
rating_diff_penalty + broken_avoid_penalty + broken_party_penalty + max_team_diff_penalty
rating_diff_penalty + broken_avoid_penalty + broken_party_penalty + max_team_diff_penalty +
captain_diff_penalty

%{
score: score,
rating_diff_penalty: rating_diff_penalty,
broken_avoid_penalty: broken_avoid_penalty,
broken_party_penalty: broken_party_penalty
broken_party_penalty: broken_party_penalty,
captain_diff_penalty: captain_diff_penalty,
second_team: second_team,
first_team: first_team
}
end

Expand Down
1 change: 1 addition & 0 deletions lib/teiserver/battle/balance/brute_force_avoid_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule Teiserver.Battle.Balance.BruteForceAvoidTypes do
broken_avoid_penalty: number(),
broken_party_penalty: number(),
rating_diff_penalty: number(),
captain_diff_penalty: number(),
score: number(),
first_team: [player()],
second_team: [player()]
Expand Down
1 change: 1 addition & 0 deletions lib/teiserver/battle/balance/brute_force_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Teiserver.Battle.Balance.BruteForceTypes do
@type combo_result :: %{
broken_party_penalty: number(),
rating_diff_penalty: number(),
stdev_diff_penalty: number(),
score: number(),
first_team: [player()],
second_team: [player()]
Expand Down
1 change: 1 addition & 0 deletions lib/teiserver/battle/balance/respect_avoids.ex
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ defmodule Teiserver.Battle.Balance.RespectAvoids do
"Team rating diff penalty: #{format(combo_result.rating_diff_penalty)}",
"Broken party penalty: #{combo_result.broken_party_penalty}",
"Broken avoid penalty: #{combo_result.broken_avoid_penalty}",
"Captain rating diff penalty: #{format(combo_result.captain_diff_penalty)}",
"Score: #{format(combo_result.score)} (lower is better)",
@splitter,
"Draft remaining players (ordered from best to worst).",
Expand Down
1 change: 1 addition & 0 deletions lib/teiserver/battle/balance/split_noobs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ defmodule Teiserver.Battle.Balance.SplitNoobs do
"Brute force result:",
"Team rating diff penalty: #{format(combo_result.rating_diff_penalty)}",
"Broken party penalty: #{combo_result.broken_party_penalty}",
"Stdev diff penalty: #{format(combo_result.stdev_diff_penalty)}",
"Score: #{format(combo_result.score)} (lower is better)",
@splitter,
"Draft remaining players (ordered from best to worst).",
Expand Down
83 changes: 83 additions & 0 deletions test/teiserver/battle/brute_force_avoid_internal_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule Teiserver.Battle.BruteForceAvoidInternalTest do
@moduledoc """
Can run all balance tests via
mix test --only balance_test
"""
use ExUnit.Case
@moduletag :balance_test
alias Teiserver.Battle.Balance.BruteForceAvoid

test "can get second team" do
players = [
%{name: "kyutoryu", rating: 12.25, id: 1},
%{name: "fbots1998", rating: 13.98, id: 2},
%{name: "Dixinormus", rating: 18.28, id: 3},
%{name: "HungDaddy", rating: 2.8, id: 4},
%{name: "SLOPPYGAGGER", rating: 8.89, id: 5},
%{name: "jauggy", rating: 20.49, id: 6},
%{name: "reddragon2010", rating: 18.4, id: 7},
%{name: "Aposis", rating: 20.42, id: 8},
%{name: "MaTThiuS_82", rating: 8.26, id: 9},
%{name: "Noody", rating: 17.64, id: 10},
%{name: "[DTG]BamBin0", rating: 20.06, id: 11},
%{name: "barmalev", rating: 3.58, id: 12}
]

first_team = [
%{name: "kyutoryu", rating: 12.25, id: 1},
%{name: "fbots1998", rating: 13.98, id: 2},
%{name: "Dixinormus", rating: 18.28, id: 3},
%{name: "HungDaddy", rating: 2.8, id: 4},
%{name: "SLOPPYGAGGER", rating: 8.89, id: 5},
%{name: "jauggy", rating: 20.49, id: 6}
]

result = BruteForceAvoid.get_second_team(first_team, players)

assert result == [
%{id: 7, name: "reddragon2010", rating: 18.4},
%{id: 8, name: "Aposis", rating: 20.42},
%{id: 9, name: "MaTThiuS_82", rating: 8.26},
%{id: 10, name: "Noody", rating: 17.64},
%{id: 11, name: "[DTG]BamBin0", rating: 20.06},
%{id: 12, name: "barmalev", rating: 3.58}
]
end

test "can get second team - side cases" do
players = []

first_team = []

result = BruteForceAvoid.get_second_team(first_team, players)

assert result == []

players = [%{id: 7, name: "reddragon2010", rating: 18.4}]

first_team = [%{id: 7, name: "reddragon2010", rating: 18.4}]

result = BruteForceAvoid.get_second_team(first_team, players)

assert result == []
end

test "can get captain rating" do
first_team = [
%{name: "kyutoryu", rating: 12.25, id: 1},
%{name: "fbots1998", rating: 13.98, id: 2},
%{name: "Dixinormus", rating: 18.28, id: 3},
%{name: "HungDaddy", rating: 2.8, id: 4},
%{name: "SLOPPYGAGGER", rating: 8.89, id: 5},
%{name: "jauggy", rating: 20.49, id: 6}
]

result = BruteForceAvoid.get_captain_rating(first_team)

assert result == 20.49

result = BruteForceAvoid.get_captain_rating([])

assert result == 0
end
end
50 changes: 34 additions & 16 deletions test/teiserver/battle/brute_force_internal_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -118,32 +118,50 @@ defmodule Teiserver.Battle.BruteForceInternalTest do

assert result == %{
broken_party_penalty: 0,
first_team: [
%{id: 1, name: "kyutoryu", rating: 12.25},
%{id: 2, name: "fbots1998", rating: 13.98},
%{id: 3, name: "Dixinormus", rating: 18.28},
%{id: 4, name: "HungDaddy", rating: 2.8},
%{id: 5, name: "SLOPPYGAGGER", rating: 8.89},
%{id: 6, name: "jauggy", rating: 20.49}
],
rating_diff_penalty: 11.670000000000044,
score: 11.670000000000044
score: 13.987048974705417,
second_team: [
%{id: 7, name: "reddragon2010", rating: 18.4},
%{id: 8, name: "Aposis", rating: 20.42},
%{id: 9, name: "MaTThiuS_82", rating: 8.26},
%{id: 10, name: "Noody", rating: 17.64},
%{id: 11, name: "[DTG]BamBin0", rating: 20.06},
%{id: 12, name: "barmalev", rating: 3.58}
],
stdev_diff_penalty: 2.317048974705372
}

best_combo = BruteForce.get_best_combo(combos, input.players, input.parties)

assert best_combo == %{
broken_party_penalty: 0,
rating_diff_penalty: 0.5100000000000477,
score: 0.5100000000000477,
first_team: [
%{id: 1, name: "kyutoryu", rating: 12.25},
%{id: 2, name: "fbots1998", rating: 13.98},
%{id: 5, name: "SLOPPYGAGGER", rating: 8.89},
%{id: 6, name: "jauggy", rating: 20.49},
%{id: 7, name: "reddragon2010", rating: 18.4},
%{id: 9, name: "MaTThiuS_82", rating: 8.26}
%{id: 8, name: "Aposis", rating: 20.42},
%{id: 12, name: "barmalev", rating: 3.58}
],
rating_diff_penalty: 5.830000000000041,
score: 7.322550984245979,
second_team: [
%{id: 3, name: "Dixinormus", rating: 18.28},
%{id: 4, name: "HungDaddy", rating: 2.8},
%{id: 8, name: "Aposis", rating: 20.42},
%{id: 7, name: "reddragon2010", rating: 18.4},
%{id: 9, name: "MaTThiuS_82", rating: 8.26},
%{id: 10, name: "Noody", rating: 17.64},
%{id: 11, name: "[DTG]BamBin0", rating: 20.06},
%{id: 12, name: "barmalev", rating: 3.58}
]
%{id: 11, name: "[DTG]BamBin0", rating: 20.06}
],
stdev_diff_penalty: 1.4925509842459377
}

result = BruteForce.standardise_result(best_combo, input.parties) |> Map.drop([:logs])
Expand All @@ -155,19 +173,19 @@ defmodule Teiserver.Battle.BruteForceInternalTest do
%{count: 1, group_rating: 13.98, members: [2], ratings: [13.98]},
%{count: 1, group_rating: 8.89, members: [5], ratings: [8.89]},
%{count: 1, group_rating: 20.49, members: [6], ratings: [20.49]},
%{count: 1, group_rating: 18.4, members: [7], ratings: [18.4]},
%{count: 1, group_rating: 8.26, members: [9], ratings: [8.26]}
%{count: 1, group_rating: 20.42, members: ~c"\b", ratings: [20.42]},
%{count: 1, group_rating: 3.58, members: ~c"\f", ratings: [3.58]}
],
2 => [
%{count: 1, group_rating: 18.28, members: [3], ratings: [18.28]},
%{count: 1, group_rating: 2.8, members: [4], ratings: [2.8]},
%{count: 1, group_rating: 20.42, members: [8], ratings: [20.42]},
%{count: 1, group_rating: 17.64, members: [10], ratings: [17.64]},
%{count: 1, group_rating: 20.06, members: [11], ratings: [20.06]},
%{count: 1, group_rating: 3.58, members: [12], ratings: [3.58]}
%{count: 1, group_rating: 18.4, members: ~c"\a", ratings: [18.4]},
%{count: 1, group_rating: 8.26, members: ~c"\t", ratings: [8.26]},
%{count: 1, members: ~c"\n", ratings: [17.64], group_rating: 17.64},
%{count: 1, group_rating: 20.06, members: ~c"\v", ratings: [20.06]}
]
},
team_players: %{1 => [1, 2, 5, 6, 7, 9], 2 => [3, 4, 8, 10, 11, 12]}
team_players: %{1 => [1, 2, 5, 6, 8, 12], 2 => [3, 4, 7, 9, 10, 11]}
}
end

Expand Down
Loading

0 comments on commit 4c0f904

Please sign in to comment.