diff --git a/lib/teiserver/battle/balance/default_balance.ex b/lib/teiserver/battle/balance/default_balance.ex new file mode 100644 index 000000000..3e5d576e9 --- /dev/null +++ b/lib/teiserver/battle/balance/default_balance.ex @@ -0,0 +1,63 @@ +defmodule Teiserver.Battle.Balance.DefaultBalance do + @moduledoc """ + This will call other balancers depending on circumstances + """ + alias Teiserver.Battle.Balance.SplitNoobs + alias Teiserver.Battle.Balance.LoserPicks + alias Teiserver.Battle.Balance.BalanceTypes, as: BT + alias Teiserver.Battle.Balance.DefaultBalanceTypes, as: DB + + @doc """ + Main entry point used by balance_lib + """ + @spec perform([BT.expanded_group()], non_neg_integer(), list()) :: any() + def perform(expanded_group, team_count, opts \\ []) do + get_balance_algorithm(expanded_group, team_count).perform(expanded_group, team_count, opts) + end + + @spec get_balance_algorithm([BT.expanded_group()], integer()) :: + any() + def get_balance_algorithm(expanded_group, team_count) do + cond do + team_count != 2 -> + LoserPicks + + true -> + players = flatten_members(expanded_group) + has_noobs? = has_noobs?(players) + + cond do + has_noobs? -> SplitNoobs + true -> LoserPicks + end + end + end + + @doc """ + Converts the input to a simple list of players + """ + @spec flatten_members([BT.expanded_group()]) :: [DB.player()] + def flatten_members(expanded_group) do + # We only care about ranks and uncertainties for now + # However, in the future we may use other data to decide what balance algorithm to use, + # e.g. whether there are parties or not, whether it's a high rating lobby, etc. + for %{ + ranks: ranks, + uncertainties: uncertainties + } <- expanded_group, + # Zipping will create binary tuples from 2 lists + {rank, uncertainty} <- + Enum.zip([ranks, uncertainties]), + do: %{ + uncertainty: uncertainty, + rank: rank + } + end + + @spec has_noobs?([DB.player()]) :: any() + def has_noobs?(players) do + Enum.any?(players, fn x -> + SplitNoobs.is_newish_player?(x.rank, x.uncertainty) + end) + end +end diff --git a/lib/teiserver/battle/balance/default_balance_types.ex b/lib/teiserver/battle/balance/default_balance_types.ex new file mode 100644 index 000000000..eee37e4e2 --- /dev/null +++ b/lib/teiserver/battle/balance/default_balance_types.ex @@ -0,0 +1,8 @@ +defmodule Teiserver.Battle.Balance.DefaultBalanceTypes do + @moduledoc false + + @type player :: %{ + rank: number(), + uncertainty: number() + } +end diff --git a/lib/teiserver/battle/balance/loser_picks.ex b/lib/teiserver/battle/balance/loser_picks.ex index 623783017..951afab28 100644 --- a/lib/teiserver/battle/balance/loser_picks.ex +++ b/lib/teiserver/battle/balance/loser_picks.ex @@ -20,6 +20,7 @@ defmodule Teiserver.Battle.Balance.LoserPicks do alias Teiserver.Battle.Balance.BalanceTypes, as: BT import Teiserver.Helper.NumberHelper, only: [round: 2] + @splitter "------------------------------------------------------" @type algorithm_state :: %{ teams: map, logs: list, @@ -81,9 +82,11 @@ defmodule Teiserver.Battle.Balance.LoserPicks do max_teamsize = (total_members / Enum.count(teams)) |> :math.ceil() |> round() + intial_logs = [@splitter, "Algorithm: loser_picks", @splitter] + state = %{ teams: teams, - logs: group_logs, + logs: intial_logs ++ group_logs, solo_players: solo_players, group_pairs: group_pairs, max_teamsize: max_teamsize, diff --git a/lib/teiserver/battle/balance/split_noobs.ex b/lib/teiserver/battle/balance/split_noobs.ex index 251bb9e57..57e3832ca 100644 --- a/lib/teiserver/battle/balance/split_noobs.ex +++ b/lib/teiserver/battle/balance/split_noobs.ex @@ -319,11 +319,7 @@ defmodule Teiserver.Battle.Balance.SplitNoobs do id: id, uncertainty: uncertainty, rank: rank, - in_party?: - cond do - count <= 1 -> false - true -> true - end + in_party?: count > 1 } end diff --git a/lib/teiserver/battle/libs/balance_lib.ex b/lib/teiserver/battle/libs/balance_lib.ex index 19e487510..d3077e2fb 100644 --- a/lib/teiserver/battle/libs/balance_lib.ex +++ b/lib/teiserver/battle/libs/balance_lib.ex @@ -27,8 +27,6 @@ defmodule Teiserver.Battle.BalanceLib do # which one will get to pick first @shuffle_first_pick true - @default_balance_algorithm "loser_picks" - @spec defaults() :: map() def defaults() do %{ @@ -43,8 +41,7 @@ defmodule Teiserver.Battle.BalanceLib do end def get_default_algorithm() do - # For now it's a constant but this could be moved to a configurable value - @default_balance_algorithm + Config.get_site_config_cache("teiserver.Default balance algorithm") end @spec algorithm_modules() :: %{String.t() => module} @@ -53,7 +50,8 @@ defmodule Teiserver.Battle.BalanceLib do "loser_picks" => Teiserver.Battle.Balance.LoserPicks, "force_party" => Teiserver.Battle.Balance.ForceParty, "brute_force" => Teiserver.Battle.Balance.BruteForce, - "split_noobs" => Teiserver.Battle.Balance.SplitNoobs + "split_noobs" => Teiserver.Battle.Balance.SplitNoobs, + "default" => Teiserver.Battle.Balance.DefaultBalance } end @@ -65,7 +63,7 @@ defmodule Teiserver.Battle.BalanceLib do if(is_moderator) do Teiserver.Battle.BalanceLib.algorithm_modules() |> Map.keys() else - mod_only = ["force_party", "brute_force"] + mod_only = ["force_party", "brute_force", "loser_picks"] Teiserver.Battle.BalanceLib.algorithm_modules() |> Map.drop(mod_only) |> Map.keys() end end diff --git a/lib/teiserver/libs/teiserver_configs.ex b/lib/teiserver/libs/teiserver_configs.ex index 68202fa57..c66992345 100644 --- a/lib/teiserver/libs/teiserver_configs.ex +++ b/lib/teiserver/libs/teiserver_configs.ex @@ -327,6 +327,16 @@ defmodule Teiserver.TeiserverConfigs do default: true }) + add_site_config_type(%{ + key: "teiserver.Default balance algorithm", + section: "Lobbies", + type: "select", + default: "loser_picks", + permissions: ["Admin"], + description: "The default balance algorithm", + opts: [choices: ["loser_picks", "default"]] + }) + add_site_config_type(%{ key: "teiserver.Curse word score A", section: "Lobbies", diff --git a/lib/teiserver/lobby/commands/explain_command.ex b/lib/teiserver/lobby/commands/explain_command.ex index c4c693820..3c167dfa5 100644 --- a/lib/teiserver/lobby/commands/explain_command.ex +++ b/lib/teiserver/lobby/commands/explain_command.ex @@ -8,7 +8,7 @@ defmodule Teiserver.Lobby.Commands.ExplainCommand do alias Teiserver.{Account, Battle, Coordinator} import Teiserver.Helper.NumberHelper, only: [round: 2] - @splitter "---------------------------" + @splitter "------------------------------------------------------" @impl true @spec name() :: String.t() diff --git a/lib/teiserver/lobby/libs/lobby_restrictions.ex b/lib/teiserver/lobby/libs/lobby_restrictions.ex index 09e6b67ba..3715cbb6b 100644 --- a/lib/teiserver/lobby/libs/lobby_restrictions.ex +++ b/lib/teiserver/lobby/libs/lobby_restrictions.ex @@ -217,9 +217,6 @@ defmodule Teiserver.Lobby.LobbyRestrictions do "To restrict this lobby to players who are new, use either:", "!maxchevlevel ", "!maxratinglevel ", - "", - "To ensure new players are distributed evenly across teams:", - "!balancealgorithm split_noobs", "" ] diff --git a/test/teiserver/battle/balance_lib_internal_test.exs b/test/teiserver/battle/balance_lib_internal_test.exs index 4eb69ea04..9e0718f78 100644 --- a/test/teiserver/battle/balance_lib_internal_test.exs +++ b/test/teiserver/battle/balance_lib_internal_test.exs @@ -101,11 +101,11 @@ defmodule Teiserver.Battle.BalanceLibInternalTest do is_moderator = true result = BalanceLib.get_allowed_algorithms(is_moderator) - assert result == ["brute_force", "force_party", "loser_picks", "split_noobs"] + assert result == ["brute_force", "default", "force_party", "loser_picks", "split_noobs"] is_moderator = false result = BalanceLib.get_allowed_algorithms(is_moderator) - assert result == ["loser_picks", "split_noobs"] + assert result == ["default", "split_noobs"] end defp create_test_users do diff --git a/test/teiserver/lobby/commands/explain_command_test.exs b/test/teiserver/lobby/commands/explain_command_test.exs index 9405b0849..2ffe56499 100644 --- a/test/teiserver/lobby/commands/explain_command_test.exs +++ b/test/teiserver/lobby/commands/explain_command_test.exs @@ -44,9 +44,9 @@ defmodule Teiserver.Lobby.Commands.ExplainCommandTest do channel: "teiserver_client_messages:#{user.id}", event: :received_direct_message, message_content: [ - "---------------------------", + "------------------------------------------------------", "No balance has been created for this room", - "---------------------------" + "------------------------------------------------------" ], sender_id: Coordinator.get_coordinator_userid() }