diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index 4dcf32e1939..b0db307882e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -250,16 +250,19 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { } List ranksOfNonMedalStartedRaces = new ArrayList(); TimePoint now = MillisecondsTimePoint.now(); + int numberOfRacesToCompareToDiscardThresholds = 0; for (RaceColumn column : raceColumnsInLeaderboard.values()) { - if (!column.isMedalRace() && column.getTrackedRace(defaultFleet) != null && column.getTrackedRace(defaultFleet).hasStarted(now)) { - ranksOfNonMedalStartedRaces.add(column.getTrackedRace(defaultFleet).getRank(competitor, now)); + if (column.getTrackedRace(defaultFleet) != null && column.getTrackedRace(defaultFleet).hasStarted(now)) { + numberOfRacesToCompareToDiscardThresholds++; + if (!column.isMedalRace()) { + ranksOfNonMedalStartedRaces.add(column.getTrackedRace(defaultFleet).getRank(competitor, now)); + } } } Collections.sort(ranksOfNonMedalStartedRaces); int carryInt = (carry == null ? 0 : carry); int totalPoints = carryInt; int medalRacePoints = getMedalRacePoints(competitor, now, defaultFleet); - int numberOfRacesFromWhichToDiscard = ranksOfNonMedalStartedRaces.size(); for (TrackedRace race : testRaces) { RaceColumn raceColumn = raceColumnsInLeaderboard.get(race); Pair key = new Pair(competitor, raceColumn); @@ -274,8 +277,8 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { // One race is discarded because four races were started, and for [3-6) one race can be discarded. // The discarded race is the worst of those started, so the one with rank 4. int expectedNumberOfDiscardedRaces = - numberOfRacesFromWhichToDiscard < firstDiscardingThreshold ? 0 : - numberOfRacesFromWhichToDiscard < secondDiscardingThreshold ? 1 : 2; + numberOfRacesToCompareToDiscardThresholds < firstDiscardingThreshold ? 0 : + numberOfRacesToCompareToDiscardThresholds < secondDiscardingThreshold ? 1 : 2; boolean discarded = ranksOfNonMedalStartedRaces.indexOf(rank) >= ranksOfNonMedalStartedRaces.size()-expectedNumberOfDiscardedRaces; int expected = discarded ? 0 : rank==medalRacePoints?2*rank:rank; assertEquals(expected, leaderboard.getTotalPoints(competitor, raceColumn, now), 0.000000001); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index 23b92835b2a..9e8c9a51a65 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -228,6 +228,90 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(47.0, leaderboard.getTotalPoints(competitors.get(9), later), 0.00000001); } + /** + * Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one + * column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column doesn't + * count for computing the number of discards. + */ + @Test + public void testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException { + List competitors = createCompetitors(10); + Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "Yellow", "Blue" }, /* final */0, + new String[] { "Default" }, + /* medal */false, "testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[]{2}); // one discard for two or more races + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5)); + TrackedRace q1Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10)); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("Yellow"), q1Yellow); + q1Column.setTrackedRace(q1Column.getFleetByName("Blue"), q1Blue); + // now add one race for yellow fleet and test that there are no discards still because blue fleet is still missing its race for Q2 + TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8)); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow); + for (Competitor competitor : competitors) { + assertFalse("Competitor "+competitor+" has a discard in Q1 but shouldn't", leaderboard.isDiscarded(competitor, q1Column, later)); + assertFalse("Competitor "+competitor+" has a discard in Q2 but shouldn't", leaderboard.isDiscarded(competitor, q2Column, later)); + } + // now add a tracked race for the blue fleet for Q2 and assert that all competitors have one discard + TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, + Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2), + competitors.get(8), competitors.get(9) })); + q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue); + for (Competitor competitor : competitors) { + assertTrue("Competitor "+competitor+" has no discard but should", + leaderboard.isDiscarded(competitor, q1Column, later) || leaderboard.isDiscarded(competitor, q2Column, later)); + } + } + + /** + * Regarding bug 961, test scoring in a leaderboard that has a qualification series with two ordered groups where for one + * column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the competitors in + * that column get their discard. + */ + @Test + public void testDiscardsForOrderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException { + List competitors = createCompetitors(10); + Regatta regatta = createRegatta(/* qualifying */ 0, new String[] { "Default" }, /* final */2, + new String[] { "Gold", "Silver" }, + /* medal */false, "testDiscardsForOrderedGroupsWithOneGroupNotHavingRacedInAColumn", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[]{2}); // one discard for two or more races + Series finalSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + seriesIter.next(); + finalSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace f1Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5)); + TrackedRace f1Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(5, 10)); + RaceColumn f1Column = finalSeries.getRaceColumnByName("F1"); + f1Column.setTrackedRace(f1Column.getFleetByName("Gold"), f1Gold); + f1Column.setTrackedRace(f1Column.getFleetByName("Silver"), f1Silver); + // now add one race for yellow fleet and test that there are no discards still because blue fleet is still missing its race for Q2 + TrackedRace f2Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 5)); + RaceColumn f2Column = finalSeries.getRaceColumnByName("F2"); + f2Column.setTrackedRace(f2Column.getFleetByName("Gold"), f2Gold); + for (int i=0; i<5; i++) { + assertTrue("Competitor "+competitors.get(i)+" has no discard in F1 or F2 but should", + leaderboard.isDiscarded(competitors.get(i), f1Column, later) || + leaderboard.isDiscarded(competitors.get(i), f2Column, later)); + } + for (int i=5; i<10; i++) { + assertFalse("Competitor "+competitors.get(i)+" has a discard in F1 or F2 but shouldn't", + leaderboard.isDiscarded(competitors.get(i), f1Column, later) || + leaderboard.isDiscarded(competitors.get(i), f2Column, later)); + } + } + /** * Asserts that the competitors ranking worse than the disqualified competitor advance by one in the * {@link Leaderboard#getCompetitorsFromBestToWorst(TimePoint)} ordering. Note that this does not test @@ -238,7 +322,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { List competitors = createCompetitors(10); Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1, new String[] { "Default" }, - /* medal */false, "testOneStartedRaceWithDifferentScores", + /* medal */false, "testOneStartedRaceWithDifferentScoresAndDisqualification", DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); Series finalSeries; @@ -277,7 +361,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } Regatta regatta = createRegatta(/* qualifying */1, new String[] { "Yellow", "Blue" }, /* final */0, new String[] { "Default" }, - /* medal */false, "testAllTrackedAndStartedWithDifferentScores", + /* medal */false, "testDistributionAcrossQualifyingFleetsWithDifferentScores", DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); TimePoint now = MillisecondsTimePoint.now(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index e85792be193..b084096c0c0 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -261,11 +261,13 @@ public interface Leaderboard extends Named { * Tells if the column represented by raceColumn shall be considered when counting the number of "races * so far" for discarding. Although medal races are never discarded themselves, they still count in determining the * number of "races so far" which is then the basis for deciding how many races may be discarded. If a leaderboard - * has corrections for a column then that column shall be considered for discarding and counts for determining the - * number of races so far. Also, if a tracked race is connected to the column and has started already, the column is - * to be considered for discarding. + * has corrections for a column for the competitor then that column shall be considered for discarding + * and counts for determining the number of races so far. Also, if a tracked race is connected to the column and has + * started already, the column is to be considered for discarding unless the column has several unordered fleets and + * not all fleets have started their race yet (see + * {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, TimePoint)}). */ - boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint); + boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint); public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index 097f6632af9..4fd85c1e6b4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -94,7 +94,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { Fleet fleet = raceColumn.getFleetOfCompetitor(competitor); if (fleetsForWhichNoScoreWasFound.contains(fleet)) { try { - if (leaderboard.getTotalPoints(competitor, raceColumn, at) != null) { + if (leaderboard.getNetPoints(competitor, raceColumn, at) != null) { fleetsForWhichNoScoreWasFound.remove(fleet); } } catch (NoWindException nwe) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index ea3ac8946b4..ed36d235b10 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -336,18 +336,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint) { - boolean result = getScoreCorrection().hasCorrectionFor(raceColumn); - if (!result && !raceColumn.isMedalRace()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null && trackedRace.hasStarted(timePoint)) { - result = true; - break; - } - } - } - return result; + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + TrackedRace trackedRaceForCompetitorInColumn; + return getScoreCorrection().isScoreCorrected(competitor, raceColumn) || + (getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint) && + (trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && + trackedRaceForCompetitorInColumn.hasStarted(timePoint)); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ResultDiscardingRuleImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ResultDiscardingRuleImpl.java index da56cad8f48..9944bbbed5f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ResultDiscardingRuleImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ResultDiscardingRuleImpl.java @@ -51,7 +51,7 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR @Override public Set getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard, final TimePoint timePoint) { - int resultsToDiscard = getNumberOfResultsToDiscard(leaderboard.getRaceColumns(), leaderboard, timePoint); + int resultsToDiscard = getNumberOfResultsToDiscard(competitor, leaderboard.getRaceColumns(), leaderboard, timePoint); Set result; if (resultsToDiscard > 0) { result = new HashSet(); @@ -94,11 +94,11 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR return result; } - private int getNumberOfResultsToDiscard(Iterable raceColumns, Leaderboard leaderboard, TimePoint timePoint) { + private int getNumberOfResultsToDiscard(Competitor competitor, Iterable raceColumns, Leaderboard leaderboard, TimePoint timePoint) { int numberOfResultsToDiscard; int numberOfStartedRaces = 0; for (RaceColumn raceInLeaderboard : raceColumns) { - if (leaderboard.considerForDiscarding(raceInLeaderboard, timePoint)) { + if (leaderboard.countRaceForComparisonWithDiscardingThresholds(competitor, raceInLeaderboard, timePoint)) { numberOfStartedRaces++; } }