1using System.Collections.ObjectModel;
2using System.Collections.Specialized;
3using System.ComponentModel;
4using CommunityToolkit.Mvvm.ComponentModel;
13 public partial class RacesVariant : ObservableObject, IEquatable<RacesVariant>, ICloneable
15 #region Constructors / Destructor
25 Races =
new ObservableCollection<Race>();
26 Races.CollectionChanged += Races_CollectionChanged;
27 _workspaceService = workspaceService;
28 if (_workspaceService !=
null) { _workspaceService.PropertyChanged += workspaceServicePropertyChangedEvent; }
38 Races =
new ObservableCollection<Race>(races);
39 Races.CollectionChanged += Races_CollectionChanged;
40 _workspaceService = workspaceService;
41 if (_workspaceService !=
null) { _workspaceService.PropertyChanged += workspaceServicePropertyChangedEvent; }
43 Races_CollectionChanged(
Races,
null);
54 Races.CollectionChanged += Races_CollectionChanged;
55 _workspaceService = workspaceService;
56 if (_workspaceService !=
null) { _workspaceService.PropertyChanged += workspaceServicePropertyChangedEvent; }
58 Races_CollectionChanged(
Races,
null);
70 if (other ==
null) {
return; }
74 Races =
new ObservableCollection<Race>(other.
Races.Select(item =>
new Race(item, deepCloneRaces)));
79 Races =
new ObservableCollection<Race>(other.
Races);
82 Races.CollectionChanged += Races_CollectionChanged;
83 _workspaceService = workspaceService ?? other._workspaceService;
84 if (_workspaceService !=
null) { _workspaceService.PropertyChanged += workspaceServicePropertyChangedEvent; }
87 Races_CollectionChanged(
Races,
null);
95 if (_workspaceService !=
null) { _workspaceService.PropertyChanged -= workspaceServicePropertyChangedEvent; }
102 #region Basic properties
107 public ObservableCollection<Race>
Races {
get;
set; }
113 #region Not assigned starts
115 private List<PersonStart> _notAssignedStarts;
122 get => _notAssignedStarts;
123 set => SetProperty(ref _notAssignedStarts, value);
133 if (allStarts ==
null)
137 else if (raceStarts ==
null)
143 NotAssignedStarts = allStarts?.Except(raceStarts)?.Where(s => s.IsActive)?.ToList();
147 OnPropertyChanged(nameof(
IsValid));
154 #region Other properties
156 private int _variantID;
164 set => SetProperty(ref _variantID, value);
167 private bool _isPersistent;
174 get => _isPersistent;
177 if (SetProperty(ref _isPersistent, value))
184 private bool _keepWhileRacesCalculation;
192 set => SetProperty(ref _keepWhileRacesCalculation, value);
199 #region Validation properties
224 #region Event handlers
226 private void workspaceServicePropertyChangedEvent(
object sender, PropertyChangedEventArgs e)
239 OnPropertyChanged(nameof(
Races));
242 race.Starts.CollectionChanged -= raceStartsCollectionChangedEventHandler;
243 race.Starts.CollectionChanged += raceStartsCollectionChangedEventHandler;
247 private void raceStartsCollectionChangedEventHandler(
object sender, NotifyCollectionChangedEventArgs e)
250 OnPropertyChanged(nameof(
Races));
253 OnPropertyChanged(nameof(
IsValid));
256 private void Races_CollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
263 OnPropertyChanged(nameof(
IsValid));
283 #region Helper methods
291 return Races.SelectMany(r => r.Starts).ToList();
298 #region Score properties
307 double weightSingleStarts = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_SINGLE_STARTS) ?? 5;
308 double weightSameStyleSequence = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_SAME_STYLE_SEQUENCE) ?? 5;
309 double weightPersonStartPauses = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_PERSON_START_PAUSES) ?? 60;
310 double weightStyleOrder = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_STYLE_ORDER) ?? 10;
311 double weightStartGenders = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_START_GENDERS) ?? 5;
312 double weightFriendships = _workspaceService?.Settings?.GetSettingValue<
double>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_WEIGHT_FRIENDSHIP) ?? 5;
313 weightFriendships = AreFriendshipsDefined ? weightFriendships : 0;
314 double sumWeights = weightSingleStarts + weightSameStyleSequence + weightPersonStartPauses + weightStyleOrder + weightStartGenders + weightFriendships;
317 score += ScoreSingleStarts * (weightSingleStarts / sumWeights);
318 score += ScoreSameStyleSequence * (weightSameStyleSequence / sumWeights);
319 score += ScorePersonStartPauses * (weightPersonStartPauses / sumWeights);
320 score += ScoreStyleOrder * (weightStyleOrder / sumWeights);
321 score += ScoreStartGenders * (weightStartGenders / sumWeights);
322 score += ScoreFriendships * (weightFriendships / sumWeights);
377 #region Additional score properties
379 private List<Person> _personStartPausesSeverelyAffectedPersons;
386 get => _personStartPausesSeverelyAffectedPersons;
389 if(SetProperty(ref _personStartPausesSeverelyAffectedPersons, value))
405 #region Score calculation methods
413 if (
Races.Count == 0)
415 ScoreSingleStarts = 0;
416 ScoreSameStyleSequence = 0;
417 ScorePersonStartPauses = 0;
419 ScoreStartGenders = 0;
420 ScoreFriendships = 0;
431 OnPropertyChanged(nameof(
Score));
444 if (
Races.Count == 0)
return 0.0;
446 int singleStarts =
Races.Count(r => r.Starts.Count == 1);
447 double ratio = (double)singleStarts /
Races.Count;
450 return 100 * (1 - ratio);
462 if (
Races.Count <= 1)
return 100.0;
464 int matchingPairs = 0;
465 for (
int i = 1; i <
Races.Count; i++)
471 return 100 * ((double)matchingPairs / (Races.Count - 1));
485 double maxPenalty = 0;
487 Dictionary<Person, int> lastRaceIndex =
new();
488 Dictionary<Person, double> individualPenalties =
new();
489 List<Person> severelyAffectedPersons =
new List<Person>();
491 uint shortPausesThreshold = _workspaceService?.Settings?.GetSettingValue<uint>(
WorkspaceSettings.GROUP_RACE_CALCULATION,
WorkspaceSettings.SETTING_RACE_CALCULATION_SHORT_PAUSE_THRESHOLD) ?? 3;
492 int severelyAffectedPersonsCount = 0;
494 for (
int i = 0; i <
Races.Count; i++)
496 foreach (var personStart
in Races[i].Starts)
499 if (!personStart.IsActive) {
continue; }
501 Person person = personStart.PersonObj;
503 if (lastRaceIndex.TryGetValue(person, out
int lastIndex))
505 int distance = i - lastIndex;
507 double personPenalty = distance
switch
513 _ => Math.Max(0, 10 - distance)
517 if (distance < shortPausesThreshold)
519 severelyAffectedPersonsCount++;
520 severelyAffectedPersons.Add(person);
524 if (!individualPenalties.ContainsKey(person) || individualPenalties[person] < personPenalty)
526 individualPenalties[person] = personPenalty;
532 lastRaceIndex[person] = i;
539 if (severelyAffectedPersonsCount > 0)
546 penalty = individualPenalties.Values.Sum();
547 double score = 100 - (penalty / maxPenalty * 100);
561 if (
Races.Count == 0)
return 100.0;
564 Dictionary<SwimmingStyles, int> StylePriorities =
new()
575 foreach (var (race, index) in
Races.Select((r, i) => (r, i)))
577 if (StylePriorities.TryGetValue(race.
Style, out
int priority))
579 int expectedMinIndex = Races.Count * priority / StylePriorities.Count;
580 if (index < expectedMinIndex)
582 penalty += (expectedMinIndex - index);
587 double maxPenalty = Races.Count * StylePriorities.Count;
588 return 100 -
LimitValue(100 * (penalty / maxPenalty), 0, 100);
600 if (
Races.Count == 0)
return 100.0;
602 int totalRaces =
Races.Count;
603 int homogeneousCount = 0;
607 List<Genders> genders = race.
Starts.Select(s => s.PersonObj.Gender).Distinct().ToList();
608 if (genders.Count == 1)
614 double score = 100.0 * homogeneousCount / totalRaces;
627 if (
Races ==
null ||
Races.Count == 0)
return 100.0;
629 double totalFriendScore = 0;
630 double maxPossibleScore = 0;
631 AreFriendshipsDefined =
false;
635 List<Person> personsInRace = race.
Starts.Select(s => s.PersonObj).ToList();
638 if (!personsInRace.Any(p => p.Friends !=
null && p.Friends.Count > 0))
continue;
640 double raceScore = 0;
643 foreach (
Person p1
in personsInRace)
647 AreFriendshipsDefined =
true;
650 List<Person> potentialFriends = p1.
Friends.Where(f =>
656 if (potentialFriends.Count == 0)
continue;
658 int friendCountInRace = personsInRace.Count(p2 => potentialFriends.Contains(p2));
660 raceScore += friendCountInRace;
661 raceMax += potentialFriends.Count;
666 totalFriendScore += raceScore / raceMax;
667 maxPossibleScore += 1;
672 if (maxPossibleScore == 0)
return 50.0;
674 double score = (totalFriendScore / maxPossibleScore) * 100.0;
687 private static double LimitValue(
double value,
double min,
double max)
689 return Math.Max(min, Math.Min(max, value));
696 #region Equality, HashCode, ToString, Clone
ushort Distance
Distance in meters for this competition (e.g.
Class describing a person.
List< Person > Friends
Friends of the person.
bool IsActive
Check if at least one of the starts in the Starts dictionary is active.
Class describing a start of a person.
Competition CompetitionObj
Reference to the competition object to which the start belongs.
Class that represents a single race.
int Distance
Distance for this Race.
ObservableCollection< PersonStart > Starts
List with all starts of this Race
SwimmingStyles Style
for this .
double _scoreSameStyleSequence
Score regarding same styles in sequence.
override int GetHashCode()
Serves as the default hash function.
static double LimitValue(double value, double min, double max)
Limit the value to [min, max] (both inclusive)
RacesVariant(List< Race > races, IWorkspaceService workspaceService=null)
Constructor for a new RacesVariant (copy an Race collection).
override bool Equals(object obj)
Compare if two RacesVariant are equal.
bool IsPersistent
If true, this RacesVariant should be persisted (to a file).
void UpdateNotAssignedStarts(List< PersonStart > allStarts)
Update the list of not assigned PersonStart objects.
object Clone()
Create a new object that has the same property values than this one.
RacesVariant(ObservableCollection< Race > races, IWorkspaceService workspaceService=null)
Constructor for a new RacesVariant (copy an Race collection).
double _scorePersonStartPauses
Score regarding pauses between person starts.
double CalculateScore()
Recalculate all scores.
double _scoreStyleOrder
Score regarding the preferred order of the styles.
RacesVariant(IWorkspaceService workspaceService=null)
Constructor for a new RacesVariant (create an empty Race collection).
double EvaluateSameStyleSequence()
Score for same styles in sequence: The more consecutive races with the same style the better.
double EvaluatePersonStartPauses()
Score for person pauses: The more pause between the starts of a person the better This returns 0 if t...
double EvaluateStartGenders()
Score for start genders: Homogenous genders in starts are better than heterogeneous ones.
bool _areFriendshipsDefined
True when at least one friendship is defined between persons in this RacesVariant.
double EvaluateStyleOrder()
Score for style order: There is a preferred order for the styles (can be defined via the workspace se...
ObservableCollection< Race > Races
List with races.
List< Person > PersonStartPausesSeverelyAffectedPersons
List of persons that are severely affected by short pauses between their starts.
double _scoreSingleStarts
Score regarding single starts.
bool IsValid_AllStartsAssigned
True when there are no empty unassigned races.
double _scoreFriendships
Score regarding friendships between persons.
~RacesVariant()
Destructor of the RacesVariant class.
bool KeepWhileRacesCalculation
Keep this RacesVariant while calculating new variants.
void updateRaceIDs()
Reassign the IDs for all races in Races starting from 1.
List< PersonStart > NotAssignedStarts
List with not assigned objects (not part of any in Races)
double Score
Overall score.
RacesVariant(RacesVariant other, bool deepClone=true, bool deepCloneRaces=true, IWorkspaceService workspaceService=null)
Create a new object that has the same property values than this one.
bool Equals(RacesVariant other)
Indicates wheather the current object is equal to another object of the same type.
bool AreStartPausesSeverelyAffectedPersonsAvailable
True if the number of persons who are severely affected by start pauses is greater than 0.
int VariantID
Number for this RacesVariant
double EvaluateSingleStarts()
Score for single starts: The less races with only one start the better.
bool IsValid_AllRacesValid
True when all Races are valid.
void UpdateRaceStartsCollectionChangedEvent()
Update the internal event handlers for the Race.Starts collection changed events.
bool IsValid
This RacesVariant is consideres valid when:
double _scoreStartGenders
Score regarding the homogenity of genders in the starts.
List< PersonStart > GetAllStarts()
Get a list with all objects of this
double EvaluateFriendships()
Score for friendships: The more friends start together in the same race the better.
Class holding all workspace settings.
Interface for a service used to manage a workspace.
WorkspaceSettings Settings
Settings for the current workspace.
SwimmingStyles
Available swimming styles.