diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinalTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinalTest.java new file mode 100644 index 00000000000..26494d78a5f --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinalTest.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal; + +public class LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinalTest { + @Test + public void testCountingPromotedCompetitors() { + final LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal scoringScheme = new LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal(); + final RaceColumnInSeries quarterFinal = mock(RaceColumnInSeries.class); + final RaceColumnInSeries semiFinal = mock(RaceColumnInSeries.class); + final RaceColumnInSeries grandFinal = mock(RaceColumnInSeries.class); + final Series medalSeries = mock(Series.class); + final Iterable medalSeriesColumns = Arrays.asList(quarterFinal, semiFinal, grandFinal); + when(medalSeries.getRaceColumns()).thenAnswer(new Answer>() { + @Override + public Iterable answer(InvocationOnMock invocation) throws Throwable { + return medalSeriesColumns; + } + }); + when(quarterFinal.getSeries()).thenReturn(medalSeries); + when(semiFinal.getSeries()).thenReturn(medalSeries); + when(grandFinal.getSeries()).thenReturn(medalSeries); + assertEquals(0, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(grandFinal)); + assertEquals(1, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(semiFinal)); + assertEquals(3, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(quarterFinal)); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java index 3cf9421a3a7..2b227c3cdf1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java @@ -116,6 +116,15 @@ public interface ScoringScheme extends Serializable { */ int compareByNumberOfRacesScored(int competitor1NumberOfRacesScored, int competitor2NumberOfRacesScored); + /** + * Having scored in a later medal series than the other is considered better. -1 means no medal series score at all. + * With a lesser result encoding "better" the direction of default integer comparison between the two parameters is + * reversed. + */ + default int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) { + return -Integer.compare(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); + } + ScoringSchemeType getType(); /** 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 b014128fda2..54757e0c5b7 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 @@ -175,6 +175,26 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { return 0; } + /** + * Returns a filtered view on the {@link Leaderboard#getRaceColumns() leaderboard's race columns}, removing + * all {@link RaceColumn#isMedalRace() medal races} from the filtered view. + */ + protected Iterable getOpeningSeriesRaceColumns(Leaderboard leaderboard) { + return Util.filter(leaderboard.getRaceColumns(), rc->!rc.isMedalRace()); + } + + protected LeaderboardTotalRankComparator getOpeningSeriesRankComparator(boolean nullScoresAreBetter, + TimePoint timePoint, Leaderboard leaderboard, + BiFunction totalPointsSupplier, + WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) { + final Iterable openingSeriesRaceColumns = getOpeningSeriesRaceColumns(leaderboard); + // pass on the totalPointsSupplier coming from the caller, most likely a LeaderboardTotalRankComparator, + // to speed up / save the total points (re-)calculation + final LeaderboardTotalRankComparator openingSeriesRankComparator = + new LeaderboardTotalRankComparator(leaderboard, timePoint, this, nullScoresAreBetter, openingSeriesRaceColumns, totalPointsSupplier, cache); + return openingSeriesRankComparator; + } + /** * Looks backwards starting at the last race until the first score difference is found, including the discarded * scored. This implements Racing Rules of Sailing (RRS) section A8.2: diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java index d1fbc91ab76..eafe3ddfbc5 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java @@ -249,7 +249,8 @@ public class LeaderboardTotalRankComparator implements Comparator { } } } - int result = compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); + // TODO bug5877: pass leaderboard and competitors and a totalPointsSupplier (based on totalPointsCache, see call to compareByBetterScore below) to allow for identifying competitors promoted through to later medal race + int result = scoringScheme.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); if (result == 0) { result = defaultFleetBasedComparisonResult; if (result == 0) { @@ -466,15 +467,6 @@ public class LeaderboardTotalRankComparator implements Comparator { return result; } - /** - * Having scored in a later medal series than the other is considered better. -1 means no medal series score at all. - * With a lesser result encoding "better" the direction of default integer comparison between the two parameters is - * reversed. - */ - private int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) { - return -Integer.compare(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); - } - /** * Returns a comparator for comparing individual scores. This implementation returns a comparator for the usual ISAF * scheme where lesser scores compare "better" which again means "lesser." Therefore, the comparator retunred compares diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinThreeRaces.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinThreeRaces.java index ce1da1d757f..6d33675036f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinThreeRaces.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinThreeRaces.java @@ -392,11 +392,9 @@ public class LowPointFirstToWinThreeRaces extends LowPoint { result = compareByA81TieBreak(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard, discardedRaceColumnsPerCompetitor, totalPointsSupplier, cache); } else { - final Iterable openingSeriesRaceColumns = getOpeningSeriesRaceColumns(leaderboard); - // pass on the totalPointsSupplier coming from the caller, most likely a LeaderboardTotalRankComparator, - // to speed up / save the total points (re-)calculation - result = new LeaderboardTotalRankComparator(leaderboard, timePoint, this, nullScoresAreBetter, openingSeriesRaceColumns, totalPointsSupplier, cache) - .compare(o1, o2); + final LeaderboardTotalRankComparator openingSeriesRankComparator = getOpeningSeriesRankComparator( + nullScoresAreBetter, timePoint, leaderboard, totalPointsSupplier, cache); + result = openingSeriesRankComparator.compare(o1, o2); } return result; } @@ -418,13 +416,6 @@ public class LowPointFirstToWinThreeRaces extends LowPoint { discardedRaceColumnsPerCompetitor, totalPointsSupplier, cache); } - /** - * Merge non-medal series columns, preserving order across {@code raceColumnsO1} and {@code raceColumnsO2}. - */ - private Iterable getOpeningSeriesRaceColumns(Leaderboard leaderboard) { - return Util.filter(leaderboard.getRaceColumns(), rc->!rc.isMedalRace()); - } - private boolean hasMedalScores(List> o1Scores) { return o1Scores.stream().anyMatch(p->p.getA().isMedalRace() && p.getB() != null); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal.java new file mode 100644 index 00000000000..75cd82eb5de --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.leaderboard.impl; + +public class LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal + extends LowPointWithEliminatingMedalSeriesWithPromotions { + private static final long serialVersionUID = -6753499035563730886L; + + public LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal() { + super(new int[] { 2, 1 }); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesWithPromotions.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesWithPromotions.java new file mode 100644 index 00000000000..0d9ebc90d34 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminatingMedalSeriesWithPromotions.java @@ -0,0 +1,57 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import java.util.List; +import java.util.ListIterator; + +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sse.common.Util; + +public abstract class LowPointWithEliminatingMedalSeriesWithPromotions extends LowPoint { + private static final long serialVersionUID = 7759999270911627798L; + + /** + * The last index for this array refers to the last medal race; e.g., the "Grand Final" race + * in an iQFOil regatta with the medal series consisting of a "Quarter Final," a "Semi Final" + * and a "Grand Final". The last-but-one index in the example would then refer to the Semi Final, + * and so on. The array may be empty, meaning that no competitor is promoted into any medal + * race. The field always has to refer to a valid array. + */ + private final int[] numberOfPromotedCompetitorsIntoLastMedalRaces; + + /** + * + * @param numberOfPromotedCompetitorsIntoLastMedalRaces + * The last index for this array refers to the last medal race; e.g., the "Grand Final" race in an iQFOil + * regatta with the medal series consisting of a "Quarter Final," a "Semi Final" and a "Grand Final". The + * last-but-one index in the example would then refer to the Semi Final, and so on. The array may be + * empty, meaning that no competitor is promoted into any medal race. The field always has to refer to a + * valid array. + */ + public LowPointWithEliminatingMedalSeriesWithPromotions(int[] numberOfPromotedCompetitorsIntoLastMedalRaces) { + super(); + this.numberOfPromotedCompetitorsIntoLastMedalRaces = numberOfPromotedCompetitorsIntoLastMedalRaces; + } + + public int getNumberOfCompetitorsBetterThanThoseSailingInRace(RaceColumnInSeries medalRace) { + final List allMedalRacesInSeries = Util.asList(medalRace.getSeries().getRaceColumns()); + int result = 0; + int indexInNumberOfPromotedCompetitorsIntoLastMedalRaces = numberOfPromotedCompetitorsIntoLastMedalRaces.length-1; + if (allMedalRacesInSeries.size() > 1) { + for (final ListIterator i=allMedalRacesInSeries.listIterator(allMedalRacesInSeries.size()); i.hasPrevious() && i.previous() != medalRace; ) { + result += numberOfPromotedCompetitorsIntoLastMedalRaces[indexInNumberOfPromotedCompetitorsIntoLastMedalRaces--]; + } + } + return result; + } + + @Override + public int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, + int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) { + // TODO We'd like to consider those as "having participated" in a medal race that may not have a score but have been promoted already to a later medal race based on their opening series result; + // For that we need to check all medal race columns and need to know the two competitors. + // If promoted to a later medal race but not sailed in the one currently considered, consider as "has participated." + // If both participated, move on to next medal race column, if any. If last medal race column, return 0. + // If one competitor is considered as "has participated" and the other one not, prefer the one that "has participated" over the one that has not. + return super.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored); + } +}