net.alloyggp.tournament.internal.SwissFormat1Runner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ggp-tournament Show documentation
Show all versions of ggp-tournament Show documentation
A library for GGP tournament specification and scheduling.
package net.alloyggp.tournament.internal;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import org.joda.time.DateTime;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import net.alloyggp.tournament.api.TGame;
import net.alloyggp.tournament.api.TMatchResult;
import net.alloyggp.tournament.api.TMatchResult.Outcome;
import net.alloyggp.tournament.api.TMatchSetup;
import net.alloyggp.tournament.api.TNextMatchesResult;
import net.alloyggp.tournament.api.TPlayer;
import net.alloyggp.tournament.api.TPlayerScore;
import net.alloyggp.tournament.api.TRanking;
import net.alloyggp.tournament.api.TScore;
import net.alloyggp.tournament.api.TSeeding;
import net.alloyggp.tournament.internal.spec.MatchSpec;
import net.alloyggp.tournament.internal.spec.RoundSpec;
public class SwissFormat1Runner implements FormatRunner {
private static final SwissFormat1Runner INSTANCE = new SwissFormat1Runner();
private SwissFormat1Runner() {
//Not instantiable
}
public static SwissFormat1Runner create() {
return INSTANCE;
}
//TODO: Factor out the common elements between this and SingleEliminationFormatSimulator
@NotThreadSafe
private static class SwissFormatSimulator {
private final String tournamentInternalName;
private final int stageNum;
private final TSeeding initialSeeding;
private final ImmutableList rounds;
private final ImmutableSet resultsFromEarlierStages;
private final ImmutableSet resultsInStage;
private final Set matchesToRun = Sets.newHashSet();
//TODO: Double-check that all these stats are updated appropriately
private TGame mostRecentGame = null; //of a fully completed round
private final Map totalPointsScored = Maps.newHashMap();
private final Map> pointsScoredByGame = Maps.newHashMap();
private final Map pointsFromByes = Maps.newHashMap();
private final Multiset> totalMatchupsSoFar = HashMultiset.create();
private final Map>> matchupsSoFarByGame = Maps.newHashMap();
private final Map>> nonFixedSumMatchupsSoFarByNumPlayers = Maps.newHashMap();
// private final Map>> nonFixedSumRoleAssignmentsSoFarByNumPlayers = Maps.newHashMap();
private final List standingsHistory = Lists.newArrayList();
private @Nullable DateTime latestStartTimeSeen = null;
private SwissFormatSimulator(String tournamentInternalName, int stageNum, TSeeding initialSeeding,
ImmutableList rounds, ImmutableSet resultsFromEarlierStages,
ImmutableSet resultsInStage) {
this.tournamentInternalName = tournamentInternalName;
this.stageNum = stageNum;
this.initialSeeding = initialSeeding;
this.rounds = rounds;
this.resultsFromEarlierStages = resultsFromEarlierStages;
this.resultsInStage = resultsInStage;
}
public static SwissFormatSimulator createAndRun(String tournamentInternalName, int stageNum, TSeeding initialSeeding,
ImmutableList rounds, Set allResultsSoFar) {
Set resultsFromEarlierStages = MatchResults.getResultsPriorToStage(allResultsSoFar, stageNum);
Set resultsInStage = MatchResults.filterByStage(allResultsSoFar, stageNum);
SwissFormatSimulator simulator = new SwissFormatSimulator(tournamentInternalName, stageNum, initialSeeding,
rounds, ImmutableSet.copyOf(resultsFromEarlierStages), ImmutableSet.copyOf(resultsInStage));
simulator.run();
return simulator;
}
private void run() {
setInitialTotalsToZero();
int roundNum = 0;
SetMultimap matchesByRound = MatchResults.mapByRound(resultsInStage, stageNum);
@Nullable EndOfRoundState endOfRoundState = TournamentStateCache.getLatestCachedEndOfRoundState(tournamentInternalName, initialSeeding, resultsFromEarlierStages, stageNum, resultsInStage);
if (endOfRoundState != null) {
Swiss1EndOfRoundState state = (Swiss1EndOfRoundState) endOfRoundState;
roundNum = state.roundNum + 1;
loadCachedState(state);
}
for (/* roundNum already set */; roundNum < rounds.size(); roundNum++) {
RoundSpec round = rounds.get(roundNum);
Set roundResults = matchesByRound.get(roundNum);
runRound(round, roundNum, roundResults);
if (!matchesToRun.isEmpty()) {
//We're still finishing up this round, not ready to assign matches in the next one
return;
}
//If we didn't run the round due to the number of players being too low,
//skip the standings and caching
if (!roundResults.isEmpty()) {
standingsHistory.add(getStandings());
Swiss1EndOfRoundState state = Swiss1EndOfRoundState.create(roundNum,
mostRecentGame, totalPointsScored, pointsScoredByGame,
pointsFromByes, totalMatchupsSoFar, matchupsSoFarByGame,
nonFixedSumMatchupsSoFarByNumPlayers, standingsHistory,
latestStartTimeSeen);
TournamentStateCache.cacheEndOfRoundState(tournamentInternalName, initialSeeding, resultsFromEarlierStages, stageNum, resultsInStage, state);
}
}
}
private void loadCachedState(Swiss1EndOfRoundState state) {
totalPointsScored.putAll(state.totalPointsScored);
pointsFromByes.putAll(state.pointsFromByes);
Set possiblePlayerCounts = Sets.newHashSet();
for (TGame game : RoundSpec.getAllGames(rounds)) {
Map pointsScoredForGame = pointsScoredByGame.get(game);
for (TPlayer player : initialSeeding.getPlayersBestFirst()) {
pointsScoredForGame.put(player, state.pointsScoredByGame.get(game).get(player));
}
for (Entry> entry : state.matchupsSoFarByGame.get(game).entrySet()) {
matchupsSoFarByGame.get(game).add(Sets.newHashSet(entry.getElement()), entry.getCount());
}
possiblePlayerCounts.add(game.getNumRoles());
}
for (int playerCount : possiblePlayerCounts) {
for (Entry> entry : state.nonFixedSumMatchupsSoFarByNumPlayers.get(playerCount).entrySet()) {
nonFixedSumMatchupsSoFarByNumPlayers.get(playerCount).add(Sets.newHashSet(entry.getElement()), entry.getCount());
}
}
mostRecentGame = state.mostRecentGame;
standingsHistory.addAll(state.standingsHistory);
for (Entry> entry : state.totalMatchupsSoFar.entrySet()) {
totalMatchupsSoFar.add(Sets.newHashSet(entry.getElement()), entry.getCount());
}
latestStartTimeSeen = state.latestStartTimeSeen;
}
private void runRound(RoundSpec round, int roundNum, Set roundResults) {
handleStartTimeForRound(round);
//...there should be only one match per round, I think?
//Or at least they must involve the same game?
TGame game = getOnlyGame(round);
//Figure out how to assign players
List> playerGroups = getPlayerGroups(game);
double maxScoreAchieved = 0;
double scoreSum = 0;
int scoreCount = 0;
for (int groupNum = 0; groupNum < playerGroups.size(); groupNum++) {
List players = playerGroups.get(groupNum);
for (int matchNum = 0; matchNum < round.getMatches().size(); matchNum++) {
MatchSpec match = round.getMatches().get(matchNum);
Optional attemptNum = getAttemptNumberIfUnfinished(groupNum, matchNum, roundResults);
if (attemptNum.isPresent()) {
String matchId = MatchIds.create(tournamentInternalName, stageNum,
roundNum, groupNum, matchNum, attemptNum.get());
matchesToRun.add(match.createMatchSetup(matchId, players));
break;
} else {
TMatchResult result = getSuccessfulAttempt(groupNum, matchNum, roundResults);
//Add the results of the match to our point totals
List playersInRoleOrder = match.putInOrder(players);
for (int role = 0; role < players.size(); role++) {
TPlayer player = playersInRoleOrder.get(role);
double goalValue = result.getGoals().get(role) * match.getWeight();
//TODO: Add to stats here, including stats yet to be introduced
//such as player-player meetings
addToSumWithKey(player, goalValue, totalPointsScored);
addToSumWithKey(player, goalValue, pointsScoredByGame.get(game));
maxScoreAchieved = maxScoreAchieved > goalValue ? maxScoreAchieved : goalValue;
scoreSum += goalValue;
scoreCount++;
this.mostRecentGame = game;
}
}
}
}
if (matchesToRun.isEmpty()) {
//If we're at the end of a round and all the groups have gone and
//we have a player left, manage the byes
Set unassignedPlayers = getUnassignedPlayers(initialSeeding.getPlayersBestFirst(), playerGroups);
if (!unassignedPlayers.isEmpty()) {
//Calculate bye score for the game
double byeScore = getByeScoreForRound(game, maxScoreAchieved, scoreSum, scoreCount);
Preconditions.checkState(byeScore >= 0 && byeScore <= 100);
for (TPlayer player : unassignedPlayers) {
addToSumWithKey(player, byeScore, totalPointsScored);
addToSumWithKey(player, byeScore, pointsScoredByGame.get(game));
addToSumWithKey(player, byeScore, pointsFromByes);
}
}
//Also...
updateMatchupStats(game, playerGroups);
}
}
private void handleStartTimeForRound(RoundSpec round) {
if (round.getStartTime().isPresent()) {
DateTime roundStartTime = round.getStartTime().get();
if (latestStartTimeSeen == null
|| latestStartTimeSeen.isBefore(roundStartTime)) {
latestStartTimeSeen = roundStartTime;
}
}
}
private void updateMatchupStats(TGame game, List> playerGroups) {
for (List players : playerGroups) {
if (game.isFixedSum()) {
if (game.getNumRoles() == 2) {
matchupsSoFarByGame.get(game).add(ImmutableSet.of(players.get(0), players.get(1)));
totalMatchupsSoFar.add(ImmutableSet.of(players.get(0), players.get(1)));
} else {
for (int p1 = 0; p1 < players.size(); p1++) {
for (int p2 = p1 + 1; p2 < players.size(); p2++) {
matchupsSoFarByGame.get(game)
.add(ImmutableSet.of(players.get(p1), players.get(p2)));
totalMatchupsSoFar.add(ImmutableSet.of(players.get(p1), players.get(p2)));
}
}
}
} else {
//TODO: Fix this
for (int p1 = 0; p1 < players.size(); p1++) {
for (int p2 = p1 + 1; p2 < players.size(); p2++) {
nonFixedSumMatchupsSoFarByNumPlayers.get(game.getNumRoles())
.add(ImmutableSet.of(players.get(p1), players.get(p2)));
}
}
// nonFixedSumRoleAssignmentsSoFarByNumPlayers
}
}
}
private static double getByeScoreForRound(TGame game, double maxScoreAchieved, double scoreSum, int scoreCount) {
if (game.isFixedSum()) {
if (game.getNumRoles() == 2) {
return maxScoreAchieved;
} else {
return maxScoreAchieved;
}
} else {
if (scoreCount > 0) {
return scoreSum / scoreCount;
} else {
//Not enough players for the round
return 0.0;
}
}
}
private List> getPlayerGroups(TGame game) {
int numRoles = game.getNumRoles();
if (numRoles == 1) {
ImmutableList.Builder> builder = ImmutableList.builder();
for (TPlayer player : initialSeeding.getPlayersBestFirst()) {
builder.add(ImmutableList.of(player));
}
return builder.build();
}
if (game.isFixedSum()) {
if (numRoles == 2) {
return getTwoPlayerFixedSumPlayerGroups(game);
} else {
return getManyPlayerFixedSumPlayerGroups(game);
}
} else {
//Use quasi-random pairings
return getNonFixedSumPlayerGroups(game.getNumRoles());
}
}
//TODO: Consider prioritizing certain players for byes? e.g. players
//doing worse in the tournament, those with fewer byes already?
private List> getNonFixedSumPlayerGroups(int numRoles) {
//Two-player: there's an elegant round-robin algorithm we
//could use here, "rotate" all but one player in two rows
//Do something naive for now
final Multiset> matchupsSoFar = nonFixedSumMatchupsSoFarByNumPlayers.get(numRoles);
Preconditions.checkNotNull(matchupsSoFar);
// Map> roleAssignmentsSoFar = nonFixedSumRoleAssignmentsSoFarByNumPlayers.get(numRoles);
List playersToAssign = Lists.newArrayList(initialSeeding.getPlayersBestFirst());
List> results = Lists.newArrayList();
while (playersToAssign.size() >= numRoles) {
TPlayer player = playersToAssign.get(0);
//Grab the first available players with the fewest previous matchups against us and each other
final List playersInGroup = Lists.newArrayList(player);
playersToAssign.remove(player);
while (playersInGroup.size() < numRoles) {
Ordering playerOrder = Ordering.from(new Comparator() {
@Override
public int compare(TPlayer p1, TPlayer p2) {
int sum1 = getSum(p1);
int sum2 = getSum(p2);
return Integer.compare(sum1, sum2);
}
//Sum of matchups against players already in group
private int getSum(TPlayer p) {
int sum = 0;
for (TPlayer playerInGroup : playersInGroup) {
sum += matchupsSoFar.count(ImmutableSet.of(p, playerInGroup));
}
return sum;
}
})
.compound(new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
int seed1 = initialSeeding.getPlayersBestFirst().indexOf(o1);
int seed2 = initialSeeding.getPlayersBestFirst().indexOf(o2);
return Integer.compare(seed1, seed2);
}
});
TPlayer playerToAdd = playerOrder.min(playersToAssign);
playersInGroup.add(playerToAdd);
playersToAssign.remove(playerToAdd);
}
//TODO: Shuffle the roles intelligently, somehow
//Should role shuffling be per-game? Across the tournament?
results.add(playersInGroup);
}
return results;
}
private List> getManyPlayerFixedSumPlayerGroups(TGame game) {
List> groups = Lists.newArrayList();
Set assignedSoFar = Sets.newHashSet();
List overallPlayerRankings = getPlayerRankingsForGame(game);
int numPlayers = initialSeeding.getPlayersBestFirst().size();
while (numPlayers - assignedSoFar.size() >= game.getNumRoles()) {
List curGroup = Lists.newArrayList();
//First, get the best player left according to the rankings so far
TPlayer firstPlayer = getFirstUnassignedPlayer(overallPlayerRankings, assignedSoFar);
curGroup.add(firstPlayer);
assignedSoFar.add(firstPlayer);
while (curGroup.size() < game.getNumRoles()) {
//Now we look for the best opponent for those players
List opponentRankings = getOpponentRankingsForPlayers(curGroup, game);
TPlayer opponent = getFirstUnassignedPlayer(opponentRankings, assignedSoFar);
curGroup.add(opponent);
assignedSoFar.add(opponent);
}
groups.add(ImmutableList.copyOf(curGroup));
}
return groups;
}
//TODO: Avoid awarding another bye to a player; when only one player in
//assignedSoFar has NOT had a bye, (and the total number of players is
//odd,) remove that player and reserve them for a bye
private List> getTwoPlayerFixedSumPlayerGroups(TGame game) {
List> groups = Lists.newArrayList();
Set assignedSoFar = Sets.newHashSet();
List overallPlayerRankings = getPlayerRankingsForGame(game);
int numPlayers = initialSeeding.getPlayersBestFirst().size();
while (numPlayers - assignedSoFar.size() >= game.getNumRoles()) {
//First, get the best player left according to the rankings so far
TPlayer firstPlayer = getFirstUnassignedPlayer(overallPlayerRankings, assignedSoFar);
assignedSoFar.add(firstPlayer);
//Now we look for the best opponent for that player
List opponentRankings = getOpponentRankingsForPlayer(firstPlayer, game);
TPlayer opponent = getFirstUnassignedPlayer(opponentRankings, assignedSoFar);
assignedSoFar.add(opponent);
//Best seed goes first
groups.add(ImmutableList.of(firstPlayer, opponent));
}
return groups;
}
private List getOpponentRankingsForPlayer(TPlayer firstPlayer, TGame game) {
return getOpponentRankingsForPlayers(ImmutableList.of(firstPlayer), game);
}
private List getOpponentRankingsForPlayers(
final List curGroup, final TGame game) {
List allOpponents = Lists.newArrayList(initialSeeding.getPlayersBestFirst());
allOpponents.removeAll(curGroup);
Collections.sort(allOpponents, Ordering.from(
new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
double score1 = compute(o1);
double score2 = compute(o2);
return Double.compare(score1, score2);
}
//Higher points scored by game better
//but discount 100/n points per matchup already played in this game
private double compute(TPlayer opponent) {
double pointsScored = pointsScoredByGame.get(game).get(opponent);
for (TPlayer player : curGroup) {
pointsScored -= (100.0 / curGroup.size())
* matchupsSoFarByGame.get(game).count(ImmutableSet.of(player, opponent));
}
return pointsScored;
}
}
).compound(
new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
double score1 = compute(o1);
double score2 = compute(o2);
return Double.compare(score1, score2);
}
private double compute(TPlayer opponent) {
//Higher total points scored better
//but discount 100/n points per matchup already played in any Swiss rounds
double pointsScored = totalPointsScored.get(opponent);
for (TPlayer player : curGroup) {
pointsScored -= (100.0 / curGroup.size())
* totalMatchupsSoFar.count(ImmutableSet.of(player, opponent));
}
return pointsScored;
}
}
).reverse()
.compound(new Comparator() {
@Override
public int compare(TPlayer p1, TPlayer p2) {
int seed1 = initialSeeding.getPlayersBestFirst().indexOf(p1);
int seed2 = initialSeeding.getPlayersBestFirst().indexOf(p2);
return Integer.compare(seed1, seed2);
}
}));
return allOpponents;
}
private List getPlayerRankingsForGame(final TGame game) {
List players = Lists.newArrayList(initialSeeding.getPlayersBestFirst());
//Sort according to an appropriate comparator
//We do want the best players at the beginning of the list...
//We may accomplish that through an appropriate reversal
//First
Collections.sort(players,
Ordering.from(new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
Double score1 = pointsScoredByGame.get(game).get(o1);
Double score2 = pointsScoredByGame.get(game).get(o2);
return Double.compare(score1, score2);
}
}).compound(new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
Double score1 = totalPointsScored.get(o1);
Double score2 = totalPointsScored.get(o2);
return Double.compare(score1, score2);
}
}).reverse()
.compound(new Comparator() {
@Override
public int compare(TPlayer o1, TPlayer o2) {
int score1 = initialSeeding.getPlayersBestFirst().indexOf(o1);
int score2 = initialSeeding.getPlayersBestFirst().indexOf(o2);
return Integer.compare(score1, score2);
}
}));
return players;
}
private TPlayer getFirstUnassignedPlayer(List players, Set assignedSoFar) {
for (TPlayer player : players) {
if (!assignedSoFar.contains(player)) {
return player;
}
}
throw new IllegalArgumentException("No unassigned players left");
}
private void addToSumWithKey(K key, double addend, Map map) {
map.put(key, addend + map.get(key));
}
private Optional getAttemptNumberIfUnfinished(int groupNum, int matchNum,
Set roundResults) {
int attemptsSoFar = 0;
for (TMatchResult result : roundResults) {
String matchId = result.getMatchId();
if (groupNum == MatchIds.parsePlayerMatchingNumber(matchId)
&& matchNum == MatchIds.parseMatchNumber(matchId)) {
if (result.getOutcome() == Outcome.ABORTED) {
attemptsSoFar++;
} else {
return Optional.absent();
}
}
}
return Optional.of(attemptsSoFar);
}
private TMatchResult getSuccessfulAttempt(int groupNum, int matchNum, Set roundResults) {
for (TMatchResult result : roundResults) {
if (result.getOutcome() == Outcome.COMPLETED) {
String matchId = result.getMatchId();
if (groupNum == MatchIds.parsePlayerMatchingNumber(matchId)
&& matchNum == MatchIds.parseMatchNumber(matchId)) {
return result;
}
}
}
throw new IllegalArgumentException("No successful attempts found");
}
private void setInitialTotalsToZero() {
for (TPlayer player : initialSeeding.getPlayersBestFirst()) {
totalPointsScored.put(player, 0.0);
pointsFromByes.put(player, 0.0);
}
Set possiblePlayerCounts = Sets.newHashSet();
for (Game game : RoundSpec.getAllGames(rounds)) {
HashMap pointsScoredForGame = Maps.newHashMap();
pointsScoredByGame.put(game, pointsScoredForGame);
for (TPlayer player : initialSeeding.getPlayersBestFirst()) {
pointsScoredForGame.put(player, 0.0);
}
matchupsSoFarByGame.put(game, HashMultiset.>create());
possiblePlayerCounts.add(game.getNumRoles());
}
for (int playerCount : possiblePlayerCounts) {
nonFixedSumMatchupsSoFarByNumPlayers.put(playerCount, HashMultiset.>create());
// Map> nonFixedSumRoleAssignmentsSoFar = Maps.newHashMap();
// for (Player player : initialSeeding.getPlayersBestFirst()) {
// nonFixedSumRoleAssignmentsSoFar.put(player, HashMultiset.create());
// }
// nonFixedSumRoleAssignmentsSoFarByNumPlayers.put(playerCount, nonFixedSumRoleAssignmentsSoFar);
}
}
public TNextMatchesResult getMatchesToRun() {
return StandardNextMatchesResult.create(ImmutableSet.copyOf(matchesToRun),
latestStartTimeSeen);
}
private TRanking getStandings() {
Set scores = Sets.newHashSet();
ImmutableList playersBestFirst = initialSeeding.getPlayersBestFirst();
for (int i = 0; i < playersBestFirst.size(); i++) {
TPlayer player = playersBestFirst.get(i);
double mostRecentGamePoints = 0;
String mostRecentGameName = null;
if (mostRecentGame != null) {
mostRecentGamePoints = pointsScoredByGame.get(mostRecentGame).get(player);
mostRecentGameName = mostRecentGame.getId();
}
TScore score = new SwissScore(totalPointsScored.get(player),
mostRecentGameName, mostRecentGamePoints,
pointsFromByes.get(player));
scores.add(TPlayerScore.create(player, score, i));
}
return StandardRanking.create(scores);
}
public List getStandingsHistory() {
return ImmutableList.copyOf(standingsHistory);
}
}
private static class SwissScore implements TScore {
//T1K stands for "times 1000".
private final long pointsSoFarT1K;
//Just for display purposes; this makes it more obvious why matchups are selected
private final @Nullable String mostRecentGameName;
private final long pointsInMostRecentGameT1K;
private final long pointsFromByesT1K;
public SwissScore(double pointsSoFar, @Nullable String mostRecentGameName,
double pointsInMostRecentGame, double pointsFromByes) {
this.pointsSoFarT1K = roundToThreePlacesT1K(pointsSoFar);
this.mostRecentGameName = mostRecentGameName;
this.pointsInMostRecentGameT1K = roundToThreePlacesT1K(pointsInMostRecentGame);
this.pointsFromByesT1K = roundToThreePlacesT1K(pointsFromByes);
}
private static long roundToThreePlacesT1K(double value) {
return Math.round(value * 1000.0);
}
@Override
public int compareTo(TScore other) {
if (!(other instanceof SwissScore)) {
throw new IllegalArgumentException();
}
return Long.compare(pointsSoFarT1K, ((SwissScore)other).pointsSoFarT1K);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (pointsSoFarT1K ^ (pointsSoFarT1K >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SwissScore other = (SwissScore) obj;
if (pointsSoFarT1K != other.pointsSoFarT1K) {
return false;
}
return true;
}
@Override
public String toString() {
return getDescription();
}
@Override
public String getDescription() {
String string;
if (mostRecentGameName == null) {
string = addDecimalPoint(pointsSoFarT1K) + " total";
} else {
string = addDecimalPoint(pointsSoFarT1K) + " total, " + addDecimalPoint(pointsInMostRecentGameT1K) + " in " + mostRecentGameName;
}
if (pointsFromByesT1K > 0L) {
string += ", " + addDecimalPoint(pointsFromByesT1K) + " from byes";
}
return string;
}
private String addDecimalPoint(long value) {
return String.format("%d.%03d", value / 1000, value % 1000);
}
}
@Override
public TNextMatchesResult getMatchesToRun(String tournamentInternalName, TSeeding initialSeeding, int stageNum,
List rounds, Set allResultsSoFar) {
return SwissFormatSimulator.createAndRun(tournamentInternalName, stageNum, initialSeeding,
ImmutableList.copyOf(rounds), allResultsSoFar).getMatchesToRun();
}
public static Set getUnassignedPlayers(Collection allPlayers, List> playerGroups) {
Set results = Sets.newHashSet(allPlayers);
for (List group : playerGroups) {
for (TPlayer player : group) {
results.remove(player);
}
}
return results;
}
@Override
public List getStandingsHistory(String tournamentInternalName, TSeeding initialSeeding, int stageNum,
List rounds, Set allResultsSoFar) {
return SwissFormatSimulator.createAndRun(tournamentInternalName, stageNum, initialSeeding,
ImmutableList.copyOf(rounds), allResultsSoFar).getStandingsHistory();
}
private static TGame getOnlyGame(RoundSpec round) {
if (round.getMatches().isEmpty()) {
throw new IllegalArgumentException("Swiss rounds must have at least one match");
}
TGame game = round.getMatches().get(0).getGame();
for (MatchSpec match : round.getMatches()) {
if (!game.equals(match.getGame())) {
throw new IllegalArgumentException("Swiss rounds in Swiss variant 1 must use "
+ "the same game in each match, and frequently have one match per round");
}
}
return game;
}
@Override
public void validateRounds(ImmutableList rounds) {
//TODO: Implement
for (RoundSpec round : rounds) {
//Validates all matches in the round are the same game
getOnlyGame(round);
}
}
@Immutable
private static class Swiss1EndOfRoundState implements EndOfRoundState {
private final int roundNum;
private final TGame mostRecentGame;
private final ImmutableMap totalPointsScored;
private final ImmutableMap> pointsScoredByGame;
private final ImmutableMap pointsFromByes;
private final ImmutableMultiset> totalMatchupsSoFar;
private final ImmutableMap>> matchupsSoFarByGame;
private final ImmutableMap>> nonFixedSumMatchupsSoFarByNumPlayers;
private final ImmutableList standingsHistory;
private final @Nullable DateTime latestStartTimeSeen;
private Swiss1EndOfRoundState(int roundNum, TGame mostRecentGame, ImmutableMap totalPointsScored,
ImmutableMap> pointsScoredByGame,
ImmutableMap pointsFromByes, ImmutableMultiset> totalMatchupsSoFar,
ImmutableMap>> matchupsSoFarByGame,
ImmutableMap>> nonFixedSumMatchupsSoFarByNumPlayers,
ImmutableList standingsHistory, @Nullable DateTime latestStartTimeSeen) {
this.roundNum = roundNum;
this.mostRecentGame = mostRecentGame;
this.totalPointsScored = totalPointsScored;
this.pointsScoredByGame = pointsScoredByGame;
this.pointsFromByes = pointsFromByes;
this.totalMatchupsSoFar = totalMatchupsSoFar;
this.matchupsSoFarByGame = matchupsSoFarByGame;
this.nonFixedSumMatchupsSoFarByNumPlayers = nonFixedSumMatchupsSoFarByNumPlayers;
this.standingsHistory = standingsHistory;
this.latestStartTimeSeen = latestStartTimeSeen;
}
public static Swiss1EndOfRoundState create(int roundNum,
TGame mostRecentGame,
Map totalPointsScored,
Map> pointsScoredByGame,
Map pointsFromByes,
Multiset> totalMatchupsSoFar,
Map>> matchupsSoFarByGame,
Map>> nonFixedSumMatchupsSoFarByNumPlayers,
List standingsHistory,
@Nullable DateTime latestStartTimeSeen) {
return new Swiss1EndOfRoundState(roundNum,
mostRecentGame,
ImmutableMap.copyOf(totalPointsScored),
toImmutableMapValuedMap(pointsScoredByGame),
ImmutableMap.copyOf(pointsFromByes),
toImmutableSetEntriedMultiset(totalMatchupsSoFar),
toImmutableMultisetOfSetsValuedMap(matchupsSoFarByGame),
toImmutableMultisetOfSetsValuedMap(nonFixedSumMatchupsSoFarByNumPlayers),
ImmutableList.copyOf(standingsHistory),
latestStartTimeSeen);
}
private static ImmutableMap>> toImmutableMultisetOfSetsValuedMap(
Map>> map) {
return ImmutableMap.copyOf(Maps.transformValues(map, new Function>, ImmutableMultiset>>() {
@Override
public ImmutableMultiset> apply(@Nonnull Multiset> input) {
return toImmutableSetEntriedMultiset(input);
}
}));
}
private static ImmutableMultiset> toImmutableSetEntriedMultiset(
Multiset> multiset) {
ImmutableMultiset.Builder> builder = ImmutableMultiset.builder();
for (Entry> entry : multiset.entrySet()) {
builder.addCopies(ImmutableSet.copyOf(entry.getElement()), entry.getCount());
}
return builder.build();
}
private static ImmutableMap> toImmutableMapValuedMap(
Map> map) {
return ImmutableMap.copyOf(Maps.transformValues(map, new Function