Skip to content

Commit

Permalink
Merge pull request #328 from jauggy/jauggy/split-one-chevs-v2
Browse files Browse the repository at this point in the history
Update split_one_chevs balancemode v2
  • Loading branch information
L-e-x-o-n authored Jul 10, 2024
2 parents 89fb967 + f9a2ccb commit 4e28cd9
Show file tree
Hide file tree
Showing 20 changed files with 617 additions and 153 deletions.
3 changes: 2 additions & 1 deletion lib/teiserver/battle/balance/balance_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule Teiserver.Battle.Balance.BalanceTypes do
names: [String.t()],
ranks: [non_neg_integer()],
group_rating: rating_value(),
count: non_neg_integer()
count: non_neg_integer(),
uncertainties: [number()]
}

@type group() :: %{
Expand Down
9 changes: 5 additions & 4 deletions lib/teiserver/battle/balance/cheeky_switcher_smart.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ defmodule Teiserver.Battle.Balance.CheekySwitcherSmart do
# Alias the types
alias Teiserver.Battle.BalanceLib
alias Teiserver.Battle.Balance.BalanceTypes, as: BT
import Teiserver.Helper.NumberHelper, only: [format: 1]

# @type algorithm_state :: %{
# teams: map,
Expand Down Expand Up @@ -158,7 +159,7 @@ defmodule Teiserver.Battle.Balance.CheekySwitcherSmart do
|> Enum.map(fn {group, _} ->
group.names
|> Enum.with_index()
|> Enum.map(fn {name, i} -> "#{name}[#{Enum.at(group.ratings, i)}]" end)
|> Enum.map(fn {name, i} -> "#{name}[#{format(Enum.at(group.ratings, i))}]" end)
end)
|> List.flatten()
|> Enum.join(",")
Expand All @@ -168,7 +169,7 @@ defmodule Teiserver.Battle.Balance.CheekySwitcherSmart do
|> Enum.map(fn {group, _} ->
group.names
|> Enum.with_index()
|> Enum.map(fn {name, i} -> "#{name}[#{Enum.at(group.ratings, i)}]" end)
|> Enum.map(fn {name, i} -> "#{name}[#{format(Enum.at(group.ratings, i))}]" end)
end)
|> List.flatten()
|> Enum.join(",")
Expand Down Expand Up @@ -429,11 +430,11 @@ defmodule Teiserver.Battle.Balance.CheekySwitcherSmart do

if next_group.count > 1 do
[
"Group picked #{names |> Enum.join(", ")} for team #{team_key}, adding #{group_rating} points for a new total of #{round(existing_team_rating + group_rating)}"
"Group picked #{names |> Enum.join(", ")} for team #{team_key}, adding #{format(group_rating)} points for a new total of #{round(existing_team_rating + group_rating)}"
]
else
[
"Picked #{Enum.at(names, 0)} for team #{team_key}, adding #{group_rating} points for a new total of #{round(existing_team_rating + group_rating)}"
"Picked #{Enum.at(names, 0)} for team #{team_key}, adding #{format(group_rating)} points for a new total of #{round(existing_team_rating + group_rating)}"
]
end
end
Expand Down
96 changes: 77 additions & 19 deletions lib/teiserver/battle/balance/split_one_chevs.ex
Original file line number Diff line number Diff line change
@@ -1,53 +1,104 @@
defmodule Teiserver.Battle.Balance.SplitOneChevs do
@moduledoc """
This balance algorithm first sorts the users by visible OS (match rating) descending. Then all rank=0 (one chevs) will be placed at the bottom of this sorted list.
Overview:
The goal of this algorithm is to mimic how a human would draft players given the visual information in a lobby.
Humans will generally avoid drafting overrated new players.
Next a team will be chosen to be the picking team. The picking team is the team with the least amount of players. If tied, then the team with the lowest total rating.
Details:
The team with the least amount of players will pick an unchosen player. If there are multiple teams tied for
the lowest player count, then the team with the lowest match rating picks.
Next the picking team will pick the player at the top of the sorted list.
Your team will prefer 3Chev+ players with high OS. If your team must pick a 1-2Chev player,
it will prefer lower uncertainty.
This is repeated until all players are chosen.
This is repeated until all players are chosen.
This algorithm completely ignores parties.
This algorithm completely ignores parties.
"""
alias Teiserver.Battle.Balance.SplitOneChevsTypes, as: ST
alias Teiserver.Battle.Balance.BalanceTypes, as: BT
import Teiserver.Helper.NumberHelper, only: [format: 1]

@splitter "---------------------------"

@doc """
Main entry point used by balance_lib
See split_one_chevs_internal_test.exs for sample input
"""
@spec perform([BT.expanded_group()], non_neg_integer(), list()) :: any()
def perform(expanded_group, team_count, _opts \\ []) do
members = flatten_members(expanded_group) |> sort_members()
%{teams: teams, logs: logs} = assign_teams(members, team_count)
standardise_result(teams, logs)
def perform(expanded_group, team_count, opts \\ []) do
if has_enough_noobs?(expanded_group) do
members = flatten_members(expanded_group) |> sort_members()
%{teams: teams, logs: logs} = assign_teams(members, team_count)
standardise_result(teams, logs)
else
# Not enough noobs; so call another balancer
result = Teiserver.Battle.Balance.LoserPicks.perform(expanded_group, team_count, opts)

new_logs =
["Not enough new players; will use another balance algorithm.", @splitter, result.logs]
|> List.flatten()

Map.put(result, :logs, new_logs)
end
end

@doc """
For now this simply checks there is at least a single 1chev or a single 2chev.
However we could modify this in the future to be more complicated e.g. at least a single 1chev
or at least two, 2chevs.
"""
@spec has_enough_noobs?([BT.expanded_group()]) :: bool()
def has_enough_noobs?(expanded_group) do
ranks =
Enum.map(expanded_group, fn x ->
Map.get(x, :ranks, [])
end)
|> List.flatten()

Enum.any?(ranks, fn x ->
x < 2
end)
end

@doc """
Remove all groups/parties and treats everyone as solo players. This algorithm doesn't support parties.
See split_one_chevs_internal_test.exs for sample input
"""
def flatten_members(expanded_group) do
for %{members: members, ratings: ratings, ranks: ranks, names: names} <- expanded_group,
for %{
members: members,
ratings: ratings,
ranks: ranks,
names: names,
uncertainties: uncertainties
} <- expanded_group,
# Zipping will create binary tuples from 2 lists
{id, rating, rank, name} <- Enum.zip([members, ratings, ranks, names]),
{id, rating, rank, name, uncertainty} <-
Enum.zip([members, ratings, ranks, names, uncertainties]),
# Create result value
do: %{member_id: id, rating: rating, rank: rank, name: name}
do: %{
member_id: id,
rating: rating,
rank: rank,
name: name,
uncertainty: uncertainty
}
end

@doc """
Sorts members by rating but puts one chevs at the bottom
See split_one_chevs_internal_test.exs for sample input
Experienced players will be on top followed by noobs.
Experienced players are 3+ Chevs. They will be sorted with higher OS on top.
Noobs are 1-2 Chevs. They will be sorted with lower uncertainty on top.
"""
def sort_members(members) do
non_noobs = Enum.filter(members, fn x -> x.rank != 0 end)
noobs = Enum.filter(members, fn x -> x.rank == 0 end)
non_noobs = Enum.filter(members, fn x -> x.rank >= 2 end)
noobs = Enum.filter(members, fn x -> x.rank < 2 end)

[
Enum.sort_by(non_noobs, fn x -> x.rating end, &>=/2),
Enum.sort_by(noobs, fn x -> x.rating end, &>=/2)
Enum.sort_by(noobs, fn x -> x.uncertainty end, &<=/2)
]
|> List.flatten()
end
Expand All @@ -59,14 +110,21 @@ defmodule Teiserver.Battle.Balance.SplitOneChevs do
def assign_teams(member_list, number_of_teams) do
default_acc = %{
teams: create_empty_teams(number_of_teams),
logs: ["Begin split_one_chevs balance"]
logs: [
"Algorithm: split_one_chevs",
@splitter,
"Your team will try and pick 3Chev+ players first, with preference for higher OS. If 1-2Chevs are the only remaining players, then lower uncertainty is preferred.",
@splitter
]
}

Enum.reduce(member_list, default_acc, fn x, acc ->
picking_team = get_picking_team(acc.teams)
update_picking_team = Map.merge(picking_team, %{members: [x | picking_team.members]})
username = x.name
new_log = "#{username} (Chev: #{x.rank + 1}) picked for Team #{picking_team.team_id}"

new_log =
"#{username} (#{format(x.rating)}, σ: #{format(x.uncertainty)}, Chev: #{x.rank + 1}) picked for Team #{picking_team.team_id}"

%{
teams: [update_picking_team | get_non_picking_teams(acc.teams, picking_team)],
Expand Down
Loading

0 comments on commit 4e28cd9

Please sign in to comment.