mirror of
https://github.com/correl/mage.git
synced 2025-03-07 20:53:18 -10:00
Use approach with minimal weight matching to determine Swiss tournament pairings
This commit is contained in:
parent
9b074876db
commit
05a789cd8b
7 changed files with 2230 additions and 68 deletions
|
@ -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<TournamentPlayer> 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<Round> 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<TournamentPlayer> 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<Round> 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<TournamentPlayer> 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<Round> 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<TournamentPlayer> players = new ArrayList<>();
|
||||
for (int i = 0; i < playersCount; i++) {
|
||||
players.add(new TournamentPlayer(new PlayerStub(), null));
|
||||
}
|
||||
|
||||
List<TournamentPairing> playedPairs = new ArrayList<>();
|
||||
Set<TournamentPlayer> playersByes = new HashSet<>();
|
||||
|
||||
List<Round> 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<TournamentPairing> 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<TournamentPairing> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
1222
Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
Normal file
1222
Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
Normal file
File diff suppressed because it is too large
Load diff
237
Mage.Tests/src/test/java/org/mage/test/stub/TournamentStub.java
Normal file
237
Mage.Tests/src/test/java/org/mage/test/stub/TournamentStub.java
Normal file
|
@ -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<TournamentPlayer> getPlayers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Round> getRounds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExpansionSet> 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<TableEvent> listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPlayerQueryEventListener(Listener<PlayerQueryEvent> 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;
|
||||
}
|
||||
}
|
|
@ -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<TournamentPlayer> 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<TournamentPlayer> roundPlayers = getActivePlayers();
|
||||
// sort players by tournament points
|
||||
Collections.sort(roundPlayers, new Comparator<TournamentPlayer>() {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
|
64
Mage/src/mage/game/tournament/pairing/RoundPairings.java
Normal file
64
Mage/src/mage/game/tournament/pairing/RoundPairings.java
Normal file
|
@ -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<TournamentPairing>(), new ArrayList<TournamentPlayer>());
|
||||
}
|
||||
|
||||
public RoundPairings(List<TournamentPairing> pairings, List<TournamentPlayer> playerByes) {
|
||||
this.pairings = pairings;
|
||||
this.playerByes = playerByes;
|
||||
}
|
||||
|
||||
private final List<TournamentPairing> pairings;
|
||||
|
||||
private final List<TournamentPlayer> playerByes;
|
||||
|
||||
public List<TournamentPairing> getPairings() {
|
||||
return pairings;
|
||||
}
|
||||
|
||||
public List<TournamentPlayer> getPlayerByes() {
|
||||
return playerByes;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<PlayerInfo> swissPlayers;
|
||||
|
||||
// number of vertexes in graph
|
||||
private final int n;
|
||||
|
||||
// weight of pairings
|
||||
private final int[][] w;
|
||||
|
||||
public SwissPairingMinimalWeightMatching(List<TournamentPlayer> players, List<Round> rounds) {
|
||||
playersCount = players.size();
|
||||
|
||||
swissPlayers = new ArrayList<PlayerInfo>();
|
||||
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<UUID, Integer> 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<PlayerInfo>() {
|
||||
@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<TournamentPairing> pairings = new ArrayList<>();
|
||||
List<TournamentPlayer> playerByes = new ArrayList<>();
|
||||
|
||||
Map<Integer, TournamentPlayer> 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;
|
||||
}
|
||||
}
|
||||
|
124
Mage/src/mage/game/tournament/pairing/SwissPairingSimple.java
Normal file
124
Mage/src/mage/game/tournament/pairing/SwissPairingSimple.java
Normal file
|
@ -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<TournamentPlayer> players, List<Round> rounds) {
|
||||
roundPairings = getPairingsSimple(players, rounds);
|
||||
}
|
||||
|
||||
public RoundPairings getRoundPairings() {
|
||||
return roundPairings;
|
||||
}
|
||||
|
||||
private RoundPairings getPairingsSimple(List<TournamentPlayer> players, List<Round> rounds) {
|
||||
List<TournamentPairing> pairings = new ArrayList<>();
|
||||
List<TournamentPlayer> playerByes = new ArrayList<>();
|
||||
|
||||
// sort players by tournament points
|
||||
Collections.sort(players, new Comparator<TournamentPlayer>() {
|
||||
@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<Round> 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue