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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using VocaluxeLib.Utils.Combinatorics;
|
||||||
|
|
||||||
namespace VocaluxeLib.PartyModes.Challenge
|
namespace VocaluxeLib.PartyModes.Challenge
|
||||||
{
|
{
|
||||||
public class CChallengeRounds
|
public class CChallengeRounds
|
||||||
{
|
{
|
||||||
private readonly Random _Rand;
|
private readonly List<CRound> _Rounds = new List<CRound>();
|
||||||
private readonly int _NumPlayer;
|
|
||||||
|
|
||||||
public readonly List<CCombination> Rounds;
|
|
||||||
|
|
||||||
public int Count
|
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>();
|
if (numRounds < 1 || numPlayer < 1 || playersPerRound < 1)
|
||||||
_Rand = new Random(DateTime.Now.Millisecond);
|
throw new ArgumentException("Invalid paramters for the rounds");
|
||||||
|
|
||||||
if (numPlayerAtOnce < 1 || numRounds < 1 || numPlayer == 0)
|
if (playersPerRound > numPlayer)
|
||||||
_NumPlayer = 0;
|
playersPerRound = numPlayer;
|
||||||
else
|
|
||||||
{
|
_BuildRounds(numRounds, numPlayer, playersPerRound);
|
||||||
_NumPlayer = numPlayer;
|
|
||||||
_BuildRounds(numRounds, numPlayerAtOnce, _Rand);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
List<int> mustSing = new List<int>();
|
||||||
return null;
|
int max = numSung.Max();
|
||||||
|
for (int i = 0; i < numSung.Count; i++)
|
||||||
return Rounds[roundNr];
|
{
|
||||||
|
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);
|
List<int> input = new List<int>(numPlayer);
|
||||||
Rounds.Add(combinations.GetNextCombination(null));
|
for (int i = 0; i < numPlayer; i++)
|
||||||
|
input.Add(i);
|
||||||
for (int i = 1; i < numRounds; i++)
|
CCombinations<int> combs = new CCombinations<int>(input, playersPerRound);
|
||||||
Rounds.Add(combinations.GetNextCombination(_GetPlayerDemand(i)));
|
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);
|
foreach (CRound round in combinations)
|
||||||
for (int i = 0; i < _NumPlayer; i++)
|
|
||||||
numPlayed.Add(0);
|
|
||||||
|
|
||||||
for (int i = 0; i < roundIndex; i++)
|
|
||||||
{
|
{
|
||||||
foreach (int playerIndex in Rounds[i].Player)
|
int matching = round.Players.Count(playersInRound.Contains);
|
||||||
numPlayed[playerIndex]++;
|
// 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();
|
private void _BuildRounds(int numRounds, int numPlayer, int playersPerRound)
|
||||||
|
{
|
||||||
int playersPerRound = Rounds[0].Player.Count;
|
// What we want is that every player sung the same amount of songs
|
||||||
List<int> result = new List<int>();
|
// So we keep track how many songs each palyer sung
|
||||||
List<int> other = new List<int>();
|
List<int> numSung = new List<int>(numPlayer);
|
||||||
List<int> last = new List<int>();
|
for (int i = 0; i < numPlayer; i++)
|
||||||
|
numSung.Add(0);
|
||||||
for (int i = 0; i < _NumPlayer; i++)
|
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 (combinations.Count == 0)
|
||||||
{
|
_AddCombinations(numPlayer, playersPerRound, combinations);
|
||||||
if (roundIndex == 0 || roundIndex > 0 && numPlayed[i] < max && !Rounds[roundIndex - 1].Contains(i))
|
List<int> playersInRound = _GetPlayersForRound(numSung);
|
||||||
result.Add(i);
|
CRound curRound = _GetMatchingCombination(playersInRound, combinations);
|
||||||
else if (numPlayed[i] < max)
|
foreach (int player in curRound.Players)
|
||||||
other.Add(i);
|
numSung[player]++;
|
||||||
}
|
_Rounds.Add(curRound);
|
||||||
else
|
//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]))
|
||||||
if (roundIndex > 0)
|
break;
|
||||||
{
|
|
||||||
if (!Rounds[roundIndex - 1].Contains(i))
|
|
||||||
other.Add(i);
|
|
||||||
else
|
|
||||||
last.Add(i);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
last.Add(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (_Rounds.Count != numRounds)
|
||||||
while (result.Count < playersPerRound && other.Count > 0)
|
CBase.Log.LogDebug("Number of rounds does not match. Expected: " + numRounds + ", Is: " + _Rounds.Count + " for " + numPlayer + "/" + playersPerRound);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CCombinations
|
public class CRound
|
||||||
{
|
{
|
||||||
private readonly List<CCombination> _Combs;
|
public readonly List<int> Players;
|
||||||
private readonly Random _Rand;
|
|
||||||
private readonly int _NumPlayer;
|
|
||||||
private readonly int _NumMics;
|
|
||||||
|
|
||||||
public CCombinations(int numPlayer, int numMics, Random rand)
|
public CRound(List<int> players)
|
||||||
{
|
{
|
||||||
_Combs = new List<CCombination>();
|
Players = players.Shuffle();
|
||||||
_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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,13 +341,13 @@ namespace VocaluxeLib.PartyModes.Challenge
|
|||||||
if (players.Length < GameData.NumPlayerAtOnce)
|
if (players.Length < GameData.NumPlayerAtOnce)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CCombination c = GameData.Rounds.GetRound(GameData.CurrentRoundNr - 1);
|
CRound c = GameData.Rounds[GameData.CurrentRoundNr - 1];
|
||||||
|
|
||||||
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||||
{
|
{
|
||||||
//try to fill with the right data
|
//try to fill with the right data
|
||||||
if (c != null)
|
if (c != null)
|
||||||
players[i].ProfileID = GameData.ProfileIDs[c.Player[i]];
|
players[i].ProfileID = GameData.ProfileIDs[c.Players[i]];
|
||||||
else
|
else
|
||||||
players[i].ProfileID = -1;
|
players[i].ProfileID = -1;
|
||||||
}
|
}
|
||||||
@@ -413,15 +413,10 @@ namespace VocaluxeLib.PartyModes.Challenge
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ScreenSongOptions.Selection.TeamNames = new string[GameData.NumPlayerAtOnce];
|
_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++)
|
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||||
{
|
_ScreenSongOptions.Selection.TeamNames[i] = CBase.Profiles.GetPlayerName(GameData.ProfileIDs[c.Players[i]]);
|
||||||
if (c != null)
|
|
||||||
_ScreenSongOptions.Selection.TeamNames[i] = CBase.Profiles.GetPlayerName(GameData.ProfileIDs[c.Player[i]]);
|
|
||||||
else
|
|
||||||
_ScreenSongOptions.Selection.TeamNames[i] = "foobar";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _UpdateScores()
|
private void _UpdateScores()
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ namespace VocaluxeLib.PartyModes.Challenge
|
|||||||
_Texts[_TextNextPlayerMessage].Visible = true;
|
_Texts[_TextNextPlayerMessage].Visible = true;
|
||||||
for (int i = 0; i < _PartyMode.GameData.NumPlayerAtOnce; i++)
|
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);
|
_NextPlayerStatics[i].Texture = CBase.Profiles.GetAvatar(id);
|
||||||
_NextPlayerTexts[i].Text = CBase.Profiles.GetPlayerName(id);
|
_NextPlayerTexts[i].Text = CBase.Profiles.GetPlayerName(id);
|
||||||
_NextPlayerTexts[i].Color = CBase.Themes.GetPlayerColor(i + 1);
|
_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++)
|
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].Number.Visible = true;
|
||||||
_RoundsTable[i].TextPlayer[p].Visible = true;
|
_RoundsTable[i].TextPlayer[p].Visible = true;
|
||||||
_RoundsTable[i].TextScores[p].Visible = true;
|
_RoundsTable[i].TextScores[p].Visible = true;
|
||||||
_RoundsTable[i].Number.Text = (i + 1 + _RoundsTableOffset) + ")";
|
_RoundsTable[i].Number.Text = (i + 1 + _RoundsTableOffset) + ")";
|
||||||
_RoundsTable[i].TextPlayer[p].Text =
|
_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
|
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
|
||||||
if ((_PartyMode.GameData.CurrentRoundNr - 1) > i + _RoundsTableOffset)
|
if ((_PartyMode.GameData.CurrentRoundNr - 1) > i + _RoundsTableOffset)
|
||||||
// ReSharper restore ConvertIfStatementToConditionalTernaryExpression
|
// ReSharper restore ConvertIfStatementToConditionalTernaryExpression
|
||||||
|
|||||||
@@ -17,218 +17,110 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using VocaluxeLib.Utils.Combinatorics;
|
||||||
|
|
||||||
namespace VocaluxeLib.PartyModes.ChallengeMedley
|
namespace VocaluxeLib.PartyModes.ChallengeMedley
|
||||||
{
|
{
|
||||||
public class CChallengeRounds
|
public class CChallengeRounds
|
||||||
{
|
{
|
||||||
private readonly Random _Rand;
|
private readonly List<CRound> _Rounds = new List<CRound>();
|
||||||
private readonly int _NumPlayer;
|
|
||||||
|
|
||||||
public readonly List<CCombination> Rounds;
|
|
||||||
|
|
||||||
public int Count
|
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>();
|
if (numRounds < 1 || numPlayer < 1 || playersPerRound < 1)
|
||||||
_Rand = new Random(DateTime.Now.Millisecond);
|
throw new ArgumentException("Invalid paramters for the rounds");
|
||||||
|
|
||||||
if (numPlayerAtOnce < 1 || numRounds < 1 || numPlayer == 0)
|
if (playersPerRound > numPlayer)
|
||||||
_NumPlayer = 0;
|
playersPerRound = numPlayer;
|
||||||
else
|
|
||||||
{
|
_BuildRounds(numRounds, numPlayer, playersPerRound);
|
||||||
_NumPlayer = numPlayer;
|
|
||||||
_BuildRounds(numRounds, numPlayerAtOnce, _Rand);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
List<int> mustSing = new List<int>();
|
||||||
return null;
|
int max = numSung.Max();
|
||||||
|
for (int i = 0; i < numSung.Count; i++)
|
||||||
return Rounds[roundNr];
|
{
|
||||||
|
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);
|
List<int> input = new List<int>(numPlayer);
|
||||||
Rounds.Add(combinations.GetNextCombination(null));
|
for (int i = 0; i < numPlayer; i++)
|
||||||
|
input.Add(i);
|
||||||
for (int i = 1; i < numRounds; i++)
|
CCombinations<int> combs = new CCombinations<int>(input, playersPerRound);
|
||||||
Rounds.Add(combinations.GetNextCombination(_GetPlayerDemand(i)));
|
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);
|
foreach (CRound round in combinations)
|
||||||
for (int i = 0; i < _NumPlayer; i++)
|
|
||||||
numPlayed.Add(0);
|
|
||||||
|
|
||||||
for (int i = 0; i < roundIndex; i++)
|
|
||||||
{
|
{
|
||||||
foreach (int playerIndex in Rounds[i].Player)
|
int matching = round.Players.Count(playersInRound.Contains);
|
||||||
numPlayed[playerIndex]++;
|
// 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();
|
private void _BuildRounds(int numRounds, int numPlayer, int playersPerRound)
|
||||||
|
{
|
||||||
int num = Rounds[0].Player.Count;
|
// What we want is that every player sung the same amount of songs
|
||||||
List<int> result = new List<int>();
|
// So we keep track how many songs each palyer sung
|
||||||
List<int> other = new List<int>();
|
List<int> numSung = new List<int>(numPlayer);
|
||||||
List<int> last = new List<int>();
|
for (int i = 0; i < numPlayer; i++)
|
||||||
|
numSung.Add(0);
|
||||||
for (int i = 0; i < numPlayed.Count; i++)
|
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 (combinations.Count == 0)
|
||||||
{
|
_AddCombinations(numPlayer, playersPerRound, combinations);
|
||||||
if (roundIndex == 0 || roundIndex > 0 && numPlayed[i] < max && !Rounds[roundIndex - 1].IsAvailable(i))
|
List<int> playersInRound = _GetPlayersForRound(numSung);
|
||||||
result.Add(i);
|
CRound curRound = _GetMatchingCombination(playersInRound, combinations);
|
||||||
else if (numPlayed[i] < max)
|
foreach (int player in curRound.Players)
|
||||||
other.Add(i);
|
numSung[player]++;
|
||||||
}
|
_Rounds.Add(curRound);
|
||||||
else
|
//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]))
|
||||||
if (roundIndex > 0)
|
break;
|
||||||
{
|
|
||||||
if (!Rounds[roundIndex - 1].IsAvailable(i))
|
|
||||||
other.Add(i);
|
|
||||||
else
|
|
||||||
last.Add(i);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
last.Add(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (_Rounds.Count != numRounds)
|
||||||
while (result.Count < num && other.Count > 0)
|
CBase.Log.LogDebug("Number of rounds does not match. Expected: " + numRounds + ", Is: " + _Rounds.Count + " for " + numPlayer + "/" + playersPerRound);
|
||||||
{
|
|
||||||
int n = other.Count;
|
|
||||||
int r = _Rand.Next(n);
|
|
||||||
result.Add(other[r]);
|
|
||||||
other.RemoveAt(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (result.Count < num)
|
|
||||||
{
|
|
||||||
int n = last.Count;
|
|
||||||
int r = _Rand.Next(n);
|
|
||||||
result.Add(last[r]);
|
|
||||||
last.RemoveAt(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (result.Count > num)
|
|
||||||
{
|
|
||||||
int n = result.Count;
|
|
||||||
int r = _Rand.Next(n);
|
|
||||||
result.RemoveAt(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Sort();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CCombinations
|
public class CRound
|
||||||
{
|
{
|
||||||
private readonly List<CCombination> _Combs;
|
public readonly List<int> Players;
|
||||||
private readonly Random _Rand;
|
|
||||||
private readonly int _NumPlayer;
|
|
||||||
private readonly int _NumMics;
|
|
||||||
|
|
||||||
public CCombinations(int numPlayer, int numMics, Random rand)
|
public CRound(List<int> players)
|
||||||
{
|
{
|
||||||
_Combs = new List<CCombination>();
|
Players = players.Shuffle();
|
||||||
_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.IsAvailableAll(playerNrDemand)));
|
|
||||||
|
|
||||||
//1st fallback
|
|
||||||
if (playerNrDemand != null && combsFiltered.Count == 0)
|
|
||||||
combsFiltered.AddRange(_Combs.Where(t => t.IsAvailableSomeone(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 IsAvailable(int playerIndex)
|
|
||||||
{
|
|
||||||
return Player.Any(p => p == playerIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAvailableAll(IEnumerable<int> playerIndices)
|
|
||||||
{
|
|
||||||
return playerIndices.All(IsAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAvailableSomeone(IEnumerable<int> playerIndices)
|
|
||||||
{
|
|
||||||
return playerIndices.Any(IsAvailable);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,13 +366,13 @@ namespace VocaluxeLib.PartyModes.ChallengeMedley
|
|||||||
if (players.Length < GameData.NumPlayerAtOnce)
|
if (players.Length < GameData.NumPlayerAtOnce)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CCombination c = GameData.Rounds.GetRound(GameData.CurrentRoundNr - 1);
|
CRound c = GameData.Rounds[GameData.CurrentRoundNr - 1];
|
||||||
|
|
||||||
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||||
{
|
{
|
||||||
//try to fill with the right data
|
//try to fill with the right data
|
||||||
if (c != null)
|
if (c != null)
|
||||||
players[i].ProfileID = GameData.ProfileIDs[c.Player[i]];
|
players[i].ProfileID = GameData.ProfileIDs[c.Players[i]];
|
||||||
else
|
else
|
||||||
players[i].ProfileID = -1;
|
players[i].ProfileID = -1;
|
||||||
}
|
}
|
||||||
@@ -475,14 +475,14 @@ namespace VocaluxeLib.PartyModes.ChallengeMedley
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ScreenSongOptions.Selection.TeamNames = new string[GameData.NumPlayerAtOnce];
|
_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++)
|
for (int i = 0; i < GameData.NumPlayerAtOnce; i++)
|
||||||
{
|
{
|
||||||
if (c != null)
|
if (c != null)
|
||||||
{
|
{
|
||||||
if (GameData.ProfileIDs[c.Player[i]] < profiles.Length)
|
if (GameData.ProfileIDs[c.Players[i]] < profiles.Length)
|
||||||
_ScreenSongOptions.Selection.TeamNames[i] = profiles[GameData.ProfileIDs[c.Player[i]]].PlayerName;
|
_ScreenSongOptions.Selection.TeamNames[i] = profiles[GameData.ProfileIDs[c.Players[i]]].PlayerName;
|
||||||
else
|
else
|
||||||
_ScreenSongOptions.Selection.TeamNames[i] = "foobar";
|
_ScreenSongOptions.Selection.TeamNames[i] = "foobar";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ namespace VocaluxeLib.PartyModes.ChallengeMedley
|
|||||||
_Texts[_TextNextPlayerMessage].Visible = true;
|
_Texts[_TextNextPlayerMessage].Visible = true;
|
||||||
for (int i = 0; i < _PartyMode.GameData.NumPlayerAtOnce; i++)
|
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);
|
_NextPlayerStatics[i].Texture = CBase.Profiles.GetAvatar(id);
|
||||||
_NextPlayerTexts[i].Text = CBase.Profiles.GetPlayerName(id);
|
_NextPlayerTexts[i].Text = CBase.Profiles.GetPlayerName(id);
|
||||||
_NextPlayerTexts[i].Color = CBase.Themes.GetPlayerColor(i + 1);
|
_NextPlayerTexts[i].Color = CBase.Themes.GetPlayerColor(i + 1);
|
||||||
@@ -452,14 +452,14 @@ namespace VocaluxeLib.PartyModes.ChallengeMedley
|
|||||||
{
|
{
|
||||||
for (int p = 0; p < _RoundsTable[i].TextPlayer.Count; p++)
|
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].Number.Visible = true;
|
||||||
_RoundsTable[i].TextPlayer[p].Visible = true;
|
_RoundsTable[i].TextPlayer[p].Visible = true;
|
||||||
_RoundsTable[i].TextScores[p].Visible = true;
|
_RoundsTable[i].TextScores[p].Visible = true;
|
||||||
_RoundsTable[i].Number.Text = (i + 1 + _RoundsTableOffset) + ")";
|
_RoundsTable[i].Number.Text = (i + 1 + _RoundsTableOffset) + ")";
|
||||||
_RoundsTable[i].TextPlayer[p].Text =
|
_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
|
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
|
||||||
if ((_PartyMode.GameData.CurrentRoundNr - 1) > i + _RoundsTableOffset)
|
if ((_PartyMode.GameData.CurrentRoundNr - 1) > i + _RoundsTableOffset)
|
||||||
// ReSharper restore ConvertIfStatementToConditionalTernaryExpression
|
// ReSharper restore ConvertIfStatementToConditionalTernaryExpression
|
||||||
|
|||||||
@@ -415,17 +415,6 @@ namespace VocaluxeLib.PartyModes.TicTacToe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void _Shuffle<T>(IList<T> list)
|
|
||||||
{
|
|
||||||
for (int n = list.Count - 1; n >= 1; n--)
|
|
||||||
{
|
|
||||||
int k = CBase.Game.GetRandom(n);
|
|
||||||
T value = list[k];
|
|
||||||
list[k] = list[n];
|
|
||||||
list[n] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateSongList()
|
public void UpdateSongList()
|
||||||
{
|
{
|
||||||
if (GameData.Songs.Count > 0)
|
if (GameData.Songs.Count > 0)
|
||||||
@@ -455,7 +444,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe
|
|||||||
CBase.Songs.SetCategory(-1);
|
CBase.Songs.SetCategory(-1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_Shuffle(GameData.Songs);
|
GameData.Songs.Shuffle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _UpdatePlayerList()
|
private void _UpdatePlayerList()
|
||||||
|
|||||||
@@ -178,6 +178,18 @@ namespace VocaluxeLib
|
|||||||
rect.Z);
|
rect.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<T> Shuffle<T>(this List<T> list)
|
||||||
|
{
|
||||||
|
for (int n = list.Count - 1; n >= 1; n--)
|
||||||
|
{
|
||||||
|
int k = CBase.Game.GetRandom(n);
|
||||||
|
T value = list[k];
|
||||||
|
list[k] = list[n];
|
||||||
|
list[n] = value;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
public static Size GetSize(this Bitmap bmp)
|
public static Size GetSize(this Bitmap bmp)
|
||||||
{
|
{
|
||||||
return new Size(bmp.Width, bmp.Height);
|
return new Size(bmp.Width, bmp.Height);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace VocaluxeLib.Utils.Combinatorics
|
namespace VocaluxeLib.Utils.Combinatorics
|
||||||
{
|
{
|
||||||
@@ -143,8 +144,7 @@ namespace VocaluxeLib.Utils.Combinatorics
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
List<T> result = new List<T>(_K);
|
List<T> result = new List<T>(_K);
|
||||||
for (int i = 0; i < _K; i++)
|
result.AddRange(_CurSet.Select(i => _Parent._Values[i]));
|
||||||
result.Add(_Parent._Values[_CurSet[i]]);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Vocaluxe.Base;
|
||||||
|
using VocaluxeLib;
|
||||||
|
using VocaluxeLib.PartyModes.Challenge;
|
||||||
|
|
||||||
|
namespace VocaluxeTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CPartyModeTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRoundBoundaries()
|
||||||
|
{
|
||||||
|
// ReSharper disable ObjectCreationAsStatement
|
||||||
|
new CChallengeRounds(1, 1, 1); // Should succeed
|
||||||
|
// ReSharper restore ObjectCreationAsStatement
|
||||||
|
for (int inv = -1; inv <= 0; inv++)
|
||||||
|
{
|
||||||
|
int invL = inv;
|
||||||
|
CTestHelpers.AssertFail<ArgumentException>(() => new CChallengeRounds(invL, 1, 1));
|
||||||
|
CTestHelpers.AssertFail<ArgumentException>(() => new CChallengeRounds(1, invL, 1));
|
||||||
|
CTestHelpers.AssertFail<ArgumentException>(() => new CChallengeRounds(1, 1, invL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void _CheckRounds(CChallengeRounds rounds, int numPlayer)
|
||||||
|
{
|
||||||
|
List<int> numSongs = new List<int>(numPlayer);
|
||||||
|
for (int i = 0; i < numPlayer; i++)
|
||||||
|
numSongs.Add(0);
|
||||||
|
for (int i = 0; i < rounds.Count; i++)
|
||||||
|
{
|
||||||
|
foreach (int player in rounds[i].Players)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(player >= 0 && player < numPlayer);
|
||||||
|
numSongs[player]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.IsTrue(numSongs.Min() == numSongs.Max(), "Some players have more songs than others");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRoundGeneration()
|
||||||
|
{
|
||||||
|
CBase.Game = new CBGame();
|
||||||
|
CLog.Init();
|
||||||
|
CBase.Log = new CBlog();
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
for (int numPlayer = 1; numPlayer <= 20; numPlayer++)
|
||||||
|
{
|
||||||
|
for (int numMic = 1; numMic <= 6; numMic++)
|
||||||
|
{
|
||||||
|
int roundFactor = (numPlayer % numMic == 0) ? numPlayer / numMic : numPlayer;
|
||||||
|
for (int numRounds = roundFactor; numRounds <= 100 && numRounds <= roundFactor * 5; numRounds += roundFactor)
|
||||||
|
{
|
||||||
|
CChallengeRounds rounds = new CChallengeRounds(numRounds, numPlayer, numMic);
|
||||||
|
Assert.IsTrue(rounds.Count >= numRounds);
|
||||||
|
_CheckRounds(rounds, numPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,9 +73,14 @@
|
|||||||
<Compile Include="CTestHelpers.cs" />
|
<Compile Include="CTestHelpers.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="CombinatoricsTest.cs" />
|
<Compile Include="CombinatoricsTest.cs" />
|
||||||
|
<Compile Include="PartyModeTests.cs" />
|
||||||
<Compile Include="XmlSerializerTest.cs" />
|
<Compile Include="XmlSerializerTest.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PartyModes\PartyModeChallenge\PartyModeChallenge.csproj">
|
||||||
|
<Project>{5a4053f1-5ad5-441f-b254-d97a088127b6}</Project>
|
||||||
|
<Name>PartyModeChallenge</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\VocaluxeLib\VocaluxeLib.csproj">
|
<ProjectReference Include="..\VocaluxeLib\VocaluxeLib.csproj">
|
||||||
<Project>{d2b6ddef-e0e1-4ddf-a73a-e6acef5c95ec}</Project>
|
<Project>{d2b6ddef-e0e1-4ddf-a73a-e6acef5c95ec}</Project>
|
||||||
<Name>VocaluxeLib</Name>
|
<Name>VocaluxeLib</Name>
|
||||||
|
|||||||
Reference in new issue
Block a user