diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/tournament/SwissPairingMinimalWeightMatchingTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/tournament/SwissPairingMinimalWeightMatchingTest.java new file mode 100644 index 00000000000..9429f71f910 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/tournament/SwissPairingMinimalWeightMatchingTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.serverside.tournament; + + +import java.util.*; + +import mage.game.tournament.*; +import mage.game.tournament.pairing.RoundPairings; +import mage.game.tournament.pairing.SwissPairingMinimalWeightMatching; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.stub.PlayerStub; +import org.mage.test.stub.TournamentStub; + + +/** + * + * @author Quercitron + */ +public class SwissPairingMinimalWeightMatchingTest { + + @Test + public void FourPlayersSecondRoundTest() { + // 1 > 3 + // 2 > 4 + + TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); + List players = new ArrayList<>(); + players.add(player4); + players.add(player2); + players.add(player3); + players.add(player1); + + player1.setPoints(3); + player2.setPoints(3); + player3.setPoints(0); + player4.setPoints(0); + + List rounds = new ArrayList<>(); + Round round = new Round(1, new TournamentStub()); + TournamentPairing pair1 = new TournamentPairing(player1, player3); + round.addPairing(pair1); + TournamentPairing pair2 = new TournamentPairing(player4, player2); + round.addPairing(pair2); + rounds.add(round); + + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(players, rounds); + RoundPairings roundPairings = swissPairing.getRoundPairings(); + + Assert.assertEquals(2, roundPairings.getPairings().size()); + Assert.assertEquals(0, roundPairings.getPlayerByes().size()); + + CheckPair(roundPairings.getPairings(), player1, player2); + CheckPair(roundPairings.getPairings(), player3, player4); + } + + @Test + public void FourPlayersSecondThirdTest() { + // 1 > 3 + // 2 > 4 + // + // 1 > 2 + // 3 > 4 + + TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); + List players = new ArrayList<>(); + players.add(player4); + players.add(player2); + players.add(player3); + players.add(player1); + + player1.setPoints(6); + player2.setPoints(3); + player3.setPoints(3); + player4.setPoints(0); + + List rounds = new ArrayList<>(); + // round 1 + Round round = new Round(1, new TournamentStub()); + TournamentPairing pair1 = new TournamentPairing(player1, player3); + round.addPairing(pair1); + TournamentPairing pair2 = new TournamentPairing(player4, player2); + round.addPairing(pair2); + rounds.add(round); + // round 2 + round = new Round(2, new TournamentStub()); + pair1 = new TournamentPairing(player2, player1); + round.addPairing(pair1); + pair2 = new TournamentPairing(player4, player3); + round.addPairing(pair2); + rounds.add(round); + + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(players, rounds); + RoundPairings roundPairings = swissPairing.getRoundPairings(); + + Assert.assertEquals(2, roundPairings.getPairings().size()); + Assert.assertEquals(0, roundPairings.getPlayerByes().size()); + + CheckPair(roundPairings.getPairings(), player1, player4); + CheckPair(roundPairings.getPairings(), player2, player3); + } + + @Test + public void FivePlayersThirdRoundTest() { + // 1 > 2 + // 3 > 4 + // 5 + // + // 1 > 5 + // 2 > 3 + // 4 + + TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); + TournamentPlayer player5 = new TournamentPlayer(new PlayerStub(), null); + List players = new ArrayList<>(); + players.add(player4); + players.add(player2); + players.add(player5); + players.add(player3); + players.add(player1); + + player1.setPoints(6); + player2.setPoints(3); + player3.setPoints(3); + player4.setPoints(3); + player5.setPoints(3); + + List rounds = new ArrayList<>(); + // first round + Round round = new Round(1, new TournamentStub()); + TournamentPairing pair1 = new TournamentPairing(player1, player2); + round.addPairing(pair1); + TournamentPairing pair2 = new TournamentPairing(player3, player4); + round.addPairing(pair2); + round.getPlayerByes().add(player5); + rounds.add(round); + // second round + round = new Round(1, new TournamentStub()); + pair1 = new TournamentPairing(player1, player5); + round.addPairing(pair1); + pair2 = new TournamentPairing(player2, player3); + round.addPairing(pair2); + round.getPlayerByes().add(player4); + rounds.add(round); + + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(players, rounds); + RoundPairings roundPairings = swissPairing.getRoundPairings(); + + Assert.assertEquals(2, roundPairings.getPairings().size()); + Assert.assertEquals(1, roundPairings.getPlayerByes().size()); + + CheckPair(roundPairings.getPairings(), player1, player4); + CheckPair(roundPairings.getPairings(), player2, player5); + Assert.assertTrue(roundPairings.getPlayerByes().contains(player3)); + } + + @Test + public void SimulateDifferentTournaments() { + int playersCount = 12; + for (int i = 0; i <= playersCount; i++) { + int roundsCount = ((i + 1) / 2) * 2 - 1; + for (int j = 1; j <= roundsCount; j++) { + SimulateTournament(i, j); + } + } + } + + private void SimulateTournament(int playersCount, int roundsCount) { + Random rnd = new Random(); + + List players = new ArrayList<>(); + for (int i = 0; i < playersCount; i++) { + players.add(new TournamentPlayer(new PlayerStub(), null)); + } + + List playedPairs = new ArrayList<>(); + Set playersByes = new HashSet<>(); + + List rounds = new ArrayList<>(); + for (int i = 0; i < roundsCount; i++) { + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(new ArrayList<>(players), rounds); + RoundPairings roundPairings = swissPairing.getRoundPairings(); + + Assert.assertEquals(playersCount / 2, roundPairings.getPairings().size()); + Assert.assertEquals(playersCount % 2, roundPairings.getPlayerByes().size()); + + Round round = new Round(1, new TournamentStub()); + rounds.add(round); + for (TournamentPairing pairing : roundPairings.getPairings()) { + if (ContainsPair(playedPairs, pairing.getPlayer1(), pairing.getPlayer2())) { + if (i < (playersCount + 1) / 2) { + throw new AssertionError("Match between players has been played already."); + } + } + playedPairs.add(pairing); + + round.addPairing(pairing); + if (rnd.nextBoolean()) { + pairing.getPlayer1().setPoints(pairing.getPlayer1().getPoints() + 3); + } else { + pairing.getPlayer2().setPoints(pairing.getPlayer2().getPoints() + 3); + } + } + for (TournamentPlayer playerBye : roundPairings.getPlayerByes()) { + if (playersByes.contains(playerBye)) { + throw new AssertionError("Player already had bye."); + } + playersByes.add(playerBye); + + round.getPlayerByes().add(playerBye); + playerBye.setPoints(playerBye.getPoints() + 3); + } + } + } + + private void CheckPair(List pairs, TournamentPlayer player1, TournamentPlayer player2) { + if (!ContainsPair(pairs, player1, player2)) { + throw new AssertionError("Pairing doesn't contain expected pair of players."); + } + } + + private boolean ContainsPair(List pairs, TournamentPlayer player1, TournamentPlayer player2) { + for (TournamentPairing pair : pairs) { + if (pair.getPlayer1().equals(player1) && pair.getPlayer2().equals(player2)) { + return true; + } + if (pair.getPlayer1().equals(player2) && pair.getPlayer2().equals(player1)) { + return true; + } + } + return false; + } +} + + diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java new file mode 100644 index 00000000000..0ae6463b2af --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -0,0 +1,1222 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.stub; + +import mage.MageObject; +import mage.abilities.*; +import mage.abilities.costs.AlternativeSourceCosts; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.VariableCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.mana.ManaOptions; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.decks.Deck; +import mage.choices.Choice; +import mage.constants.*; +import mage.constants.PlayerAction; +import mage.counters.Counter; +import mage.counters.Counters; +import mage.game.Game; +import mage.game.Graveyard; +import mage.game.Table; +import mage.game.combat.CombatGroup; +import mage.game.draft.Draft; +import mage.game.match.Match; +import mage.game.match.MatchPlayer; +import mage.game.permanent.Permanent; +import mage.game.tournament.Tournament; +import mage.players.Library; +import mage.players.ManaPool; +import mage.players.Player; +import mage.players.net.UserData; +import mage.target.Target; +import mage.target.TargetAmount; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; +import mage.util.MessageToClient; + +import java.io.Serializable; +import java.util.*; + +/** + * + * @author Quercitron + */ +public class PlayerStub implements Player { + + private final UUID id = UUID.randomUUID(); + + @Override + public UUID getId() { + return id; + } + + @Override + public boolean isHuman() { + return false; + } + + @Override + public String getName() { + return null; + } + + @Override + public String getLogName() { + return null; + } + + @Override + public RangeOfInfluence getRange() { + return null; + } + + @Override + public Library getLibrary() { + return null; + } + + @Override + public Cards getSideboard() { + return null; + } + + @Override + public Graveyard getGraveyard() { + return null; + } + + @Override + public Abilities getAbilities() { + return null; + } + + @Override + public void addAbility(Ability ability) { + + } + + @Override + public Counters getCounters() { + return null; + } + + @Override + public int getLife() { + return 0; + } + + @Override + public void initLife(int life) { + + } + + @Override + public void setLife(int life, Game game) { + + } + + @Override + public int loseLife(int amount, Game game) { + return 0; + } + + @Override + public int gainLife(int amount, Game game) { + return 0; + } + + @Override + public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable) { + return 0; + } + + @Override + public int damage(int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, ArrayList appliedEffects) { + return 0; + } + + @Override + public boolean isCanLoseLife() { + return false; + } + + @Override + public void setCanLoseLife(boolean canLoseLife) { + + } + + @Override + public void setCanGainLife(boolean canGainLife) { + + } + + @Override + public boolean isCanGainLife() { + return false; + } + + @Override + public void setCanPayLifeCost(boolean canPayLifeCost) { + + } + + @Override + public boolean canPayLifeCost() { + return false; + } + + @Override + public void setCanPaySacrificeCost(boolean canPaySacrificeCost) { + + } + + @Override + public boolean canPaySacrificeCost() { + return false; + } + + @Override + public void setLifeTotalCanChange(boolean lifeTotalCanChange) { + + } + + @Override + public boolean isLifeTotalCanChange() { + return false; + } + + @Override + public void setLoseByZeroOrLessLife(boolean loseByZeroOrLessLife) { + + } + + @Override + public boolean canLoseByZeroOrLessLife() { + return false; + } + + @Override + public void setPlayCardsFromGraveyard(boolean playCardsFromGraveyard) { + + } + + @Override + public boolean canPlayCardsFromGraveyard() { + return false; + } + + @Override + public List getAlternativeSourceCosts() { + return null; + } + + @Override + public Cards getHand() { + return null; + } + + @Override + public int getLandsPlayed() { + return 0; + } + + @Override + public int getLandsPerTurn() { + return 0; + } + + @Override + public void setLandsPerTurn(int landsPerTurn) { + + } + + @Override + public int getLoyaltyUsePerTurn() { + return 0; + } + + @Override + public void setLoyaltyUsePerTurn(int loyaltyUsePerTurn) { + + } + + @Override + public int getMaxHandSize() { + return 0; + } + + @Override + public void setMaxHandSize(int maxHandSize) { + + } + + @Override + public int getMaxAttackedBy() { + return 0; + } + + @Override + public void setMaxAttackedBy(int maxAttackedBy) { + + } + + @Override + public boolean isPassed() { + return false; + } + + @Override + public boolean isEmptyDraw() { + return false; + } + + @Override + public void pass(Game game) { + + } + + @Override + public void resetPassed() { + + } + + @Override + public void resetPlayerPassedActions() { + + } + + @Override + public boolean getPassedTurn() { + return false; + } + + @Override + public boolean getPassedUntilEndOfTurn() { + return false; + } + + @Override + public boolean getPassedUntilNextMain() { + return false; + } + + @Override + public boolean getPassedUntilStackResolved() { + return false; + } + + @Override + public boolean getPassedAllTurns() { + return false; + } + + @Override + public AbilityType getJustActivatedType() { + return null; + } + + @Override + public void setJustActivatedType(AbilityType abilityType) { + + } + + @Override + public boolean hasLost() { + return false; + } + + @Override + public boolean hasWon() { + return false; + } + + @Override + public boolean hasQuit() { + return false; + } + + @Override + public void quit(Game game) { + + } + + @Override + public boolean hasTimerTimeout() { + return false; + } + + @Override + public void timerTimeout(Game game) { + + } + + @Override + public boolean hasIdleTimeout() { + return false; + } + + @Override + public void idleTimeout(Game game) { + + } + + @Override + public boolean hasLeft() { + return false; + } + + @Override + public boolean isInGame() { + return false; + } + + @Override + public boolean canRespond() { + return false; + } + + @Override + public void otherPlayerLeftGame(Game game) { + + } + + @Override + public ManaPool getManaPool() { + return null; + } + + @Override + public Set getInRange() { + return null; + } + + @Override + public boolean isTopCardRevealed() { + return false; + } + + @Override + public void setTopCardRevealed(boolean topCardRevealed) { + + } + + @Override + public UserData getUserData() { + return null; + } + + @Override + public void setUserData(UserData userData) { + + } + + @Override + public boolean canLose(Game game) { + return false; + } + + @Override + public boolean autoLoseGame() { + return false; + } + + @Override + public Set getPlayersUnderYourControl() { + return null; + } + + @Override + public void controlPlayersTurn(Game game, UUID playerId) { + + } + + @Override + public void setTurnControlledBy(UUID playerId) { + + } + + @Override + public UUID getTurnControlledBy() { + return null; + } + + @Override + public void resetOtherTurnsControlled() { + + } + + @Override + public boolean isGameUnderControl() { + return false; + } + + @Override + public void setGameUnderYourControl(boolean value) { + + } + + @Override + public boolean isTestMode() { + return false; + } + + @Override + public void setTestMode(boolean value) { + + } + + @Override + public void addAction(String action) { + + } + + @Override + public int getActionCount() { + return 0; + } + + @Override + public void setAllowBadMoves(boolean allowBadMoves) { + + } + + @Override + public void init(Game game) { + + } + + @Override + public void init(Game game, boolean testMode) { + + } + + @Override + public void useDeck(Deck deck, Game game) { + + } + + @Override + public void reset() { + + } + + @Override + public void shuffleLibrary(Game game) { + + } + + @Override + public int drawCards(int num, Game game) { + return 0; + } + + @Override + public int drawCards(int num, Game game, ArrayList appliedEffects) { + return 0; + } + + @Override + public boolean cast(SpellAbility ability, Game game, boolean noMana) { + return false; + } + + @Override + public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { + return null; + } + + @Override + public boolean putInHand(Card card, Game game) { + return false; + } + + @Override + public boolean removeFromHand(Card card, Game game) { + return false; + } + + @Override + public boolean removeFromBattlefield(Permanent permanent, Game game) { + return false; + } + + @Override + public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) { + return false; + } + + @Override + public boolean removeFromGraveyard(Card card, Game game) { + return false; + } + + @Override + public boolean removeFromLibrary(Card card, Game game) { + return false; + } + + @Override + public boolean searchLibrary(TargetCardInLibrary target, Game game) { + return false; + } + + @Override + public boolean searchLibrary(TargetCardInLibrary target, Game game, UUID targetPlayerId) { + return false; + } + + @Override + public boolean canPlayLand() { + return false; + } + + @Override + public boolean playLand(Card card, Game game) { + return false; + } + + @Override + public boolean activateAbility(ActivatedAbility ability, Game game) { + return false; + } + + @Override + public boolean triggerAbility(TriggeredAbility ability, Game game) { + return false; + } + + @Override + public boolean canBeTargetedBy(MageObject source, UUID sourceControllerId, Game game) { + return false; + } + + @Override + public boolean hasProtectionFrom(MageObject source, Game game) { + return false; + } + + @Override + public boolean flipCoin(Game game) { + return false; + } + + @Override + public boolean flipCoin(Game game, ArrayList appliedEffects) { + return false; + } + + @Override + public void discard(int amount, Ability source, Game game) { + + } + + @Override + public Card discardOne(boolean random, Ability source, Game game) { + return null; + } + + @Override + public Cards discard(int amount, boolean random, Ability source, Game game) { + return null; + } + + @Override + public void discardToMax(Game game) { + + } + + @Override + public boolean discard(Card card, Ability source, Game game) { + return false; + } + + @Override + public void lost(Game game) { + + } + + @Override + public void lostForced(Game game) { + + } + + @Override + public void won(Game game) { + + } + + @Override + public void leave() { + + } + + @Override + public void concede(Game game) { + + } + + @Override + public void abort() { + + } + + @Override + public void abortReset() { + + } + + @Override + public void skip() { + + } + + @Override + public void sendPlayerAction(PlayerAction passPriorityAction, Game game, Object data) { + + } + + @Override + public int getStoredBookmark() { + return 0; + } + + @Override + public void setStoredBookmark(int bookmark) { + + } + + @Override + public void resetStoredBookmark(Game game) { + + } + + @Override + public void revealCards(String name, Cards cards, Game game) { + + } + + @Override + public void revealCards(String name, Cards cards, Game game, boolean postToLog) { + + } + + @Override + public void lookAtCards(String name, Card card, Game game) { + + } + + @Override + public void lookAtCards(String name, Cards cards, Game game) { + + } + + @Override + public Player copy() { + return null; + } + + @Override + public void restore(Player player) { + + } + + @Override + public void setResponseString(String responseString) { + + } + + @Override + public void setResponseUUID(UUID responseUUID) { + + } + + @Override + public void setResponseBoolean(Boolean responseBoolean) { + + } + + @Override + public void setResponseInteger(Integer data) { + + } + + @Override + public void setResponseManaType(UUID manaTypePlayerId, ManaType responseManaType) { + + } + + @Override + public boolean priority(Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game) { + return false; + } + + @Override + public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseMulligan(Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, MessageToClient message, Ability source, Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + return false; + } + + @Override + public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { + return false; + } + + @Override + public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { + return false; + } + + @Override + public boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder) { + return false; + } + + @Override + public boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder) { + return false; + } + + @Override + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + return 0; + } + + @Override + public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { + return 0; + } + + @Override + public int chooseReplacementEffect(Map abilityMap, Game game) { + return 0; + } + + @Override + public TriggeredAbility chooseTriggeredAbility(List abilities, Game game) { + return null; + } + + @Override + public Mode chooseMode(Modes modes, Ability source, Game game) { + return null; + } + + @Override + public void selectAttackers(Game game, UUID attackingPlayerId) { + + } + + @Override + public void selectBlockers(Game game, UUID defendingPlayerId) { + + } + + @Override + public UUID chooseAttackerOrder(List attacker, Game game) { + return null; + } + + @Override + public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, List blockerOrder, Game game) { + return null; + } + + @Override + public void assignDamage(int damage, List targets, String singleTargetName, UUID sourceId, Game game) { + + } + + @Override + public int getAmount(int min, int max, String message, Game game) { + return 0; + } + + @Override + public void sideboard(Match match, Deck deck) { + + } + + @Override + public void construct(Tournament tournament, Deck deck) { + + } + + @Override + public void pickCard(List cards, Deck deck, Draft draft) { + + } + + @Override + public void declareAttacker(UUID attackerId, UUID defenderId, Game game, boolean allowUndo) { + + } + + @Override + public void declareBlocker(UUID defenderId, UUID blockerId, UUID attackerId, Game game) { + + } + + @Override + public List getAvailableAttackers(Game game) { + return null; + } + + @Override + public List getAvailableAttackers(UUID defenderId, Game game) { + return null; + } + + @Override + public List getAvailableBlockers(Game game) { + return null; + } + + @Override + public void beginTurn(Game game) { + + } + + @Override + public void endOfTurn(Game game) { + + } + + @Override + public void phasing(Game game) { + + } + + @Override + public void untap(Game game) { + + } + + @Override + public ManaOptions getManaAvailable(Game game) { + return null; + } + + @Override + public List getPlayable(Game game, boolean hidden) { + return null; + } + + @Override + public List getPlayableOptions(Ability ability, Game game) { + return null; + } + + @Override + public Set getPlayableInHand(Game game) { + return null; + } + + @Override + public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { + return null; + } + + @Override + public void addCounters(Counter counter, Game game) { + + } + + @Override + public List getAttachments() { + return null; + } + + @Override + public boolean addAttachment(UUID permanentId, Game game) { + return false; + } + + @Override + public boolean removeAttachment(Permanent permanent, Game game) { + return false; + } + + @Override + public void becomesActivePlayer() { + + } + + @Override + public int getTurns() { + return 0; + } + + @Override + public boolean lookAtFaceDownCard(Card card, Game game) { + return false; + } + + @Override + public void setPriorityTimeLeft(int timeLeft) { + + } + + @Override + public int getPriorityTimeLeft() { + return 0; + } + + @Override + public void setReachedNextTurnAfterLeaving(boolean reachedNextTurnAfterLeaving) { + + } + + @Override + public boolean hasReachedNextTurnAfterLeaving() { + return false; + } + + @Override + public boolean canJoinTable(Table table) { + return false; + } + + @Override + public void setCommanderId(UUID commanderId) { + + } + + @Override + public UUID getCommanderId() { + return null; + } + + @Override + public boolean moveCards(Cards cards, Zone fromZone, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Card card, Zone fromZone, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Set cards, Zone fromZone, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Card card, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Card card, Zone toZone, Ability source, Game game, boolean tapped, boolean faceDown, boolean byOwner, ArrayList appliedEffects) { + return false; + } + + @Override + public boolean moveCards(Cards cards, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Set cards, Zone toZone, Ability source, Game game) { + return false; + } + + @Override + public boolean moveCards(Set cards, Zone toZone, Ability source, Game game, boolean tapped, boolean faceDown, boolean byOwner, ArrayList appliedEffects) { + return false; + } + + @Override + public boolean moveCardsToExile(Card card, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName) { + return false; + } + + @Override + public boolean moveCardsToExile(Set cards, Ability source, Game game, boolean withName, UUID exileId, String exileZoneName) { + return false; + } + + @Override + public boolean moveCardToHandWithInfo(Card card, UUID sourceId, Game game) { + return false; + } + + @Override + public boolean moveCardToHandWithInfo(Card card, UUID sourceId, Game game, boolean withName) { + return false; + } + + @Override + public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, Game game, Zone fromZone, boolean withName) { + return false; + } + + @Override + public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, Game game, Zone fromZone) { + return false; + } + + @Override + public Set moveCardsToGraveyardWithInfo(Set cards, Ability source, Game game, Zone fromZone) { + return null; + } + + @Override + public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, Game game, Zone fromZone, boolean toTop, boolean withName) { + return false; + } + + @Override + public boolean hasOpponent(UUID playerToCheckId, Game game) { + return false; + } + + @Override + public void cleanUpOnMatchEnd() { + + } + + @Override + public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs) { + + } + + @Override + public UUID getCastSourceIdWithAlternateMana() { + return null; + } + + @Override + public ManaCosts getCastSourceIdManaCosts() { + return null; + } + + @Override + public Costs getCastSourceIdCosts() { + return null; + } + + @Override + public void addPermissionToShowHandCards(UUID watcherUserId) { + + } + + @Override + public boolean hasUserPermissionToSeeHand(UUID userId) { + return false; + } + + @Override + public void revokePermissionToSeeHandCards() { + + } + + @Override + public boolean isRequestToShowHandCardsAllowed() { + return false; + } + + @Override + public Set getUsersAllowedToSeeHandCards() { + return null; + } + + @Override + public boolean isInPayManaMode() { + return false; + } + + @Override + public void setMatchPlayer(MatchPlayer matchPlayer) { + + } + + @Override + public MatchPlayer getMatchPlayer() { + return null; + } + + @Override + public boolean scry(int value, Ability source, Game game) { + return false; + } + + @Override + public boolean addTargets(Ability ability, Game game) { + return false; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/TournamentStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/TournamentStub.java new file mode 100644 index 00000000000..acdc6c43e74 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/stub/TournamentStub.java @@ -0,0 +1,237 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package org.mage.test.stub; + +import mage.cards.ExpansionSet; +import mage.cards.decks.Deck; +import mage.game.draft.Draft; +import mage.game.events.Listener; +import mage.game.events.PlayerQueryEvent; +import mage.game.events.TableEvent; +import mage.game.tournament.*; +import mage.players.Player; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + * + * @author Quercitron + */ +public class TournamentStub implements Tournament { + + private final UUID id = UUID.randomUUID(); + + @Override + public UUID getId() { + return id; + } + + @Override + public void addPlayer(Player player, String playerType) { + + } + + @Override + public void removePlayer(UUID playerId) { + + } + + @Override + public TournamentPlayer getPlayer(UUID playerId) { + return null; + } + + @Override + public Collection getPlayers() { + return null; + } + + @Override + public Collection getRounds() { + return null; + } + + @Override + public List getSets() { + return null; + } + + @Override + public void updateResults() { + + } + + @Override + public void setBoosterInfo(String setInfo) { + + } + + @Override + public String getBoosterInfo() { + return null; + } + + @Override + public void submitDeck(UUID playerId, Deck deck) { + + } + + @Override + public void updateDeck(UUID playerId, Deck deck) { + + } + + @Override + public void autoSubmit(UUID playerId, Deck deck) { + + } + + @Override + public boolean allJoined() { + return false; + } + + @Override + public boolean isDoneConstructing() { + return false; + } + + @Override + public void quit(UUID playerId) { + + } + + @Override + public void leave(UUID playerId) { + + } + + @Override + public void nextStep() { + + } + + @Override + public void addTableEventListener(Listener listener) { + + } + + @Override + public void addPlayerQueryEventListener(Listener listener) { + + } + + @Override + public void fireConstructEvent(UUID playerId) { + + } + + @Override + public TournamentOptions getOptions() { + return null; + } + + @Override + public void setStartTime() { + + } + + @Override + public Date getStartTime() { + return null; + } + + @Override + public Date getEndTime() { + return null; + } + + @Override + public Date getStepStartTime() { + return null; + } + + @Override + public void setStepStartTime(Date date) { + + } + + @Override + public TournamentType getTournamentType() { + return null; + } + + @Override + public void setTournamentType(TournamentType tournamentType) { + + } + + @Override + public String getTournamentState() { + return null; + } + + @Override + public void setTournamentState(String tournamentState) { + + } + + @Override + public int getNumberRounds() { + return 0; + } + + @Override + public void cleanUpOnTournamentEnd() { + + } + + @Override + public boolean isAbort() { + return false; + } + + @Override + public void setAbort(boolean abort) { + + } + + @Override + public void clearDraft() { + + } + + @Override + public Draft getDraft() { + return null; + } +} diff --git a/Mage/src/mage/game/tournament/TournamentSwiss.java b/Mage/src/mage/game/tournament/TournamentSwiss.java index 6855e57eb01..91622534ad8 100644 --- a/Mage/src/mage/game/tournament/TournamentSwiss.java +++ b/Mage/src/mage/game/tournament/TournamentSwiss.java @@ -27,13 +27,15 @@ */ package mage.game.tournament; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.UUID; + import mage.constants.TournamentPlayerState; import mage.game.events.TableEvent; +import mage.game.tournament.pairing.RoundPairings; +import mage.game.tournament.pairing.SwissPairingMinimalWeightMatching; +import mage.game.tournament.pairing.SwissPairingSimple; /** * @@ -65,76 +67,35 @@ public abstract class TournamentSwiss extends TournamentImpl { } protected Round createRoundSwiss() { + List roundPlayers = getActivePlayers(); + + RoundPairings roundPairings; + if (roundPlayers.size() <= 16) { + SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds); + roundPairings = swissPairing.getRoundPairings(); + } else { + SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds); + roundPairings = swissPairing.getRoundPairings(); + } + Round round = new Round(rounds.size() + 1, this); rounds.add(round); - List roundPlayers = getActivePlayers(); - // sort players by tournament points - Collections.sort(roundPlayers, new Comparator() { - @Override - public int compare(TournamentPlayer p1, TournamentPlayer p2) { - return p2.getPoints() - p1.getPoints(); - } - - }); - // create pairings - while (roundPlayers.size() > 0) { - TournamentPlayer player1 = roundPlayers.get(0); - roundPlayers.remove(0); - TournamentPlayer playerForPossibleSecondPairing = null; - for (TournamentPlayer player2 : roundPlayers) { - if (alreadyPaired(player1, player2)) { - // if already paired but equal points -> remember if second pairing is needed - if (playerForPossibleSecondPairing == null) { - playerForPossibleSecondPairing = player2; - } - } else { - if (player2.getPoints() < player1.getPoints() && playerForPossibleSecondPairing != null) { - // pair again with a player - round.addPairing(new TournamentPairing(player1, playerForPossibleSecondPairing)); - roundPlayers.remove(playerForPossibleSecondPairing); - player1 = null; - break; - } else { - // pair agains the next not paired before - round.addPairing(new TournamentPairing(player1, player2)); - roundPlayers.remove(player2); - player1 = null; - break; - } - } - } - if (player1 != null) { - // no pairing done yet - if (playerForPossibleSecondPairing != null) { - // pair again with a player - round.addPairing(new TournamentPairing(player1, playerForPossibleSecondPairing)); - roundPlayers.remove(playerForPossibleSecondPairing); - } else { - // player free round - add to bye players of this round - round.getPlayerByes().add(player1); - if (round.getRoundNumber() == getNumberRounds()) { - player1.setState(TournamentPlayerState.FINISHED); - } else { - player1.setState(TournamentPlayerState.WAITING); - } - player1.setStateInfo("Round Bye"); - updateResults(); - } - } + for (TournamentPairing pairing : roundPairings.getPairings()) { + round.addPairing(pairing); } + for (TournamentPlayer playerBye : roundPairings.getPlayerByes()) { + // player free round - add to bye players of this round + round.getPlayerByes().add(playerBye); + if (round.getRoundNumber() == getNumberRounds()) { + playerBye.setState(TournamentPlayerState.FINISHED); + } else { + playerBye.setState(TournamentPlayerState.WAITING); + } + playerBye.setStateInfo("Round Bye"); + updateResults(); + } + return round; } - protected boolean alreadyPaired(TournamentPlayer player1, TournamentPlayer player2) { - for (Round round : rounds) { - for (TournamentPairing pairing : round.getPairs()) { - if (pairing.getPlayer1().equals(player1) || pairing.getPlayer2().equals(player1)) { - if (pairing.getPlayer1().equals(player2) || pairing.getPlayer2().equals(player2)) { - return true; - } - } - } - } - return false; - } } diff --git a/Mage/src/mage/game/tournament/pairing/RoundPairings.java b/Mage/src/mage/game/tournament/pairing/RoundPairings.java new file mode 100644 index 00000000000..b6c77874e18 --- /dev/null +++ b/Mage/src/mage/game/tournament/pairing/RoundPairings.java @@ -0,0 +1,64 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.game.tournament.pairing; + +import mage.game.tournament.TournamentPairing; +import mage.game.tournament.TournamentPlayer; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Quercitron + */ +public class RoundPairings { + + public RoundPairings() { + this(new ArrayList(), new ArrayList()); + } + + public RoundPairings(List pairings, List playerByes) { + this.pairings = pairings; + this.playerByes = playerByes; + } + + private final List pairings; + + private final List playerByes; + + public List getPairings() { + return pairings; + } + + public List getPlayerByes() { + return playerByes; + } + +} diff --git a/Mage/src/mage/game/tournament/pairing/SwissPairingMinimalWeightMatching.java b/Mage/src/mage/game/tournament/pairing/SwissPairingMinimalWeightMatching.java new file mode 100644 index 00000000000..bff184c5ab5 --- /dev/null +++ b/Mage/src/mage/game/tournament/pairing/SwissPairingMinimalWeightMatching.java @@ -0,0 +1,283 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.game.tournament.pairing; + +import mage.game.tournament.Round; +import mage.game.tournament.TournamentPairing; +import mage.game.tournament.TournamentPlayer; + +import java.util.*; + +/** + * + * @author Quercitron + */ + +// SwissPairingMinimalWeightMatching creates round pairings for swiss tournament. +// It assigns weight to each possible pair and searches perfect matching with minimal weight +// for more details see https://www.leaguevine.com/blog/18/swiss-tournament-scheduling-leaguevines-new-algorithm/ +// This implementation don't use fast minimum weight maximum matching algorithm, +// it uses brute-force search, so it works reasonably fast only up to 16 players. + +public class SwissPairingMinimalWeightMatching { + + private final int playersCount; + + List swissPlayers; + + // number of vertexes in graph + private final int n; + + // weight of pairings + private final int[][] w; + + public SwissPairingMinimalWeightMatching(List players, List rounds) { + playersCount = players.size(); + + swissPlayers = new ArrayList(); + for (int i = 0; i < playersCount; i++) { + PlayerInfo swissPlayer = new PlayerInfo(); + swissPlayer.tournamentPlayer = players.get(i); + swissPlayer.playerId = players.get(i).getPlayer().getId(); + swissPlayer.points = players.get(i).getPoints(); + swissPlayers.add(swissPlayer); + } + + // shuffle players first to add some randomness + Collections.shuffle(swissPlayers); + Map map = new HashMap<>(); + for (int i = 0; i < playersCount; i++) { + swissPlayers.get(i).id = i; + map.put(swissPlayers.get(i).playerId, i); + } + + // calculate Tie Breaker points -- Sum of Opponents' Scores (SOS) + // see http://senseis.xmp.net/?SOS + for (Round round : rounds) { + for (TournamentPairing pairing : round.getPairs()) { + TournamentPlayer player1 = pairing.getPlayer1(); + TournamentPlayer player2 = pairing.getPlayer2(); + + int id1 = map.get(player1.getPlayer().getId()); + int id2 = map.get(player2.getPlayer().getId()); + + swissPlayers.get(id1).sosPoints += player2.getPoints(); + swissPlayers.get(id2).sosPoints += player1.getPoints(); + // todo: sos points for byes? maybe add player points? + } + } + + // sort by points and then by sos points + Collections.sort(swissPlayers, new Comparator() { + @Override + public int compare(PlayerInfo p1, PlayerInfo p2) { + int result = p2.points - p1.points; + if (result != 0) { + return result; + } + return p2.sosPoints - p1.sosPoints; + } + }); + + // order could be changed, update ids and mapping + for (int i = 0; i < playersCount; i++) { + swissPlayers.get(i).id = i; + map.put(swissPlayers.get(i).playerId, i); + } + + // count ties and matches between players + int[][] duels = new int[playersCount][playersCount]; + int[] byes = new int[playersCount]; + for (Round round : rounds) { + for (TournamentPairing pairing : round.getPairs()) { + TournamentPlayer player1 = pairing.getPlayer1(); + TournamentPlayer player2 = pairing.getPlayer2(); + + int id1 = map.get(player1.getPlayer().getId()); + int id2 = map.get(player2.getPlayer().getId()); + + duels[id1][id2]++; + duels[id2][id1]++; + } + for (TournamentPlayer playerBye : round.getPlayerByes()) { + int id = map.get(playerBye.getPlayer().getId()); + byes[id]++; + } + } + + // set vertex count + // add vertex for bye if we have odd number of players + n = (playersCount % 2 == 1 ? playersCount + 1 : playersCount); + + // calculate weight + // try to pair players with equal scores + w = new int[n][n]; + for (int i = 0; i < playersCount; i++) { + PlayerInfo player = swissPlayers.get(i); + for (int p = player.points; p >= 0 ; p--) { + int first = -1; + int last = -1; + for (int j = 0; j < playersCount; j++) { + if (swissPlayers.get(j).points == p) { + if (first < 0) { + first = j; + } + last = j; + } + } + if (first < 0) { + continue; + } + int self = (p == player.points ? i : first - 1); + int diff = 10 * (player.points - p); + for (int j = Math.max(first, i); j <= last; j++) { + w[i][j] = Math.abs(j - (last + first - self)) + diff; + w[j][i] = w[i][j]; + } + } + } + + // avoid pairing players that have played each other already + for (int i = 0; i < playersCount; i++) { + for (int j = 0; j < i; j++) { + w[i][j] += duels[i][j] * 500; + w[j][i] = w[i][j]; + } + } + + // try to give bye to a player with a low score + // try to avoid giving the same person multiple byes + if (n > playersCount) { + for (int i = 0; i < playersCount; i++) { + w[i][n - 1] = 10 * (swissPlayers.get(i).points - swissPlayers.get(playersCount - 1).points) + (playersCount - i - 1); + w[i][n - 1] += byes[i] * 2000; + w[n - 1][i] = w[i][n - 1]; + } + } + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + w[i][j] *= w[i][j]; + } + } + + // initialize variables for backtrack + used = new boolean[n]; + pairs = new int[n]; + Arrays.fill(pairs, -1); + result = new int[n]; + weight = 0; + minCost = -1; + makePairings(0); + } + + public RoundPairings getRoundPairings() { + // return round pairings with minimal weight + List pairings = new ArrayList<>(); + List playerByes = new ArrayList<>(); + + Map map = new HashMap<>(); + for (PlayerInfo player : swissPlayers) { + map.put(player.id, player.tournamentPlayer); + } + + if (n > playersCount) { + // last vertex -- bye + playerByes.add(map.get(result[n - 1])); + result[result[n - 1]] = -1; + result[n - 1] = -1; + } + + for (int i = 0; i < playersCount; i++) { + if (result[i] >= 0) { + pairings.add(new TournamentPairing(map.get(i), map.get(result[i]))); + result[result[i]] = -1; + result[i] = -1; + } + } + + return new RoundPairings(pairings, playerByes); + } + + boolean[] used; + + // current pairs + int[] pairs; + // current weight + int weight; + + int[] result; + int minCost; + + // backtrack all possible pairings and choose one with minimal weight + private void makePairings(int t) { + if (t >= n) { + if (minCost < 0 || minCost > weight) { + minCost = weight; + System.arraycopy(pairs, 0, result, 0, n); + } + return; + } + + if (!used[t]) { + for (int i = t + 1; i < n; i++) { + if (!used[i]) { + pairs[t] = i; + pairs[i] = t; + used[t] = true; + used[i] = true; + weight += w[t][i]; + + makePairings(t + 1); + + pairs[t] = -1; + pairs[i] = -1; + used[t] = false; + used[i] = false; + weight -= w[t][i]; + } + } + } else { + makePairings(t + 1); + } + } + + class PlayerInfo { + public int id; + + public TournamentPlayer tournamentPlayer; + + public UUID playerId; + + public int points; + + public int sosPoints; + } +} + diff --git a/Mage/src/mage/game/tournament/pairing/SwissPairingSimple.java b/Mage/src/mage/game/tournament/pairing/SwissPairingSimple.java new file mode 100644 index 00000000000..0af702e22c2 --- /dev/null +++ b/Mage/src/mage/game/tournament/pairing/SwissPairingSimple.java @@ -0,0 +1,124 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.game.tournament.pairing; + +import mage.game.tournament.Round; +import mage.game.tournament.TournamentPairing; +import mage.game.tournament.TournamentPlayer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * + * @author BetaSteward_at_googlemail.com + * @author Quercitron + */ +public class SwissPairingSimple { + + private final RoundPairings roundPairings; + + public SwissPairingSimple(List players, List rounds) { + roundPairings = getPairingsSimple(players, rounds); + } + + public RoundPairings getRoundPairings() { + return roundPairings; + } + + private RoundPairings getPairingsSimple(List players, List rounds) { + List pairings = new ArrayList<>(); + List playerByes = new ArrayList<>(); + + // sort players by tournament points + Collections.sort(players, new Comparator() { + @Override + public int compare(TournamentPlayer p1, TournamentPlayer p2) { + return p2.getPoints() - p1.getPoints(); + } + + }); + // create pairings + while (players.size() > 0) { + TournamentPlayer player1 = players.get(0); + players.remove(0); + TournamentPlayer playerForPossibleSecondPairing = null; + for (TournamentPlayer player2 : players) { + if (alreadyPaired(rounds, player1, player2)) { + // if already paired but equal points -> remember if second pairing is needed + if (playerForPossibleSecondPairing == null) { + playerForPossibleSecondPairing = player2; + } + } else { + if (player2.getPoints() < player1.getPoints() && playerForPossibleSecondPairing != null) { + // pair again with a player + pairings.add(new TournamentPairing(player1, playerForPossibleSecondPairing)); + players.remove(playerForPossibleSecondPairing); + player1 = null; + break; + } else { + // pair agains the next not paired before + pairings.add(new TournamentPairing(player1, player2)); + players.remove(player2); + player1 = null; + break; + } + } + } + if (player1 != null) { + // no pairing done yet + if (playerForPossibleSecondPairing != null) { + // pair again with a player + pairings.add(new TournamentPairing(player1, playerForPossibleSecondPairing)); + players.remove(playerForPossibleSecondPairing); + } else { + // player free round - add to bye players of this round + playerByes.add(player1); + } + } + } + + return new RoundPairings(pairings, playerByes); + } + + private boolean alreadyPaired(List rounds, TournamentPlayer player1, TournamentPlayer player2) { + for (Round round : rounds) { + for (TournamentPairing pairing : round.getPairs()) { + if (pairing.getPlayer1().equals(player1) || pairing.getPlayer2().equals(player1)) { + if (pairing.getPlayer1().equals(player2) || pairing.getPlayer2().equals(player2)) { + return true; + } + } + } + } + return false; + } +}