package tc.oc.pgm.victory; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import tc.oc.commons.core.util.Comparators; import tc.oc.commons.core.util.RankedSet; import tc.oc.pgm.match.Competitor; /** * This class contains the core logic of the VictoryMatchModule. It was factored out in * order to write tests for it, but that is still infeasible until we have a way to * mock {@link Competitor}, and probably Match as well. */ public class VictoryCalculator { /** * Conditions that determine the ranks of Competitors and the completion of the Match */ private Set<VictoryCondition> victoryConditions = new HashSet<>(); /** * Effective order in which VictoryConditions are asked to rank competitors. * The first non-zero answer wins. * * VictoryCondition that say the match is over take priority over those that don't. * After that, they are ordered by their priority property. */ private final Comparator<VictoryCondition> victoryConditionOrder = Comparators .firstIf(VictoryCondition::isCompleted) .thenComparing(VictoryCondition::priority); /** * Ordering of Competitors by their closeness to victory. * * Obviously, this is not constant, so this comparator cannot be used in a sorted collection. */ private final Comparator<Competitor> victoryOrder = (a, b) -> { // If there are any final conditions, one of them alone (arbitrarily chosen) determines the result. // Otherwise, the highest priority condition with a non-zero result is used. VictoryCondition topCondition = null; int topResult = 0; for(VictoryCondition condition : victoryConditions) { if(topCondition == null || victoryConditionOrder.compare(condition, topCondition) < 0) { final int result = condition.result().compare(a, b); if(result != 0 || condition.result().isDefinite()) { topCondition = condition; topResult = result; } } } return topResult; }; private final RankedSet<Competitor> rankedCompetitors = new RankedSet<>(victoryOrder); public void addCompetitor(Competitor competitor) { rankedCompetitors.add(competitor); } public void addCompetitors(Collection<Competitor> competitors) { rankedCompetitors.addAll(competitors); } public void removeCompetitor(Competitor competitor) { rankedCompetitors.remove(competitor); } public void invalidateRanking() { rankedCompetitors.invalidateRanking(); } public Comparator<Competitor> victoryOrder() { return victoryOrder; } public void setImmediateWinner(Competitor competitor) { setImmediateResult(new CompetitorResult(competitor)); } public void setImmediateResult(MatchResult result) { setVictoryCondition(new ImmediateVictoryCondition(result)); } public void setVictoryCondition(VictoryCondition condition) { setVictoryCondition((Class<VictoryCondition>) condition.getClass(), condition); } public <T extends VictoryCondition> void setVictoryCondition(Class<T> type, @Nullable T condition) { removeVictoryConditions(type); if(condition != null && victoryConditions.add(condition)) { invalidateRanking(); } } public void removeVictoryConditions(Class<? extends VictoryCondition> type) { boolean changed = false; for(Iterator<VictoryCondition> iterator = victoryConditions.iterator(); iterator.hasNext(); ) { VictoryCondition condition = iterator.next(); if(type.isInstance(condition)) { iterator.remove(); changed = true; } } if(changed) invalidateRanking(); } /** * Return all currently active competitors, ordered by closeness to winning the match. * Competitors that are tied are returned in arbitrary, and inconsistent order. */ public RankedSet<Competitor> rankedCompetitors() { return rankedCompetitors; } public boolean shouldMatchEnd() { return victoryConditions.stream().anyMatch(VictoryCondition::isCompleted); } /** * Return all {@link Competitor}s that are as close as, or closer to winning * the match than any other competitor. Unless the match is empty, the returned * set will always contain at least one competitor. If all competitors are tied * for the lead, the returned set will contain all of them. */ public Set<Competitor> leaders() { return rankedCompetitors.getRank(0); } /** * There are winners if there is only one competitor, or there * are multiple competitors and some are ranked higher than others. */ public boolean hasWinners() { return leaders().size() > 0 && leaders().size() < Math.max(2, rankedCompetitors().size()); } /** * If some competitors finished ahead of others, return the winners, * otherwise return an empty set. */ public Set<Competitor> winners() { return hasWinners() ? leaders() : ImmutableSet.of(); } /** * If some competitors finished behind others, return those that did not win, * otherwise return an empty set. */ public Set<Competitor> losers() { return hasWinners() ? Sets.difference(rankedCompetitors(), leaders()) : ImmutableSet.of(); } }