Files
Quentin Bray 6ffa63b923 Apply R# clean-up for reliability (#877)
* Apply R# clean-up for reliability

- Migrate to NUnit4 Assert.That
- Update dependencise versions
- Fix LangVersion to 9.0 (until net core migration)
- Reformat code
- Apply 'var' style (easier for refactoring)
- Remove redundant parentheses
- Add braces on every condition blocks (1 line)
- Uses new null checking patterns
- Uses AutoProperties when possible
- Uses string scalar type instead of class
- Clean usings
- Add .orig files in git ignore
- Rename some properties to respect defined conventions and US format definition
	- ID -> Id
	- BPM -> Bpm
	- Finish -> End

* Update R# settings according to the clean-up made
2026-06-22 19:52:06 +02:00

177 lines
6.2 KiB
C#

#region license
// This file is part of Vocaluxe.
//
// Vocaluxe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vocaluxe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
#endregion
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 List<CRound> _Rounds = new List<CRound>();
public int Count
{
get { return _Rounds.Count; }
}
public CRound this[int index]
{
get { return _Rounds[index]; }
}
public CChallengeRounds(int numRounds, int numPlayer, int playersPerRound)
{
if (numRounds < 1 || numPlayer < 1 || playersPerRound < 1)
{
throw new ArgumentException("Invalid paramters for the rounds");
}
if (playersPerRound > numPlayer)
{
playersPerRound = numPlayer;
}
_BuildRounds(numRounds, numPlayer, playersPerRound);
if (Count > numRounds)
{
//Try again and take the better one
var old = new List<CRound>(_Rounds);
_Rounds.Clear();
_BuildRounds(numRounds, numPlayer, playersPerRound);
if (Count > old.Count)
{
_Rounds.Clear();
_Rounds.AddRange(old);
}
}
}
/// <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)
{
var mustSing = new List<int>();
var max = numSung.Max();
for (var i = 0; i < numSung.Count; i++)
{
if (numSung[i] < max)
{
mustSing.Add(i);
}
}
return mustSing;
}
private static void _AddCombinations(int numPlayer, int playersPerRound, List<CRound> combinations)
{
var input = new List<int>(numPlayer);
for (var i = 0; i < numPlayer; i++)
{
input.Add(i);
}
var combs = new CCombinations<int>(input, playersPerRound);
combinations.AddRange(combs.Select(combination => new CRound(combination)));
combinations.Shuffle();
}
private static CRound _GetMatchingCombination(List<int> playersInRound, List<int> numSung, List<CRound> combinations)
{
foreach (var round in combinations)
{
var 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;
}
}
// It may happen, that there are only players that already sung against each other. So create a 2nd best option with the most number of players that should sing in it
var maxMatching = combinations.Max(c => c.Players.Count(playersInRound.Contains));
var nextOptions = combinations.Where(c => c.Players.Count(playersInRound.Contains) == maxMatching).ToList();
// And select the combination that minimizes the sum of the number of songs sung (--> favor rounds with players that sung less than others)
CRound result = null;
var minSung = int.MaxValue;
foreach (var round in nextOptions)
{
var curNumSung = round.Players.Sum(pl => numSung[pl]);
if (curNumSung < minSung)
{
minSung = curNumSung;
result = round;
}
}
Debug.Assert(result != null);
return result;
}
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
var numSung = new List<int>(numPlayer);
for (var i = 0; i < numPlayer; i++)
{
numSung.Add(0);
}
var combinations = new List<CRound>();
// Posibly add more rounds than requested if the song count per player is unequal
for (var i = 0; i < numRounds * 10; i++)
{
if (combinations.Count == 0)
{
_AddCombinations(numPlayer, playersPerRound, combinations);
}
var playersInRound = _GetPlayersForRound(numSung);
var curRound = _GetMatchingCombination(playersInRound, numSung, combinations);
foreach (var player in curRound.Players)
{
numSung[player]++;
}
_Rounds.Add(curRound);
combinations.Remove(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;
}
}
}
}
public class CRound
{
public readonly List<int> Players;
public CRound(List<int> players)
{
Players = players.Shuffle();
}
}
}