diff --git a/lib/teiserver/battle/balance/cheeky_switcher_smart.ex b/lib/teiserver/battle/balance/cheeky_switcher_smart.ex index 0f91475b3..8964645ec 100644 --- a/lib/teiserver/battle/balance/cheeky_switcher_smart.ex +++ b/lib/teiserver/battle/balance/cheeky_switcher_smart.ex @@ -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, @@ -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(",") @@ -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(",") @@ -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 diff --git a/lib/teiserver/battle/balance/split_one_chevs.ex b/lib/teiserver/battle/balance/split_one_chevs.ex index 98ef99304..5ca3643f5 100644 --- a/lib/teiserver/battle/balance/split_one_chevs.ex +++ b/lib/teiserver/battle/balance/split_one_chevs.ex @@ -18,6 +18,7 @@ defmodule Teiserver.Battle.Balance.SplitOneChevs do """ alias Teiserver.Battle.Balance.SplitOneChevsTypes, as: ST alias Teiserver.Battle.Balance.BalanceTypes, as: BT + import Teiserver.Helper.NumberHelper, only: [format: 1] @splitter "---------------------------" @@ -26,10 +27,34 @@ defmodule Teiserver.Battle.Balance.SplitOneChevs do 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 noobs; calling another balancer.", @splitter, result.logs] + |> List.flatten() + + Map.put(result, :logs, new_logs) + end + end + + @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 """ @@ -73,16 +98,6 @@ defmodule Teiserver.Battle.Balance.SplitOneChevs do |> List.flatten() end - defp round_number(rating_value) when is_float(rating_value) do - rating_value - |> Decimal.from_float() - |> Decimal.round(1) - end - - defp round_number(rating_value) when is_integer(rating_value) do - rating_value - end - @doc """ Assigns teams using algorithm defined in moduledoc See split_one_chevs_internal_test.exs for sample input @@ -104,7 +119,7 @@ defmodule Teiserver.Battle.Balance.SplitOneChevs do username = x.name new_log = - "#{username} (#{round_number(x.rating)}, σ: #{round_number(x.uncertainty)}, Chev: #{x.rank + 1}) picked for Team #{picking_team.team_id}" + "#{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)], diff --git a/lib/teiserver/helpers/number_helper.ex b/lib/teiserver/helpers/number_helper.ex index 800eb194a..0c6bfd023 100644 --- a/lib/teiserver/helpers/number_helper.ex +++ b/lib/teiserver/helpers/number_helper.ex @@ -120,4 +120,20 @@ defmodule Teiserver.Helper.NumberHelper do def percent(v, dp) do round(v * 100, dp) end + + @doc """ + Use this function for printing floats with only one decimal place. + Integers will be returned without modification. + If you need to always print the same amount of decimals no matter if float or integer, + then use the round function also defined in this module. + """ + def format(rating_value) when is_float(rating_value) do + rating_value + |> Decimal.from_float() + |> Decimal.round(1) + end + + def format(rating_value) when is_integer(rating_value) do + rating_value + end end diff --git a/test/teiserver/battle/split_one_chevs_internal_test.exs b/test/teiserver/battle/split_one_chevs_internal_test.exs index dbf02f593..9ff7f975b 100644 --- a/test/teiserver/battle/split_one_chevs_internal_test.exs +++ b/test/teiserver/battle/split_one_chevs_internal_test.exs @@ -108,4 +108,76 @@ defmodule Teiserver.Battle.SplitOneChevsInternalTest do %{members: [], team_id: 3} ] end + + test "has enough noobs" do + expanded_group = [ + %{ + count: 2, + members: ["Pro1", "Noob1"], + group_rating: 13, + ratings: [8, 5], + ranks: [1, 0], + names: ["Pro1", "Noob1"], + uncertainties: [0, 1] + }, + %{ + count: 1, + members: ["Noob2"], + group_rating: 6, + ratings: [6], + ranks: [0], + names: ["Noob2"], + uncertainties: [2] + }, + %{ + count: 1, + members: ["Noob3"], + group_rating: 7, + ratings: [17], + ranks: [0], + names: ["Noob3"], + uncertainties: [3] + } + ] + + result = SplitOneChevs.has_enough_noobs?(expanded_group) + + assert result + end + + test "not have enough noobs" do + expanded_group = [ + %{ + count: 2, + members: ["B", "A"], + group_rating: 13, + ratings: [8, 5], + ranks: [2, 2], + names: ["B", "A"], + uncertainties: [0, 1] + }, + %{ + count: 1, + members: ["C"], + group_rating: 6, + ratings: [6], + ranks: [2], + names: ["C"], + uncertainties: [2] + }, + %{ + count: 1, + members: ["D"], + group_rating: 7, + ratings: [17], + ranks: [2], + names: ["D"], + uncertainties: [3] + } + ] + + result = SplitOneChevs.has_enough_noobs?(expanded_group) + + refute result + end end diff --git a/test/teiserver/battle/split_one_chevs_test.exs b/test/teiserver/battle/split_one_chevs_test.exs index f811fe1d4..c501090d2 100644 --- a/test/teiserver/battle/split_one_chevs_test.exs +++ b/test/teiserver/battle/split_one_chevs_test.exs @@ -64,7 +64,7 @@ defmodule Teiserver.Battle.SplitOneChevsTest do algorithm: @split_algo ) - assert result.team_players == %{1 => [1, 5], 2 => [2, 6], 3 => [3, 4]} + assert result.team_players == %{1 => [5, 2], 2 => [6, 1], 3 => [4, 3]} end test "split one chevs simple group" do @@ -83,7 +83,7 @@ defmodule Teiserver.Battle.SplitOneChevsTest do algorithm: @split_algo ) - assert result.team_players == %{1 => [4, 1], 2 => [2, 3]} + assert result.team_players == %{1 => [1, 4], 2 => [2, 3]} end test "logs FFA" do @@ -135,4 +135,27 @@ defmodule Teiserver.Battle.SplitOneChevsTest do "Noob2 (8, σ: 8, Chev: 1) picked for Team 1" ] end + + test "calls another balancer when no noobs" do + result = + BalanceLib.create_balance( + [ + %{"A" => %{rating: 5, rank: 2}}, + %{"B" => %{rating: 6, rank: 2}}, + %{"C" => %{rating: 7, rank: 2, uncertainty: 7.9}}, + %{"D" => %{rating: 8, rank: 2, uncertainty: 8}} + ], + 2, + algorithm: @split_algo + ) + + assert result.logs == [ + "Not enough noobs; calling another balancer.", + "---------------------------", + "Picked D for team 1, adding 8.0 points for new total of 8.0", + "Picked C for team 2, adding 7.0 points for new total of 7.0", + "Picked B for team 2, adding 6.0 points for new total of 13.0", + "Picked A for team 1, adding 5.0 points for new total of 13.0" + ] + end end