From ef5e9a49c033f904588f934409275472046c126e Mon Sep 17 00:00:00 2001 From: axunonb Date: Fri, 4 Aug 2023 21:01:22 +0200 Subject: [PATCH 1/2] Improvements to TouramentManager.ExtensionMethods SetEntityExtensions: * Add tie-break rule handling for match points to MatchEntityExtensions. Use existing entries in MatchRuleEntity for configuration. * New Add() overloads to more easily add sets from a string (e.g. "25:20 25:19") - especialy for unit tests League.Models.MatchViewModels.EnterResultViewModel: * Remove code duplication in method MapEntityToFormFields: Sets are now added using a SetEntityExtensions method Refactoring: * Replace class PointResultNullable with modified existing class PointResult * Move methods CalculateSetPoints and Overrule from SetEntity to SetEntityExtensions * Methods ToShortTimeString and ToLongTimeString of TimeSpanExtensions are implemented using TimeOnly * Rename (unused) CloneHelperExtensions to EntityCoreExtensions Unit tests: * Add package FluentAssertions v6.11.0 to unit tests of solution * Added more unit tests to classes in TournamentManager.ExtensionMethods --- .../Axuno.BackgroundTask.Tests.csproj | 1 + Axuno.Tools.Tests/Axuno.Tools.Tests.csproj | 1 + League.Demo/League.Demo.csproj | 2 + League.Tests/League.Tests.csproj | 1 + .../MatchViewModels/EnterResultViewModel.cs | 20 +- .../CustomExtensions/MatchEntity.cs | 3 +- .../CustomExtensions/SetEntity.cs | 43 ---- .../MatchEntityExtensionTests.cs | 124 +++++++++++ .../MatchRuleEntityExtensionTests.cs | 9 +- .../SetEntityExtensionTests.cs | 12 +- .../SetEntityListExtensionTests.cs | 114 +++++----- .../PointResultTests.cs | 98 ++++++++ .../TournamentManager.Tests.csproj | 1 + ...rExtensions.cs => EntityCoreExtensions.cs} | 13 +- .../ExtensionMethods/MatchEntityExtensions.cs | 66 +++++- .../ExtensionMethods/SetEntityExtensions.cs | 55 +++++ .../SetEntityListExtensions.cs | 52 ++++- .../ExtensionMethods/TimeSpanExtensions.cs | 8 +- .../TournamentManager/Match/PointResult.cs | 143 ------------ .../Match/PointResultNullable.cs | 20 -- .../TournamentManager/PointResult.cs | 209 ++++++++++++++++++ .../TournamentManager.csproj | 6 +- 22 files changed, 681 insertions(+), 320 deletions(-) create mode 100644 TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchEntityExtensionTests.cs rename TournamentManager/TournamentManager.Tests/{DAL_CustomExtensions => ExtensionMethods}/MatchRuleEntityExtensionTests.cs (58%) rename TournamentManager/TournamentManager.Tests/{DAL_CustomExtensions => ExtensionMethods}/SetEntityExtensionTests.cs (86%) create mode 100644 TournamentManager/TournamentManager.Tests/PointResultTests.cs rename TournamentManager/TournamentManager/ExtensionMethods/{CloneHelperExtensions.cs => EntityCoreExtensions.cs} (69%) create mode 100644 TournamentManager/TournamentManager/ExtensionMethods/SetEntityExtensions.cs delete mode 100644 TournamentManager/TournamentManager/Match/PointResult.cs delete mode 100644 TournamentManager/TournamentManager/Match/PointResultNullable.cs create mode 100644 TournamentManager/TournamentManager/PointResult.cs diff --git a/Axuno.BackgroundTask.Test/Axuno.BackgroundTask.Tests.csproj b/Axuno.BackgroundTask.Test/Axuno.BackgroundTask.Tests.csproj index 18a2f357..f65b0342 100644 --- a/Axuno.BackgroundTask.Test/Axuno.BackgroundTask.Tests.csproj +++ b/Axuno.BackgroundTask.Test/Axuno.BackgroundTask.Tests.csproj @@ -6,6 +6,7 @@ + all diff --git a/Axuno.Tools.Tests/Axuno.Tools.Tests.csproj b/Axuno.Tools.Tests/Axuno.Tools.Tests.csproj index c484cedf..4bc70ff6 100644 --- a/Axuno.Tools.Tests/Axuno.Tools.Tests.csproj +++ b/Axuno.Tools.Tests/Axuno.Tools.Tests.csproj @@ -14,6 +14,7 @@ + all diff --git a/League.Demo/League.Demo.csproj b/League.Demo/League.Demo.csproj index ef8f7d61..b10c6770 100644 --- a/League.Demo/League.Demo.csproj +++ b/League.Demo/League.Demo.csproj @@ -4,6 +4,8 @@ League.Demo net6.0 en + + en;de diff --git a/League.Tests/League.Tests.csproj b/League.Tests/League.Tests.csproj index 3f804fd3..a35522e4 100644 --- a/League.Tests/League.Tests.csproj +++ b/League.Tests/League.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/League/Models/MatchViewModels/EnterResultViewModel.cs b/League/Models/MatchViewModels/EnterResultViewModel.cs index 7c4c3f46..2bb61fa3 100644 --- a/League/Models/MatchViewModels/EnterResultViewModel.cs +++ b/League/Models/MatchViewModels/EnterResultViewModel.cs @@ -12,7 +12,6 @@ using TournamentManager.DAL; using TournamentManager.DAL.EntityClasses; using TournamentManager.ExtensionMethods; -using TournamentManager.Match; using TournamentManager.ModelValidators; namespace League.Models.MatchViewModels; @@ -71,12 +70,12 @@ public void MapEntityToFormFields() Match.Sets.Sort((int)SetFieldIndex.SequenceNo, ListSortDirection.Ascending); foreach (var set in Match.Sets) { - Sets.Add(new PointResultNullable(set.HomeBallPoints, set.GuestBallPoints)); + Sets.Add(new PointResult(set.HomeBallPoints, set.GuestBallPoints)); } while (Sets.Count < _maxNumberOfSets) { - Sets.Add(new PointResultNullable(null, null)); + Sets.Add(new PointResult(default, default(int?))); } Id = Match.Id; @@ -99,18 +98,11 @@ public void MapFormFieldsToEntity() // Add sets to entity Match.Sets.Clear(true); - for (var i = 0; i < Sets.Count; i++) - { - // sets where home and guest ball points are NULL, will be ignored - if (Sets[i].Home.HasValue || Sets[i].Guest.HasValue) - { - // home or guest NULL values are invalidated with -1 - Match.Sets.Add(new SetEntity { MatchId = Match.Id, SequenceNo = i + 1, HomeBallPoints = Sets[i].Home ?? -1, GuestBallPoints = Sets[i].Guest ?? -1 }); - } - } + // sets where home or guest ball points are NULL will be ignored + Match.Sets.Add(Match.Id, Sets); // point calculation must run before validation because of tie-break handling - Match.Sets.CalculateSetPoints(Round!.SetRule, Round.MatchRule); + _ = Match.Sets.CalculateSetPoints(Round!.SetRule, Round.MatchRule); Match.Remarks = Remarks; Match.ChangeSerial++; Match.IsComplete = true; @@ -147,7 +139,7 @@ public void MapFormFieldsToEntity() [MaxLength(2000)] public string? Remarks { get; set; } - public List Sets { get; set; } = new(); + public List Sets { get; set; } = new(); #endregion diff --git a/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/MatchEntity.cs b/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/MatchEntity.cs index f6aab996..28f0ffb4 100644 --- a/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/MatchEntity.cs +++ b/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/MatchEntity.cs @@ -91,8 +91,7 @@ public void SetVenueId(long? venueId) { if (venueId == VenueId) return; - if (!OrigVenueId.HasValue) - OrigVenueId = VenueId; + OrigVenueId ??= VenueId; VenueId = venueId; diff --git a/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/SetEntity.cs b/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/SetEntity.cs index 0aec9239..46fdc0c8 100644 --- a/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/SetEntity.cs +++ b/TournamentManager/DAL/DatabaseGeneric/CustomExtensions/SetEntity.cs @@ -9,49 +9,6 @@ namespace TournamentManager.DAL.EntityClasses { public partial class SetEntity { - /// - /// Assigns "set points" to the . - /// The property will be set to - /// - /// The with the rules to apply. - public void CalculateSetPoints(SetRuleEntity setRule) - { - IsOverruled = false; - if (HomeBallPoints > GuestBallPoints) - { - HomeSetPoints = setRule.PointsSetWon; - GuestSetPoints = setRule.PointsSetLost; - } - else if (HomeBallPoints < GuestBallPoints) - { - HomeSetPoints = setRule.PointsSetLost; - GuestSetPoints = setRule.PointsSetWon; - } - else - { - HomeSetPoints = GuestSetPoints = setRule.PointsSetTie; - } - } - - /// - /// Sets home/guest ball points and home/guest set points. - /// The property will be set to , - /// while will be set to . - /// - /// - /// - /// - /// - public void Overrule(int homeBallPoints, int guestBallPoints, int homeSetPoints, int guestSetPoints) - { - HomeBallPoints = homeBallPoints; - GuestBallPoints = guestBallPoints; - HomeSetPoints = homeSetPoints; - GuestSetPoints = guestSetPoints; - IsTieBreak = false; - IsOverruled = true; - } - protected override void OnBeforeEntitySave() { var now = DateTime.UtcNow; diff --git a/TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchEntityExtensionTests.cs b/TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchEntityExtensionTests.cs new file mode 100644 index 00000000..b2223874 --- /dev/null +++ b/TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchEntityExtensionTests.cs @@ -0,0 +1,124 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using FluentAssertions; +using TournamentManager.DAL.EntityClasses; +using TournamentManager.ExtensionMethods; +using TournamentManager.ModelValidators; +using TournamentManager.MultiTenancy; + +namespace TournamentManager.Tests.ExtensionMethods; + +[TestFixture] +public class MatchEntityExtensionTests +{ + [Test] + public void Calc_With_No_Sets_Should_Not_Throw() + { + var matchRule = GetMatchRule_NoTieBreakRule(); + var setRule = GetSetRule(); + var match = new MatchEntity(1234); + + Assert.That(del: () => _ = match.Sets.CalculateSetPoints(setRule, matchRule), Throws.Nothing); + } + + [TestCase("25:1 25:2 25:3", 3, 0)] + [TestCase("25:1 25:2 1:25 1:25 15:1", 3, 0)] + [TestCase("1:25 2:25 2:25", 0, 3)] + [TestCase("1:25 2:25 25:13 25:1 1:15", 0, 3)] + [TestCase("1:25 25:1", 1, 1)] + public void Calc_MatchPoints_No_TieBreakRule(string setResults, int expectedHome, int expectedGuest) + { + // Note: Test cases are not validated against the rules here, but they are valid. + // Validation is tested in ModelValidator tests + + var matchRule = GetMatchRule_NoTieBreakRule(); + var setRule = GetSetRule(); + var match = new MatchEntity(1234); + match.Sets.Add(match.Id, setResults); + _ = match.Sets.CalculateSetPoints(setRule, matchRule); + _ = match.CalculateMatchPoints(matchRule); + + var pointResult = new PointResult(match.HomePoints, match.GuestPoints); + var expectedResult = new PointResult(expectedHome, expectedGuest); + + pointResult.Should().BeEquivalentTo(expectedResult); + } + + [TestCase("25:1 25:2 25:3", 3, 0)] + [TestCase("25:1 25:2 1:25 1:25 15:1", 2, 1)] + [TestCase("1:25 2:25 2:25", 0, 3)] + [TestCase("1:25 2:25 25:13 25:1 1:15", 1, 2)] + [TestCase("1:25 25:1", 1, 1)] + public void Calc_MatchPoints_With_TieBreakRule(string setResults, int expectedHome, int expectedGuest) + { + // Note: Test cases are not validated against the rules here, but they are valid. + // Validation is tested in ModelValidator tests + + var matchRule = GetMatchRule_TieBreakRule(); + var setRule = GetSetRule(); + var match = new MatchEntity(1234); + match.Sets.Add(match.Id, setResults); + _ = match.Sets.CalculateSetPoints(setRule, matchRule); + _ = match.CalculateMatchPoints(matchRule); + + var pointResult = new PointResult(match.HomePoints, match.GuestPoints); + var expectedResult = new PointResult(expectedHome, expectedGuest); + + pointResult.Should().BeEquivalentTo(expectedResult); + } + + [Test] + public void Calc_MatchPoints_With_TieBreakRule_Throws() + { + var matchRule = GetMatchRule_TieBreakRule(); + var setRule = GetSetRule(); + var match = new MatchEntity(1234); + match.Sets.Add(match.Id, "25:1 25:2 1:25 1:25 15:1"); + _ = match.Sets.CalculateSetPoints(setRule, matchRule); + + // This will trigger an exception, because the set points are tie + var lastSet = match.Sets.Last(); + lastSet.HomeSetPoints = lastSet.GuestSetPoints = 0; + + Assert.That(del: () => _ = match.CalculateMatchPoints(matchRule), Throws.InvalidOperationException); + } + + private static MatchRuleEntity GetMatchRule_NoTieBreakRule() + { + return new MatchRuleEntity { + BestOf = true, + NumOfSets = 3, + PointsMatchWon = 3, + PointsMatchLost = 0, + PointsMatchTie = 1 + }; + } + + private static MatchRuleEntity GetMatchRule_TieBreakRule() + { + return new MatchRuleEntity { + BestOf = true, + NumOfSets = 3, + PointsMatchWon = 3, + PointsMatchLost = 0, + PointsMatchWonAfterTieBreak = 2, + PointsMatchLostAfterTieBreak = 1, + PointsMatchTie = 1 + }; + } + + private static SetRuleEntity GetSetRule() + { + return new SetRuleEntity { + NumOfPointsToWinRegular = 25, + NumOfPointsToWinTiebreak = 15, + PointsDiffToWinRegular = 2, + PointsDiffToWinTiebreak = 2, + PointsSetWon = 1, + PointsSetLost = 0, + PointsSetTie = 0 + }; + } +} diff --git a/TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/MatchRuleEntityExtensionTests.cs b/TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchRuleEntityExtensionTests.cs similarity index 58% rename from TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/MatchRuleEntityExtensionTests.cs rename to TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchRuleEntityExtensionTests.cs index 961c8256..db79500e 100644 --- a/TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/MatchRuleEntityExtensionTests.cs +++ b/TournamentManager/TournamentManager.Tests/ExtensionMethods/MatchRuleEntityExtensionTests.cs @@ -1,7 +1,10 @@ -using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using TournamentManager.DAL.EntityClasses; -namespace TournamentManager.Tests.DAL_CustomExtensions; +namespace TournamentManager.Tests.ExtensionMethods; [TestFixture] public class MatchRuleEntityExtensionTests @@ -11,7 +14,7 @@ public class MatchRuleEntityExtensionTests [TestCase(true, 2, 3)] public void Calculate_Max_Number_Of_Sets(bool isBestOf, int numOfSets, int expected) { - var rule = new MatchRuleEntity {BestOf = isBestOf, NumOfSets = numOfSets}; + var rule = new MatchRuleEntity { BestOf = isBestOf, NumOfSets = numOfSets }; Assert.AreEqual(expected, rule.MaxNumOfSets()); } } diff --git a/TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/SetEntityExtensionTests.cs b/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityExtensionTests.cs similarity index 86% rename from TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/SetEntityExtensionTests.cs rename to TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityExtensionTests.cs index 7638ef9e..dd3aecd6 100644 --- a/TournamentManager/TournamentManager.Tests/DAL_CustomExtensions/SetEntityExtensionTests.cs +++ b/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityExtensionTests.cs @@ -5,18 +5,18 @@ using TournamentManager.DAL.EntityClasses; using TournamentManager.ExtensionMethods; -namespace TournamentManager.Tests.DAL_CustomExtensions; +namespace TournamentManager.Tests.ExtensionMethods; [TestFixture] public class SetEntityExtensionTests { - [TestCase(25, 1, 3,1)] + [TestCase(25, 1, 3, 1)] [TestCase(1, 25, 1, 3)] [TestCase(25, 25, 2, 2)] public void Calculate_Set_Points(int homeBallPts, int guestBallPts, int expectedHomeSetPts, int expectedGuestSetPts) { - var set = new SetEntity {HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts}; - var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 2}; + var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts }; + var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 2 }; set.CalculateSetPoints(setRule); Assert.Multiple(() => { @@ -30,7 +30,7 @@ public void Calculate_Set_Points(int homeBallPts, int guestBallPts, int expected [TestCase(1, 25, 1, 2)] public void Overrule_Set_Points(int homeBallPts, int guestBallPts, int homeSetPts, int guestSetPts) { - var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts, IsTieBreak = true, IsOverruled = false}; + var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts, IsTieBreak = true, IsOverruled = false }; set.Overrule(homeBallPts, guestBallPts, homeSetPts, guestSetPts); Assert.Multiple(() => { @@ -40,4 +40,4 @@ public void Overrule_Set_Points(int homeBallPts, int guestBallPts, int homeSetPt Assert.IsTrue(set.IsOverruled); }); } -} \ No newline at end of file +} diff --git a/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityListExtensionTests.cs b/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityListExtensionTests.cs index a9d8909c..0840e399 100644 --- a/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityListExtensionTests.cs +++ b/TournamentManager/TournamentManager.Tests/ExtensionMethods/SetEntityListExtensionTests.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using NuGet.Frameworks; using NUnit.Framework; using TournamentManager.DAL.EntityClasses; using TournamentManager.ExtensionMethods; @@ -12,96 +9,95 @@ namespace TournamentManager.Tests.ExtensionMethods; [TestFixture] public class SetEntityListExtensionTests { - [TestCase(25, 1, 25, 1, 25, 1, 9, 3)] - [TestCase(1, 25, 1, 25, 1, 25, 3, 9)] - [TestCase(25, 25, 25, 25, 25, 25, 6, 6)] - public void Calculate_Set_Points_NoTieBreak(int s1HomeBall, int s1GuestBall, int s2HomeBall, int s2GuestBall, int s3HomeBall, int s3GuestBall, int expectedHomeSetPts, int expectedGuestSetPts) + [TestCase("25:1 25:1 25:1", 9, 3)] + [TestCase("1:25 1:25 1:25", 3, 9)] + [TestCase("25:25 25:25 25:25",6, 6)] + public void Calculate_Set_Points_NoTieBreak(string setResults, int expectedHomeSetPts, int expectedGuestSetPts) { - var sets = new List - { - new() {HomeBallPoints = s1HomeBall, GuestBallPoints = s1GuestBall}, - new() {HomeBallPoints = s2HomeBall, GuestBallPoints = s2GuestBall}, - new() {HomeBallPoints = s3HomeBall, GuestBallPoints = s3GuestBall} - }; + var sets = new List { { -1, setResults } }; + var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 2 }; var matchRule = new MatchRuleEntity { BestOf = false, NumOfSets = 3 }; sets.CalculateSetPoints(setRule, matchRule); Assert.Multiple(() => { - Assert.AreEqual(expectedHomeSetPts, sets.GetSetPoints().Home); - Assert.AreEqual(expectedGuestSetPts, sets.GetSetPoints().Guest); + Assert.That(expectedHomeSetPts, Is.EqualTo(sets.GetSetPoints().Home)); + Assert.That(expectedGuestSetPts, Is.EqualTo(sets.GetSetPoints().Guest)); }); } - [TestCase(25, 1, 1, 25, 25, 1, 7, 5, true)] - [TestCase(25, 1, 25, 1, 25, 1, 9, 3, false)] - [TestCase(25, 25, 25, 25, 25, 25, 0, 0, false)] - public void Calculate_Set_Points_TieBreak(int s1HomeBall, int s1GuestBall, int s2HomeBall, int s2GuestBall, int s3HomeBall, int s3GuestBall, int expectedHomeSetPts, int expectedGuestSetPts, bool expectedTieBreak) + [TestCase("25:1 1:25 25:1", 7, 5, true)] + [TestCase("25:1 25:1 25:1", 9, 3, false)] + [TestCase("25:25 25:25 25:25", 0, 0, false)] + public void Calculate_Set_Points_TieBreak(string setResults, int expectedHomeSetPts, int expectedGuestSetPts, bool expectedTieBreak) { - var sets = new List - { - new() {HomeBallPoints = s1HomeBall, GuestBallPoints = s1GuestBall}, - new() {HomeBallPoints = s2HomeBall, GuestBallPoints = s2GuestBall}, - new() {HomeBallPoints = s3HomeBall, GuestBallPoints = s3GuestBall} - }; + var sets = new List { { -1, setResults } }; + var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 0 }; var matchRule = new MatchRuleEntity { BestOf = true, NumOfSets = 2 }; sets.CalculateSetPoints(setRule, matchRule); Assert.Multiple(() => { - Assert.AreEqual(expectedHomeSetPts, sets.GetSetPoints().Home); - Assert.AreEqual(expectedGuestSetPts, sets.GetSetPoints().Guest); - Assert.AreEqual(expectedTieBreak, sets.Last().IsTieBreak); + Assert.That(expectedHomeSetPts, Is.EqualTo(sets.GetSetPoints().Home)); + Assert.That(expectedGuestSetPts, Is.EqualTo(sets.GetSetPoints().Guest)); + Assert.That(expectedTieBreak, Is.EqualTo(sets.Last().IsTieBreak)); }); } + [Test] + public void Sequence_Of_Sets_Is_As_Played() + { + var sets = new List { { -1, "25:1 25:2 25:3" } }; + + Assert.That(sets.Count, Is.EqualTo(3)); + Assert.That(sets.All(s => s.SequenceNo == s.GuestBallPoints)); + } + + [Test] + public void All_Sets_Contain_MatchId() + { + var sets = new List { { 12345, "25:1 25:2 25:3" } }; + + Assert.That(sets.Count, Is.EqualTo(3)); + Assert.That(sets.All(s => s.MatchId == 12345)); + } + [TestCase(25, 1, 5, 4)] [TestCase(25, 1, 2, 0)] [TestCase(1, 25, 1, 2)] public void Overrule_Set_Points(int homeBallPts, int guestBallPts, int homeSetPts, int guestSetPts) { - var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts, IsTieBreak = true, IsOverruled = false}; + var set = new SetEntity {IsTieBreak = true} ; set.Overrule(homeBallPts, guestBallPts, homeSetPts, guestSetPts); Assert.Multiple(() => { - Assert.AreEqual(homeSetPts, set.HomeSetPoints); - Assert.AreEqual(guestSetPts, set.GuestSetPoints); - Assert.IsFalse(set.IsTieBreak); - Assert.IsTrue(set.IsOverruled); + Assert.That(homeSetPts, Is.EqualTo(set.HomeSetPoints)); + Assert.That(guestSetPts, Is.EqualTo(set.GuestSetPoints)); + Assert.That(set.IsTieBreak, Is.False); // reset by overrule + Assert.That(set.IsOverruled, Is.True); // set by overrule }); } - [TestCase(25, 1, 25, 1, 25, 1, 3, 0)] - [TestCase(1, 25, 1, 25, 1, 25, 0, 3)] - [TestCase(25, 25, 25, 25, 25, 25, 0, 0)] - public void Get_Sets_Won(int s1HomeBall, int s1GuestBall, int s2HomeBall, int s2GuestBall, int s3HomeBall, int s3GuestBall, int expectedWonHome, int expectedWonGuest) + [TestCase("25:1 25:1 25:1", 3, 0)] + [TestCase("1:25 1:25 1:25", 0, 3)] + [TestCase("25:25 25:25 25:25", 0, 0)] + public void Get_Sets_Won(string setResults, int expectedWonHome, int expectedWonGuest) { - var sets = new List - { - new() {HomeBallPoints = s1HomeBall, GuestBallPoints = s1GuestBall}, - new() {HomeBallPoints = s2HomeBall, GuestBallPoints = s2GuestBall}, - new() {HomeBallPoints = s3HomeBall, GuestBallPoints = s3GuestBall} - }; + var sets = new List { { -1, setResults } }; Assert.Multiple(() => { - Assert.AreEqual(expectedWonHome, sets.GetSetsWon().Home); - Assert.AreEqual(expectedWonGuest, sets.GetSetsWon().Guest); - Assert.AreEqual(expectedWonHome > expectedWonGuest ? expectedWonHome : expectedWonGuest, sets.MaxBestOf()); + Assert.That(expectedWonHome, Is.EqualTo(sets.GetSetsWon().Home)); + Assert.That(expectedWonGuest, Is.EqualTo(sets.GetSetsWon().Guest)); + Assert.That(expectedWonHome > expectedWonGuest ? expectedWonHome : expectedWonGuest, Is.EqualTo(sets.MaxBestOf())); }); } - [TestCase(0, 0, 0, 0, 0, 0, 0)] - [TestCase(25, 1, 25, 1, 25, 1, 78)] - public void Get_Sets_Total_Ball_Points(int s1HomeBall, int s1GuestBall, int s2HomeBall, int s2GuestBall, - int s3HomeBall, int s3GuestBall, int expected) + [TestCase("0:0 0:0 0:0", 0)] + [TestCase("25:1 25:1 25:1", 78)] + public void Get_Sets_Total_Ball_Points(string setResults, int expected) { - var sets = new List - { - new() {HomeBallPoints = s1HomeBall, GuestBallPoints = s1GuestBall}, - new() {HomeBallPoints = s2HomeBall, GuestBallPoints = s2GuestBall}, - new() {HomeBallPoints = s3HomeBall, GuestBallPoints = s3GuestBall} - }; + var sets = new List { { -1, setResults } }; - Assert.AreEqual(expected, sets.GetTotalBallPoints()); + Assert.That(expected, Is.EqualTo(sets.GetTotalBallPoints())); } -} \ No newline at end of file +} diff --git a/TournamentManager/TournamentManager.Tests/PointResultTests.cs b/TournamentManager/TournamentManager.Tests/PointResultTests.cs new file mode 100644 index 00000000..4acca233 --- /dev/null +++ b/TournamentManager/TournamentManager.Tests/PointResultTests.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using NUnit.Framework; + +namespace TournamentManager.Tests; + +[TestFixture] +public class PointResultTests +{ + [Test] + public void Create_Result_From_String_Containing_Only_Separator() + { + var pointResult = new PointResult("-", '-'); + new PointResult(null, null) { PointsSeparator = '-' }.Should().BeEquivalentTo(pointResult); + } + + [TestCase(12, 25)] + [TestCase(null, null)] + public void Create_Result_From_Nullable_Int(int? home, int? guest) + { + var pointResult = new PointResult(home, guest); + + Assert.That(pointResult.Home == home); + Assert.That(pointResult.Guest == guest); + Assert.That(pointResult.ToString(), Is.EqualTo($"{(home != null ? home.Value : "-")}:{(guest != null ? guest.Value : "-")}")); + } + + [Test] + public void Add_Integer_Results() + { + var pointResult1 = new PointResult("1:25"); + var pointResult2 = new PointResult("25:1"); + + var result = pointResult1 + pointResult2; + + Assert.That(result is { Home: 26, Guest: 26 }); + } + + [Test] + public void Add_Null_Results() + { + var pointResult1 = new PointResult("1:25"); + var pointResult2 = new PointResult(null, null); + + var result = pointResult1 + pointResult2; + + Assert.That(result is { Home: 1, Guest: 25 }); + } + + [Test] + public void Subtract_Integer_Results() + { + var pointResult1 = new PointResult("25:23"); + var pointResult2 = new PointResult("23:21"); + + var result = pointResult1 - pointResult2; + + Assert.That(result is { Home: 2, Guest: 2 }); + } + + [Test] + public void Compare_Results_For_Equality() + { + var pointResult1 = new PointResult("25:18"); + var pointResult2 = new PointResult("25:18"); + + Assert.That(pointResult1 == pointResult2, Is.True); + Assert.That(pointResult1 != pointResult2, Is.False); + } + + [TestCase("25:0", "25:1", true)] + [TestCase("0:25", "25:0", false)] + [TestCase("0:25", "1:25", false)] + [TestCase("4:25", "1:25", true)] + [TestCase("25:4", "25:1", false)] + public void Compare_Results_A_more_than_B(string a, string b, bool expected) + { + var pointResult1 = new PointResult(a); + var pointResult2 = new PointResult(b); + + Assert.That(pointResult1 > pointResult2, Is.EqualTo(expected)); + Assert.That(pointResult1 < pointResult2, Is.EqualTo(!expected)); + } + + [Test] + public void Compare_Results_Null_Results() + { + var pointResult = new PointResult(25, 1); + + Assert.That(pointResult.CompareTo(null), Is.EqualTo(1)); + Assert.That(pointResult.Compare(null, pointResult), Is.EqualTo(-1)); + Assert.That(pointResult.Compare(null, null), Is.EqualTo(0)); + } +} diff --git a/TournamentManager/TournamentManager.Tests/TournamentManager.Tests.csproj b/TournamentManager/TournamentManager.Tests/TournamentManager.Tests.csproj index d6f9dd4e..18fcde02 100644 --- a/TournamentManager/TournamentManager.Tests/TournamentManager.Tests.csproj +++ b/TournamentManager/TournamentManager.Tests/TournamentManager.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/TournamentManager/TournamentManager/ExtensionMethods/CloneHelperExtensions.cs b/TournamentManager/TournamentManager/ExtensionMethods/EntityCoreExtensions.cs similarity index 69% rename from TournamentManager/TournamentManager/ExtensionMethods/CloneHelperExtensions.cs rename to TournamentManager/TournamentManager/ExtensionMethods/EntityCoreExtensions.cs index 3c38e096..4a5c0a06 100644 --- a/TournamentManager/TournamentManager/ExtensionMethods/CloneHelperExtensions.cs +++ b/TournamentManager/TournamentManager/ExtensionMethods/EntityCoreExtensions.cs @@ -1,20 +1,15 @@ using System.IO; using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using SD.LLBLGen.Pro.ORMSupportClasses; namespace TournamentManager.ExtensionMethods; /// -/// For cloning an Entity and all related entities, i.e. the whole graph +/// Extension methods for types implementing . /// -public static class CloneHelperExtensions +public static class EntityCoreExtensions { - static CloneHelperExtensions() - { - } - - public static void CloneEntity(T sourceObject, out T targetObject) where T : class, IEntityCore + public static void CloneEntity(this IEntityCore sourceObject, out T targetObject) where T : class, IEntityCore { var ms = new MemoryStream(); var bf = new DataContractSerializer(typeof(T)); @@ -35,4 +30,4 @@ public static void ResetEntityAsNew(this IEntityCore entity) entity.Fields[f].IsChanged = true; } } -} \ No newline at end of file +} diff --git a/TournamentManager/TournamentManager/ExtensionMethods/MatchEntityExtensions.cs b/TournamentManager/TournamentManager/ExtensionMethods/MatchEntityExtensions.cs index 40cc62c7..cb0eb6f6 100644 --- a/TournamentManager/TournamentManager/ExtensionMethods/MatchEntityExtensions.cs +++ b/TournamentManager/TournamentManager/ExtensionMethods/MatchEntityExtensions.cs @@ -1,24 +1,54 @@ using System; -using System.Collections.Generic; using System.Linq; using TournamentManager.DAL.EntityClasses; namespace TournamentManager.ExtensionMethods; /// -/// extensions, which can't be located in generic CustomExtensions -/// because of dependencies to classes of . +/// extension methods. /// public static class MatchEntityExtensions { + /// + /// Calculates the match points following the defined rules + /// and stores them in the . + /// + /// + /// + /// public static MatchEntity CalculateMatchPoints(this MatchEntity match, MatchRuleEntity matchRule) { if (!match.Sets.Any()) return match; + var hasTieBreak = match.Sets.Any(s => s.IsTieBreak); match.IsOverruled = match.Sets.Any(s => s.IsOverruled); - var setPoints = match.Sets.GetSetPoints(); + // Only check PointsMatchWonAfterTieBreak for not zero, + // because for PointsMatchLostAfterTieBreak it might be the desired value + if (hasTieBreak && matchRule.PointsMatchWonAfterTieBreak != 0) + { + // Special match point distribution for tie-breaks + TieBreakRule(match, matchRule, setPoints); + } + else + { + // Regular match point distribution + NoTieBreakRule(match, matchRule, setPoints); + } + + match.IsComplete = true; + return match; + } + + /// + /// This method gets called, when the + /// and the are zero, + /// or the match was won without a tie-break. + /// + /// Throws if set result is tie. + private static void NoTieBreakRule(MatchEntity match, MatchRuleEntity matchRule, IOpponent setPoints) + { if (setPoints.Home < setPoints.Guest) { match.HomePoints = matchRule.PointsMatchLost; @@ -33,9 +63,29 @@ public static MatchEntity CalculateMatchPoints(this MatchEntity match, MatchRule { match.HomePoints = match.GuestPoints = matchRule.PointsMatchTie; } + } - match.IsComplete = true; - - return match; + /// + /// Usually, there are 3 match points to distribute, if the tie-break rule applies: + /// a) If a team wins without a tie-break, the match points are 3:0 + /// b) If a team wins after a tie-break, the match points are 2:1 + /// + private static void TieBreakRule(MatchEntity match, MatchRuleEntity matchRule, IOpponent setPoints) + { + if (setPoints.Home < setPoints.Guest) + { + match.HomePoints = matchRule.PointsMatchLostAfterTieBreak; + match.GuestPoints = matchRule.PointsMatchWonAfterTieBreak; + } + else if (setPoints.Home > setPoints.Guest) + { + match.HomePoints = matchRule.PointsMatchWonAfterTieBreak; + match.GuestPoints = matchRule.PointsMatchLostAfterTieBreak; + } + else + { + throw new InvalidOperationException( + $"Set points '{setPoints.Home}:{setPoints.Guest}' is a tie result, which is not allowed using {nameof(TieBreakRule)}."); + } } -} \ No newline at end of file +} diff --git a/TournamentManager/TournamentManager/ExtensionMethods/SetEntityExtensions.cs b/TournamentManager/TournamentManager/ExtensionMethods/SetEntityExtensions.cs new file mode 100644 index 00000000..89df3339 --- /dev/null +++ b/TournamentManager/TournamentManager/ExtensionMethods/SetEntityExtensions.cs @@ -0,0 +1,55 @@ +using System.Linq; +using TournamentManager.DAL.EntityClasses; + +namespace TournamentManager.ExtensionMethods; + +/// +/// extension methods. +/// +public static class SetEntityExtensions +{ + /// + /// Assigns "set points" to the . + /// The property will be set to + /// + /// + /// The with the rules to apply. + public static void CalculateSetPoints(this SetEntity setEntity, SetRuleEntity setRule) + { + setEntity.IsOverruled = false; + if (setEntity.HomeBallPoints > setEntity.GuestBallPoints) + { + setEntity.HomeSetPoints = setRule.PointsSetWon; + setEntity.GuestSetPoints = setRule.PointsSetLost; + } + else if (setEntity.HomeBallPoints < setEntity.GuestBallPoints) + { + setEntity.HomeSetPoints = setRule.PointsSetLost; + setEntity.GuestSetPoints = setRule.PointsSetWon; + } + else + { + setEntity.HomeSetPoints = setEntity.GuestSetPoints = setRule.PointsSetTie; + } + } + + /// + /// Sets home/guest ball points and home/guest set points. + /// The property will be set to , + /// while will be set to . + /// + /// + /// + /// + /// + /// + public static void Overrule(this SetEntity setEntity, int homeBallPoints, int guestBallPoints, int homeSetPoints, int guestSetPoints) + { + setEntity.HomeBallPoints = homeBallPoints; + setEntity.GuestBallPoints = guestBallPoints; + setEntity.HomeSetPoints = homeSetPoints; + setEntity.GuestSetPoints = guestSetPoints; + setEntity.IsTieBreak = false; + setEntity.IsOverruled = true; + } +} diff --git a/TournamentManager/TournamentManager/ExtensionMethods/SetEntityListExtensions.cs b/TournamentManager/TournamentManager/ExtensionMethods/SetEntityListExtensions.cs index a851ef9f..7bf79e62 100644 --- a/TournamentManager/TournamentManager/ExtensionMethods/SetEntityListExtensions.cs +++ b/TournamentManager/TournamentManager/ExtensionMethods/SetEntityListExtensions.cs @@ -17,7 +17,7 @@ public static class SetEntityListExtensions /// /// The for which the calculation takes place. /// Returns a with the number of won sets for home and guest team. - public static IOpponent GetSetsWon(this IList setList) + public static IOpponent GetSetsWon(this IList setList) { var setsWon = new PointResult(setList.Count(set => set.HomeBallPoints > set.GuestBallPoints), setList.Count(set => set.HomeBallPoints < set.GuestBallPoints)); return setsWon; @@ -28,7 +28,7 @@ public static IOpponent GetSetsWon(this IList setList) /// /// The for which the calculation takes place. /// Returns the maximum of won sets by home or guest team - public static int MaxBestOf(this IList setList) + public static int? MaxBestOf(this IList setList) { var setsWon = setList.GetSetsWon(); return setsWon.Home < setsWon.Guest ? setsWon.Guest : setsWon.Home; @@ -48,21 +48,21 @@ public static IList CalculateSetPoints(this IList setList, foreach (var set in setList) { set.CalculateSetPoints(setRule); - set.IsTieBreak = false || matchRule.BestOf && wonSets.Home == wonSets.Guest && + set.IsTieBreak = matchRule.BestOf && wonSets.Home == wonSets.Guest && wonSets.Home + wonSets.Guest == matchRule.MaxNumOfSets() - 1; wonSets.Home += set.HomeBallPoints > set.GuestBallPoints ? 1 : 0; - wonSets.Guest += set.HomeBallPoints < set.GuestBallPoints ? 0 : 1; + wonSets.Guest += set.HomeBallPoints > set.GuestBallPoints ? 0 : 1; } return setList; } /// - /// Gets the sum of set points for home and guest team. + /// Gets the sum of set points for home and guest team stored in the list s. /// /// /// Returns an with the sum of set points for home and guest team. - public static IOpponent GetSetPoints(this IList setList) + public static IOpponent GetSetPoints(this IList setList) { return new PointResult(setList.Sum(s => s.HomeSetPoints), setList.Sum(s => s.GuestSetPoints)); } @@ -76,4 +76,42 @@ public static int GetTotalBallPoints(this IList setList) { return setList.Sum(s => s.HomeBallPoints) + setList.Sum(s => s.GuestBallPoints); } -} \ No newline at end of file + + /// + /// Adds a delimited string with set results to the of . + /// + /// + /// The + /// The set results, e.g. "25:23 25:18 12:25" + /// The character to delimit sets. Default is blank. + public static void Add(this IList setList, long matchId, string setResults, char setSeparator = ' ') + { + var pointResults = setResults.Split(setSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(set => new PointResult(set)).ToList(); + + setList.Add(matchId, pointResults); + } + + /// + /// Adds a of s to the of . + /// + /// + /// The + /// The of . + public static void Add(this IList setList, long matchId, IList pointResults) + { + var sequenceNo = 0; + foreach (var pointResult in pointResults) + { + if(pointResult.Home is null || pointResult.Guest is null) + continue; + + setList.Add(new SetEntity { + MatchId = matchId, + SequenceNo = ++sequenceNo, + HomeBallPoints = pointResult.Home.Value, + GuestBallPoints = pointResult.Guest.Value + }); + } + } +} diff --git a/TournamentManager/TournamentManager/ExtensionMethods/TimeSpanExtensions.cs b/TournamentManager/TournamentManager/ExtensionMethods/TimeSpanExtensions.cs index a8a8ba76..c2986754 100644 --- a/TournamentManager/TournamentManager/ExtensionMethods/TimeSpanExtensions.cs +++ b/TournamentManager/TournamentManager/ExtensionMethods/TimeSpanExtensions.cs @@ -1,16 +1,16 @@ using System; -using System.Collections.Generic; + namespace TournamentManager.ExtensionMethods; public static class TimeSpanExtensions { public static string ToShortTimeString(this TimeSpan timeSpan) { - return DateTime.UtcNow.Date.Add(timeSpan).ToShortTimeString(); + return TimeOnly.FromTimeSpan(timeSpan).ToShortTimeString(); } public static string ToLongTimeString(this TimeSpan timeSpan) { - return DateTime.UtcNow.Date.Add(timeSpan).ToLongTimeString(); + return TimeOnly.FromTimeSpan(timeSpan).ToLongTimeString(); } -} \ No newline at end of file +} diff --git a/TournamentManager/TournamentManager/Match/PointResult.cs b/TournamentManager/TournamentManager/Match/PointResult.cs deleted file mode 100644 index 8993bb45..00000000 --- a/TournamentManager/TournamentManager/Match/PointResult.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TournamentManager.Match; - -/// -/// Class which represents any points which can occur in sports match with 2 teams. -/// -public class PointResult : IOpponent, IComparable>, IComparer> -{ - /// - /// Initializes a new instance of the class TournamentManager.Match.ResultBase - /// - /// The result as a string. - /// The separator which is used to separate home points and guest points - public PointResult(string result, string pointsSeparator = ":") - { - PointsSeparator = pointsSeparator; - var r = result.Split(new[] { PointsSeparator }, StringSplitOptions.None); - try - { - Home = int.Parse(r[0], System.Globalization.CultureInfo.CurrentCulture); - Guest = int.Parse(r[1], System.Globalization.CultureInfo.CurrentCulture); - } - catch - { - throw new Exception(); - } - } - - /// - /// Initializes a new instance of the class TournamentManager.Match.ResultBase - /// - /// The home points of the result. - /// The guest points of the result. - public PointResult(int home, int guest) - { - Home = home; - Guest = guest; - } - - static PointResult() - { - } - - /// - /// Gets or sets the separator string which is used to separate home points and guest points. - /// - public string PointsSeparator { get; set; } = ":"; - - /// - /// Gets or sets the home points of the result. - /// - public int Home { get; set; } - - /// - /// Gets or sets the guest points of the result. - /// - public int Guest { get; set; } - - /// - /// Implements - /// - /// - /// - public int CompareTo(IOpponent? other) - { - return Compare(this, other); - } - - /// - /// Implements - /// - /// - /// - /// - public int Compare(IOpponent? x, IOpponent? y) - { - if (x == null) - throw new ArgumentNullException(nameof(x)); - if (y == null) - throw new ArgumentNullException(nameof(y)); - - if (x.Home < y.Home) - return -1; - if (x.Home > y.Home) - return 1; - - // Home points are equal - - if (x.Guest < y.Guest) - return 1; - if (x.Guest > y.Guest) - return -1; - - // Home and guest points are equal - return 0; - } - - /// - /// Gets the string representation of the result using a format string with 2 placeholders, e.g. "Home: {0} - Guest: {1} " - /// - /// The format string with 2 placeholders, e.g. "Home: {0} - Guest: {1} " - /// Returns the string representation of the result using a format string. - public string ToString(string format) - { - return string.Format(format, Home, Guest); - } - - /// - /// Gets the string representation of the result using the default points separator. - /// - /// Returns the string representation of the result using the default points separator. - public override string ToString() - { - return string.Concat(Home, PointsSeparator, Guest); - } - - /// - /// Adds the 2 results. - /// - /// The result of team A. - /// The result of team B. - /// - public static PointResult operator +(PointResult a, IOpponent b) - { - return new PointResult(a.Home + b.Home, a.Guest + b.Guest); - } - - /// - /// Subtracts results A from result B. - /// - /// The result of team A. - /// The result of team B. - /// - public static PointResult operator -(PointResult a, IOpponent b) - { - if (a.Home < b.Home || a.Guest < b.Guest) - throw new ArgumentException("Operation would lead to negative points value.", b.ToString()); - - return new PointResult(a.Home - b.Home, a.Guest - b.Guest); - } -} \ No newline at end of file diff --git a/TournamentManager/TournamentManager/Match/PointResultNullable.cs b/TournamentManager/TournamentManager/Match/PointResultNullable.cs deleted file mode 100644 index 0379c939..00000000 --- a/TournamentManager/TournamentManager/Match/PointResultNullable.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace TournamentManager.Match; - -public class PointResultNullable : IOpponent -{ - public PointResultNullable() - {} - - public PointResultNullable(int? home, int? guest) - { - Home = home; - Guest = guest; - } - - public int? Home { get; set; } - public int? Guest { get; set; } -} diff --git a/TournamentManager/TournamentManager/PointResult.cs b/TournamentManager/TournamentManager/PointResult.cs new file mode 100644 index 00000000..bac86f30 --- /dev/null +++ b/TournamentManager/TournamentManager/PointResult.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; + +namespace TournamentManager; + +/// +/// Class which represents any points which can occur in sports match with 2 teams. +/// +public class PointResult : IOpponent, IComparable>, IComparer> +{ + /// + /// Initializes a new instance + /// + /// The result as a string. + /// The separators which are used to separate home points and guest points + public PointResult(string result, char pointsSeparator = ':') + { + PointsSeparator = pointsSeparator; + var r = result.Split(PointsSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (r.Length < 2) + { + Home = Guest = null; + } + else + { + Home = int.Parse(r[0], System.Globalization.CultureInfo.InvariantCulture); + Guest = int.Parse(r[1], System.Globalization.CultureInfo.InvariantCulture); + } + } + + /// + /// Initializes a new instance + /// + /// The home points of the result. + /// The guest points of the result. + public PointResult(int? home, int? guest) + { + Home = home; + Guest = guest; + } + + /// + /// Initializes a new instance + /// + public PointResult() + { + } + + /// + /// Gets or sets the separator string which is used to separate home points and guest points. + /// + public char PointsSeparator { get; set; } = ':'; + + /// + /// Gets or sets the home points of the result. + /// + public int? Home { get; set; } + + /// + /// Gets or sets the guest points of the result. + /// + public int? Guest { get; set; } + + /// + /// Implements . results are treated as zero. + /// + /// + /// + public int CompareTo(IOpponent? other) + { + return Compare(this, other); + } + + /// + /// Implements . results are treated as zero. + /// + /// + /// + /// + public int Compare(IOpponent? x, IOpponent? y) + { + // The "not null" is more than the "null" opponent + + if (x is null && y is not null) return -1; + if (x is not null && y is null) return 1; + if (x is null && y is null) return 0; + + if ((x!.Home ?? 0) < (y!.Home ?? 0)) return -1; + if ((x.Home ?? 0) > (y.Home ?? 0)) return 1; + + // Home points are equal + + if ((x.Guest ?? 0) < (y.Guest ?? 0)) return 1; + if ((x.Guest ?? 0) > (y.Guest ?? 0)) return -1; + + // Home and guest points are equal + return 0; + } + + /// + /// Gets the string representation of the result using a format string with 2 placeholders, e.g. "Home: {0} - Guest: {1} " + /// + /// The format string with 2 placeholders, e.g. "Home: {0} - Guest: {1} " + /// Returns the string representation of the result using a format string. + public string ToString(string format) + { + return string.Format(format, Home.HasValue ? Home : "-", Guest.HasValue ? Guest : "-"); + } + + /// + /// Gets the string representation of the result using the default points separator. + /// + /// Returns the string representation of the result using the default points separator. + public override string ToString() + { + return ToString($"{{0}}{PointsSeparator}{{1}}"); + } + + /// + /// Adds the 2 results. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static PointResult operator +(PointResult a, IOpponent b) + { + return new PointResult((a.Home ?? 0) + (b.Home ?? 0), (a.Guest ?? 0) + (b.Guest ?? 0)); + } + + /// + /// Subtracts results A from result B. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static PointResult operator -(PointResult a, IOpponent b) + { + if (a.Home < b.Home || a.Guest < b.Guest) + throw new ArgumentException(@"Operation would lead to negative points value.", b.ToString()); + + return new PointResult((a.Home ?? 0) - (b.Home ?? 0), (a.Guest ?? 0) - (b.Guest ?? 0)); + } + + /// + /// Compares 2 results for equality. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static bool operator ==(PointResult a, IOpponent b) + { + return a.CompareTo(b) == 0; + } + + /// + /// Compares 2 results for inequality. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static bool operator !=(PointResult a, IOpponent b) + { + return a.CompareTo(b) != 0; + } + + /// + /// Compares 2 results for less than. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static bool operator <(PointResult a, IOpponent b) + { + return a.CompareTo(b) == -1; + } + + /// + /// Compares 2 results for more than. results are treated as zero. + /// + /// The result of team A. + /// The result of team B. + /// + public static bool operator >(PointResult a, IOpponent b) + { + return a.CompareTo(b) == 1; + } + + #region ** Equality members ** + + protected bool Equals(PointResult other) + { + return PointsSeparator == other.PointsSeparator && Home == other.Home && Guest == other.Guest; + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((PointResult) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(ToString()); + } + + #endregion +} diff --git a/TournamentManager/TournamentManager/TournamentManager.csproj b/TournamentManager/TournamentManager/TournamentManager.csproj index 3c9e0f8a..0584993a 100644 --- a/TournamentManager/TournamentManager/TournamentManager.csproj +++ b/TournamentManager/TournamentManager/TournamentManager.csproj @@ -4,8 +4,10 @@ Tournament Manager net6.0 en - 6.1.0 - 6.1.0 + + en;de + 6.2.0 + 6.2.0 true 1591 TournamentManager is the backend for Volleyball League. From a2b60b7a974b58be921e85cd286ea1f207a831c1 Mon Sep 17 00:00:00 2001 From: axunonb Date: Fri, 4 Aug 2023 23:07:58 +0200 Subject: [PATCH 2/2] Bump version to v6.4.2 --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index ebb976b2..bd35665c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,8 +7,8 @@ Copyright 2011-$(CurrentYear) axuno gGmbH https://github.com/axuno/Volleyball-League true - 6.4.1 - 6.4.1 + 6.4.2 + 6.4.2 6.0.0.0 latest enable