All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.alloyggp.tournament.spec.TournamentSpec Maven / Gradle / Ivy

There is a newer version: 0.1.0
Show newest version
package net.alloyggp.tournament.spec;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.annotation.concurrent.Immutable;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import net.alloyggp.tournament.api.Game;
import net.alloyggp.tournament.api.MatchResult;
import net.alloyggp.tournament.api.NextMatchesResult;
import net.alloyggp.tournament.api.Player;
import net.alloyggp.tournament.api.PlayerScore;
import net.alloyggp.tournament.api.Ranking;
import net.alloyggp.tournament.api.Score;
import net.alloyggp.tournament.api.Seeding;
import net.alloyggp.tournament.api.Tournament;
import net.alloyggp.tournament.api.TournamentSpecParser;
import net.alloyggp.tournament.impl.MatchResults;
import net.alloyggp.tournament.impl.StandardNextMatchesResult;
import net.alloyggp.tournament.impl.StandardRanking;
import net.alloyggp.tournament.impl.TimeUtils;
import net.alloyggp.tournament.impl.YamlUtils;

@Immutable
public class TournamentSpec implements Tournament {
    private final String tournamentInternalName;
    private final String tournamentDisplayName;
    private final ImmutableList stages;

    private TournamentSpec(String tournamentInternalName, String tournamentDisplayName,
            ImmutableList stages) {
        Preconditions.checkNotNull(tournamentInternalName);
        Preconditions.checkNotNull(tournamentDisplayName);
        Preconditions.checkArgument(!stages.isEmpty());
        Preconditions.checkArgument(tournamentInternalName.matches("[a-zA-Z0-9_]+"),
                "Tournament internal name should consist of alphanumerics and underscores, but was %s", tournamentInternalName);
        this.tournamentInternalName = tournamentInternalName;
        this.tournamentDisplayName = tournamentDisplayName;
        this.stages = stages;
    }

    private static final ImmutableSet ALLOWED_KEYS = ImmutableSet.of(
            "games",
            "nameInternal",
            "nameDisplay",
            "stages"
            );
    /**
     * Parses an already-loaded YAML object containing a tournament specification.
     *
     * 

For general use, see {@link TournamentSpecParser} instead. */ @SuppressWarnings("unchecked") public static TournamentSpec parseYamlRootObject(Object yamlRoot) { Map rootMap = (Map) yamlRoot; YamlUtils.validateKeys(rootMap, "root", ALLOWED_KEYS); Map games = parseGames(rootMap.get("games")); String tournamentInternalName = (String) rootMap.get("nameInternal"); String tournamentDisplayName = (String) rootMap.get("nameDisplay"); List stages = Lists.newArrayList(); int stageNum = 0; for (Object yamlStage : (List) rootMap.get("stages")) { stages.add(StageSpec.parseYaml(yamlStage, stageNum, games)); stageNum++; } return new TournamentSpec(tournamentInternalName, tournamentDisplayName, ImmutableList.copyOf(stages)); } @SuppressWarnings("unchecked") private static Map parseGames(Object gamesYaml) { Preconditions.checkNotNull(gamesYaml, "The YAML file must have a 'games' section."); Map results = Maps.newHashMap(); for (Object gameYaml : (List) gamesYaml) { Map gameMap = (Map) gameYaml; String name = (String) gameMap.get("name"); String repository = (String) gameMap.get("repository"); int numRoles = (int) gameMap.get("numRoles"); boolean fixedSum = (boolean) gameMap.get("fixedSum"); Game game = Game.create(repository, name, numRoles, fixedSum); if (results.containsKey(name)) { throw new IllegalArgumentException("Can't have two games with the same name defined"); } results.put(name, game); } return results; } /* (non-Javadoc) * @see net.alloyggp.swiss.api.Tournament#getTournamentInternalName() */ @Override public String getTournamentInternalName() { return tournamentInternalName; } /* (non-Javadoc) * @see net.alloyggp.swiss.api.Tournament#getTournamentDisplayName() */ @Override public String getTournamentDisplayName() { return tournamentDisplayName; } public ImmutableList getStages() { return stages; } /* (non-Javadoc) * @see net.alloyggp.swiss.api.Tournament#getMatchesToRun(net.alloyggp.swiss.api.Seeding, com.google.common.collect.ImmutableList) */ @Override public NextMatchesResult getMatchesToRun(Seeding initialSeeding, Set resultsSoFar) { Seeding seeding = initialSeeding; for (int stageNum = 0; stageNum < stages.size(); stageNum++) { StageSpec stage = stages.get(stageNum); Set resultsInStage = MatchResults.filterByStage(resultsSoFar, stageNum); NextMatchesResult matchesForStage = stage.getMatchesToRun(tournamentInternalName, initialSeeding, resultsInStage); if (!matchesForStage.getMatchesToRun().isEmpty()) { return matchesForStage; } Ranking standings = stage.getCurrentStandings(tournamentInternalName, seeding, resultsInStage); seeding = stage.getSeedingsFromFinalStandings(standings); } //No stages had matches left; the tournament is over return StandardNextMatchesResult.createEmpty(); } /* (non-Javadoc) * @see net.alloyggp.swiss.api.Tournament#getCurrentStandings(net.alloyggp.swiss.api.Seeding, com.google.common.collect.ImmutableList) */ @Override public Ranking getCurrentStandings(Seeding initialSeeding, Set resultsSoFar) { Seeding seeding = initialSeeding; Ranking standings = null; for (int stageNum = 0; stageNum < stages.size(); stageNum++) { StageSpec stage = stages.get(stageNum); NextMatchesResult matchesForStage = stage.getMatchesToRun(tournamentInternalName, initialSeeding, resultsSoFar); standings = mixInStandings(standings, stage.getCurrentStandings(tournamentInternalName, seeding, resultsSoFar)); if (!matchesForStage.getMatchesToRun().isEmpty()) { return standings; } seeding = stage.getSeedingsFromFinalStandings(standings); } //No stages had matches left; the tournament is over; use the last set of standings Preconditions.checkNotNull(standings); return standings; } private Ranking mixInStandings(Ranking oldStandings, Ranking newStandings) { if (oldStandings == null) { return newStandings; } //preserve old standings for players that didn't make the cut Set allPlayerScores = Sets.newHashSet(); Set playersInNewerStandings = Sets.newHashSet(); for (PlayerScore score : newStandings.getScores()) { allPlayerScores.add(PlayerScore.create(score.getPlayer(), CutoffScore.madeCutoff(score), score.getSeedFromRoundStart())); playersInNewerStandings.add(score.getPlayer()); } for (PlayerScore score : oldStandings.getScores()) { if (!playersInNewerStandings.contains(score.getPlayer())) { allPlayerScores.add(PlayerScore.create(score.getPlayer(), CutoffScore.failedCutoff(score), score.getSeedFromRoundStart())); } } return StandardRanking.create(allPlayerScores); } private static class CutoffScore implements Score { private final boolean madeCutoff; private final Score score; private CutoffScore(boolean madeCutoff, Score score) { this.madeCutoff = madeCutoff; this.score = score; } public static CutoffScore failedCutoff(PlayerScore score) { return new CutoffScore(false, score.getScore()); } public static CutoffScore madeCutoff(PlayerScore score) { return new CutoffScore(true, score.getScore()); } @Override public int compareTo(Score other) { if (!(other instanceof CutoffScore)) { throw new IllegalArgumentException(); } CutoffScore otherCutoff = (CutoffScore) other; if (!madeCutoff && otherCutoff.madeCutoff) { return -1; } else if (madeCutoff && !otherCutoff.madeCutoff) { return 1; } else { return score.compareTo(otherCutoff.score); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (madeCutoff ? 1231 : 1237); result = prime * result + ((score == null) ? 0 : score.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } CutoffScore other = (CutoffScore) obj; if (madeCutoff != other.madeCutoff) { return false; } if (score == null) { if (other.score != null) { return false; } } else if (!score.equals(other.score)) { return false; } return true; } @Override public String toString() { if (!madeCutoff) { return "eliminated"; } else { return score.toString(); } } } @Override public List getStandingsHistory(Seeding initialSeeding, Set resultsSoFar) { List result = Lists.newArrayList(); result.add(StandardRanking.createForSeeding(initialSeeding)); Seeding seeding = initialSeeding; for (int stageNum = 0; stageNum < stages.size(); stageNum++) { StageSpec stage = stages.get(stageNum); NextMatchesResult matchesForStage = stage.getMatchesToRun(tournamentInternalName, initialSeeding, resultsSoFar); result.addAll(stage.getStandingsHistory(tournamentInternalName, initialSeeding, resultsSoFar)); if (!matchesForStage.getMatchesToRun().isEmpty()) { return result; } Ranking standings = stage.getCurrentStandings(tournamentInternalName, seeding, resultsSoFar); seeding = stage.getSeedingsFromFinalStandings(standings); } return result; } @Override public Optional getInitialStartTime() { return stages.get(0).getRounds().get(0).getStartTime(); } @Override public long getSecondsToWaitUntilInitialStartTime() { return TimeUtils.getSecondsToWaitUntilStartTime(getInitialStartTime()); } }