Rewrite round generation and add unit test
This commit is contained in:
1 parent
84e9163b96
commit
6d494b64b3
11 files changed
+240
-386
No files matched your search
@@ -17,218 +17,110 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using VocaluxeLib.Utils.Combinatorics;
|
||||
|
||||
namespace VocaluxeLib.PartyModes.Challenge
|
||||
{
|
||||
public class CChallengeRounds
|
||||
{
|
||||
private readonly Random _Rand;
|
||||
private readonly int _NumPlayer;
|
||||
|
||||
public readonly List<CCombination> Rounds;
|
||||
private readonly List<CRound> _Rounds = new List<CRound>();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return Rounds.Count; }
|
||||
get { return _Rounds.Count; }
|
||||
}
|
||||
|
||||
public CCombination this[int index]
|
||||
public CRound this[int index]
|
||||
{
|
||||
get { return Rounds[index]; }
|
||||
get { return _Rounds[index]; }
|
||||
}
|
||||
|
||||
public CChallengeRounds(int numRounds, int numPlayer, int numPlayerAtOnce)
|
||||
public CChallengeRounds(int numRounds, int numPlayer, int playersPerRound)
|
||||
{
|
||||
Rounds = new List<CCombination>();
|
||||
_Rand = new Random(DateTime.Now.Millisecond);
|
||||
if (numRounds < 1 || numPlayer < 1 || playersPerRound < 1)
|
||||
throw new ArgumentException("Invalid paramters for the rounds");
|
||||
|
||||
if (numPlayerAtOnce < 1 || numRounds < 1 || numPlayer == 0)
|
||||
_NumPlayer = 0;
|
||||
else
|
||||
{
|
||||
_NumPlayer = numPlayer;
|
||||
_BuildRounds(numRounds, numPlayerAtOnce, _Rand);
|
||||
}
|
||||
if (playersPerRound > numPlayer)
|
||||
playersPerRound = numPlayer;
|
||||
|
||||
_BuildRounds(numRounds, numPlayer, playersPerRound);
|
||||
}
|
||||
|
||||
public CCombination GetRound(int roundNr)
|
||||
/// <summary>
|
||||
/// Divides players in 2 categories: Those how sung the most and the others
|
||||
/// </summary>
|
||||
/// <param name="numSung">List matching the playerIndex to the number of songs sung</param>
|
||||
private static List<int> _GetPlayersForRound(IList<int> numSung)
|
||||
{
|
||||
if (roundNr >= Rounds.Count || _NumPlayer == 0 || Rounds.Count == 0)
|
||||
return null;
|
||||
|
||||
return Rounds[roundNr];
|
||||
List<int> mustSing = new List<int>();
|
||||
int max = numSung.Max();
|
||||
for (int i = 0; i < numSung.Count; i++)
|
||||
{
|
||||
if (numSung[i] < max)
|
||||
mustSing.Add(i);
|
||||
}
|
||||
return mustSing;
|
||||
}
|
||||
|
||||
private void _BuildRounds(int numRounds, int numPlayerAtOnce, Random rand)
|
||||
private static void _AddCombinations(int numPlayer, int playersPerRound, List<CRound> combinations)
|
||||
{
|
||||
CCombinations combinations = new CCombinations(_NumPlayer, numPlayerAtOnce, rand);
|
||||
Rounds.Add(combinations.GetNextCombination(null));
|
||||
|
||||
for (int i = 1; i < numRounds; i++)
|
||||
Rounds.Add(combinations.GetNextCombination(_GetPlayerDemand(i)));
|
||||
List<int> input = new List<int>(numPlayer);
|
||||
for (int i = 0; i < numPlayer; i++)
|
||||
input.Add(i);
|
||||
CCombinations<int> combs = new CCombinations<int>(input, playersPerRound);
|
||||
combinations.AddRange(combs.Select(combination => new CRound(combination)));
|
||||
combinations.Shuffle();
|
||||
}
|
||||
|
||||
private IEnumerable<int> _GetPlayerDemand(int roundIndex)
|
||||
private static CRound _GetMatchingCombination(List<int> playersInRound, List<CRound> combinations)
|
||||
{
|
||||
List<int> numPlayed = new List<int>(_NumPlayer);
|
||||
for (int i = 0; i < _NumPlayer; i++)
|
||||
numPlayed.Add(0);
|
||||
|
||||
for (int i = 0; i < roundIndex; i++)
|
||||
foreach (CRound round in combinations)
|
||||
{
|
||||
foreach (int playerIndex in Rounds[i].Player)
|
||||
numPlayed[playerIndex]++;
|
||||
int matching = round.Players.Count(playersInRound.Contains);
|
||||
// A valid round contains only players from playersInRound or all players from playersInRound (and some others)
|
||||
if (matching == round.Players.Count || matching == playersInRound.Count)
|
||||
return round;
|
||||
}
|
||||
Debug.Assert(false, "This should not happen!");
|
||||
return combinations[0];
|
||||
}
|
||||
|
||||
int max = numPlayed.Max();
|
||||
|
||||
int playersPerRound = Rounds[0].Player.Count;
|
||||
List<int> result = new List<int>();
|
||||
List<int> other = new List<int>();
|
||||
List<int> last = new List<int>();
|
||||
|
||||
for (int i = 0; i < _NumPlayer; i++)
|
||||
private void _BuildRounds(int numRounds, int numPlayer, int playersPerRound)
|
||||
{
|
||||
// What we want is that every player sung the same amount of songs
|
||||
// So we keep track how many songs each palyer sung
|
||||
List<int> numSung = new List<int>(numPlayer);
|
||||
for (int i = 0; i < numPlayer; i++)
|
||||
numSung.Add(0);
|
||||
List<CRound> combinations = new List<CRound>();
|
||||
// Posibly add more rounds than requested if the song count per player is unequal
|
||||
for (int i = 0; i < numRounds * 10; i++)
|
||||
{
|
||||
if (roundIndex == 0 || numPlayed[i] < max)
|
||||
{
|
||||
if (roundIndex == 0 || roundIndex > 0 && numPlayed[i] < max && !Rounds[roundIndex - 1].Contains(i))
|
||||
result.Add(i);
|
||||
else if (numPlayed[i] < max)
|
||||
other.Add(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (roundIndex > 0)
|
||||
{
|
||||
if (!Rounds[roundIndex - 1].Contains(i))
|
||||
other.Add(i);
|
||||
else
|
||||
last.Add(i);
|
||||
}
|
||||
else
|
||||
last.Add(i);
|
||||
}
|
||||
if (combinations.Count == 0)
|
||||
_AddCombinations(numPlayer, playersPerRound, combinations);
|
||||
List<int> playersInRound = _GetPlayersForRound(numSung);
|
||||
CRound curRound = _GetMatchingCombination(playersInRound, combinations);
|
||||
foreach (int player in curRound.Players)
|
||||
numSung[player]++;
|
||||
_Rounds.Add(curRound);
|
||||
//Stop if we have enough rounds and all players sung the same amount of songs
|
||||
if (_Rounds.Count >= numRounds && numSung.All(ct => ct == numSung[0]))
|
||||
break;
|
||||
}
|
||||
|
||||
while (result.Count < playersPerRound && other.Count > 0)
|
||||
{
|
||||
int n = other.Count;
|
||||
int r = _Rand.Next(n);
|
||||
result.Add(other[r]);
|
||||
other.RemoveAt(r);
|
||||
}
|
||||
|
||||
while (result.Count < playersPerRound)
|
||||
{
|
||||
int n = last.Count;
|
||||
int r = _Rand.Next(n);
|
||||
result.Add(last[r]);
|
||||
last.RemoveAt(r);
|
||||
}
|
||||
|
||||
while (result.Count > playersPerRound)
|
||||
{
|
||||
int n = result.Count;
|
||||
int r = _Rand.Next(n);
|
||||
result.RemoveAt(r);
|
||||
}
|
||||
|
||||
result.Sort();
|
||||
return result;
|
||||
if (_Rounds.Count != numRounds)
|
||||
CBase.Log.LogDebug("Number of rounds does not match. Expected: " + numRounds + ", Is: " + _Rounds.Count + " for " + numPlayer + "/" + playersPerRound);
|
||||
}
|
||||
}
|
||||
|
||||
class CCombinations
|
||||
public class CRound
|
||||
{
|
||||
private readonly List<CCombination> _Combs;
|
||||
private readonly Random _Rand;
|
||||
private readonly int _NumPlayer;
|
||||
private readonly int _NumMics;
|
||||
public readonly List<int> Players;
|
||||
|
||||
public CCombinations(int numPlayer, int numMics, Random rand)
|
||||
public CRound(List<int> players)
|
||||
{
|
||||
_Combs = new List<CCombination>();
|
||||
_NumMics = numMics;
|
||||
_NumPlayer = numPlayer;
|
||||
_Rand = rand;
|
||||
}
|
||||
|
||||
public CCombination GetNextCombination(IEnumerable<int> playerNrDemand)
|
||||
{
|
||||
if (_Combs.Count == 0)
|
||||
_Create();
|
||||
|
||||
if (_NumPlayer == _NumMics)
|
||||
return _Combs[0];
|
||||
|
||||
//filter against PlayerNrDemand
|
||||
List<CCombination> combsFiltered = new List<CCombination>();
|
||||
if (playerNrDemand != null)
|
||||
combsFiltered.AddRange(_Combs.Where(t => t.ContainsAll(playerNrDemand)));
|
||||
|
||||
//1st fallback
|
||||
if (playerNrDemand != null && combsFiltered.Count == 0)
|
||||
combsFiltered.AddRange(_Combs.Where(t => t.ContainsAny(playerNrDemand)));
|
||||
|
||||
//2nd fallback
|
||||
if (combsFiltered.Count == 0)
|
||||
combsFiltered.AddRange(_Combs);
|
||||
|
||||
int num = combsFiltered.Count;
|
||||
int rand = _Rand.Next(num);
|
||||
CCombination c = new CCombination();
|
||||
for (int i = 0; i < combsFiltered[rand].Player.Count; i++)
|
||||
c.Player.Add(combsFiltered[rand].Player[i]);
|
||||
_Combs.Remove(combsFiltered[rand]);
|
||||
|
||||
return combsFiltered[rand];
|
||||
}
|
||||
|
||||
private void _Create()
|
||||
{
|
||||
_Combs.Clear();
|
||||
|
||||
int num = (int)Math.Pow(2, _NumPlayer);
|
||||
|
||||
for (int i = 1; i < num; i++)
|
||||
{
|
||||
CCombination c = new CCombination();
|
||||
|
||||
for (int j = 0; j < _NumPlayer; j++)
|
||||
{
|
||||
if ((i & (1 << j)) > 0)
|
||||
c.Player.Add(j);
|
||||
}
|
||||
|
||||
if (c.Player.Count == _NumMics)
|
||||
_Combs.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CCombination
|
||||
{
|
||||
public readonly List<int> Player;
|
||||
|
||||
public CCombination()
|
||||
{
|
||||
Player = new List<int>();
|
||||
}
|
||||
|
||||
public bool Contains(int playerIndex)
|
||||
{
|
||||
return Player.Any(p => p == playerIndex);
|
||||
}
|
||||
|
||||
public bool ContainsAll(IEnumerable<int> playerIndices)
|
||||
{
|
||||
return playerIndices.All(Contains);
|
||||
}
|
||||
|
||||
public bool ContainsAny(IEnumerable<int> playerIndices)
|
||||
{
|
||||
return playerIndices.Any(Contains);
|
||||
Players = players.Shuffle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,13 +341,13 @@ namespace VocaluxeLib.PartyModes.Challenge
|
||||
if (players.Length < GameData.NumPlayerAtOnce)
|
||||
return;
|
||||
|
||||
CCombination c = GameData.Rounds.GetRound(GameData.CurrentRoundNr - 1);
|
||||
CRound c = GameData.Rounds[GameData.CurrentRoundNr - 1];
|
||||
|
||||
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||
{
|
||||
//try to fill with the right data
|
||||
if (c != null)
|
||||
players[i].ProfileID = GameData.ProfileIDs[c.Player[i]];
|
||||
players[i].ProfileID = GameData.ProfileIDs[c.Players[i]];
|
||||
else
|
||||
players[i].ProfileID = -1;
|
||||
}
|
||||
@@ -413,15 +413,10 @@ namespace VocaluxeLib.PartyModes.Challenge
|
||||
}
|
||||
|
||||
_ScreenSongOptions.Selection.TeamNames = new string[GameData.NumPlayerAtOnce];
|
||||
CCombination c = GameData.Rounds.GetRound(GameData.CurrentRoundNr - 1);
|
||||
CRound c = GameData.Rounds[GameData.CurrentRoundNr - 1];
|
||||
|
||||
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||
{
|
||||
if (c != null)
|
||||
_ScreenSongOptions.Selection.TeamNames[i] = CBase.Profiles.GetPlayerName(GameData.ProfileIDs[c.Player[i]]);
|
||||
else
|
||||
_ScreenSongOptions.Selection.TeamNames[i] = "foobar";
|
||||
}
|
||||
_ScreenSongOptions.Selection.TeamNames[i] = CBase.Profiles.GetPlayerName(GameData.ProfileIDs[c.Players[i]]);
|
||||
}
|
||||
|
||||
private void _UpdateScores()
|
||||
|
||||
@@ -340,7 +340,7 @@ namespace VocaluxeLib.PartyModes.Challenge
|
||||
_Texts[_TextNextPlayerMessage].Visible = true;
|
||||
for (int i = 0; i < _PartyMode.GameData.NumPlayerAtOnce; i++)
|
||||
{
|
||||
int id = _PartyMode.GameData.ProfileIDs[_PartyMode.GameData.Rounds[_PartyMode.GameData.CurrentRoundNr - 1].Player[i]];
|
||||
int id = _PartyMode.GameData.ProfileIDs[_PartyMode.GameData.Rounds[_PartyMode.GameData.CurrentRoundNr - 1].Players[i]];
|
||||
_NextPlayerStatics[i].Texture = CBase.Profiles.GetAvatar(id);
|
||||
_NextPlayerTexts[i].Text = CBase.Profiles.GetPlayerName(id);
|
||||
_NextPlayerTexts[i].Color = CBase.Themes.GetPlayerColor(i + 1);
|
||||
@@ -452,14 +452,14 @@ namespace VocaluxeLib.PartyModes.Challenge
|
||||
{
|
||||
for (int p = 0; p < _RoundsTable[i].TextPlayer.Count; p++)
|
||||
{
|
||||
if (_PartyMode.GameData.Rounds.Count > i + _RoundsTableOffset && _PartyMode.GameData.Rounds[i + _RoundsTableOffset].Player.Count > p)
|
||||
if (_PartyMode.GameData.Rounds.Count > i + _RoundsTableOffset && _PartyMode.GameData.Rounds[i + _RoundsTableOffset].Players.Count > p)
|
||||
{
|
||||
_RoundsTable[i].Number.Visible = true;
|
||||
_RoundsTable[i].TextPlayer[p].Visible = true;
|
||||
_RoundsTable[i].TextScores[p].Visible = true;
|
||||
_RoundsTable[i].Number.Text = (i + 1 + _RoundsTableOffset) + ")";
|
||||
_RoundsTable[i].TextPlayer[p].Text =
|
||||
CBase.Profiles.GetPlayerName(_PartyMode.GameData.ProfileIDs[_PartyMode.GameData.Rounds[i + _RoundsTableOffset].Player[p]]);
|
||||
CBase.Profiles.GetPlayerName(_PartyMode.GameData.ProfileIDs[_PartyMode.GameData.Rounds[i + _RoundsTableOffset].Players[p]]);
|
||||
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
|
||||
if ((_PartyMode.GameData.CurrentRoundNr - 1) > i + _RoundsTableOffset)
|
||||
// ReSharper restore ConvertIfStatementToConditionalTernaryExpression
|
||||
|
||||
Reference in new issue
Block a user