Adding Championship Swiss pairing mode

- Champ Swiss runs for a fixed number of rounds (8) as opposed to basing it on the number of players
- Adjusted automatic swiss between-round delay to 1 minute instead of 2
- Added Paused stage to be used by kickoff-enabled tournaments
This commit is contained in:
Christian 'ketura' McCarty
2023-10-14 02:05:54 -05:00
parent 771648dfdc
commit 8e8d875f8b
6 changed files with 351 additions and 11 deletions

View File

@@ -9,12 +9,12 @@ WHERE id = 9
INSERT INTO gemp_db.scheduled_tournament
(tournament_id, name, format, start_date, cost, playoff, tiebreaker, prizes, minimum_players, manual_kickoff, started)
VALUES('test-tourney-4', 'Test4', 'pc_movie', '2023-08-26 05:29:00', 0, 'swiss', 'owr', 'daily', 2, true, false);
VALUES('2023-wc-am-pc-expanded-walk-on', 'AM PC-Expanded Walk-on Qualifier', 'pc_expanded', '2023-09-23 17:00:00', 0, 'swiss', 'owr', 'daily', 2, true, false);
SELECT *
FROM player p
WHERE name = 'Pokec'
WHERE name LIKE 'Chad%'
SELECT *
FROM deck d
@@ -22,6 +22,23 @@ where player_id = 29802
AND name = 'Rohan/Uruk Towers standard'
SELECT *
FROM league l
ORDER BY id DESC
UPDATE league
SET parameters = '20230908,default,0.69,3,2,open,7,5,rev_tow_sta,7,10'
WHERE id = 642
SELECT *
,CONCAT('https://play.lotrtcgpc.net/gemp-lotr/game.html?replayId=',REPLACE(winner, '_', '%5F'), '$', win_recording_id) AS winner_replay
,CONCAT('https://play.lotrtcgpc.net/gemp-lotr/game.html?replayId=',REPLACE(loser, '_', '%5F'), '$', lose_recording_id) AS loser_replay
FROM game_history gh
WHERE tournament = 'Constructed - Weekend Qualifier PC-Movie - Serie 1'
AND (winner = 'Chadwick537' OR loser = 'Chadwick537')
ORDER BY id DESC
SELECT *
FROM tournament t
@@ -34,7 +51,7 @@ WHERE name IS NULL
UPDATE tournament
SET stage = 'Preparing'
WHERE id = 1386
WHERE id = 1422
UPDATE tournament
@@ -48,7 +65,11 @@ ORDER BY ID DESC
UPDATE tournament_player
SET dropped = 1
WHERE id = 10387
WHERE id = 11284
SELECT *
FROM tournament_match tm
ORDER BY id DESC
INSERT INTO gemp_db.tournament_player
@@ -67,11 +88,54 @@ FROM game_history gh
ORDER BY id DESC
UPDATE tournament_match
SET winner = 'dstaley'
WHERE id = 14638
SELECT *
UPDATE tournament_match
SET winner = stephan77
WHERE id = 14637
UPDATE game_history
SET winner = 'dstaley', winnerId = 33114, loser = 'johnec', loserId = 35804
WHERE id = 1214409
UPDATE game_history
SET winner = 'stephan77', winnerId = 30969, loser = 'balrog69', loserId = 35908
WHERE id = 1214410
SELECT FROM_UNIXTIME('1695509366664')
SELECT *, FROM_UNIXTIME(floor(transfer_date / 1000)) AS transfer_time
FROM transfer t
WHERE player = 'Aaron_Brutcher'
AND collection LIKE '%2_125%'
ORDER BY id DESC
SELECT *
FROM collection c
INNER JOIN player p
ON p.id = c.player_id
INNER JOIN collection_entries ce
ON ce.collection_id = c.id
WHERE p.name = 'Aaron_Brutcher'
AND c.id IN (199929, 202698)
AND ce.product = '2_125'
ORDER BY IFNULL(ce.modified_date, ce.created_date)
UPDATE collection_entries
SET quantity = 4
WHERE collection_id = 199929 AND product = '2_125'
SELECT c.*
FROM collection c
INNER JOIN player p
ON p.id = c.player_id
WHERE p.name = 'Aaron_Brutcher'

View File

@@ -805,7 +805,7 @@ public class HallServer extends AbstractServer {
null, null, true, false, false, false, GameTimer.TOURNAMENT_TIMER, null);
wcGameSettings = new GameSettings(null, _formatLibrary.getFormat(_tournament.getFormat()),
null, null, true, false, false, false, GameTimer.EXPANDED_CHAMPIONSHIP_TIMER, null);
null, null, true, false, false, false, GameTimer.CHAMPIONSHIP_TIMER, null);
}
@Override

View File

@@ -0,0 +1,27 @@
package com.gempukku.lotro.tournament;
import com.gempukku.lotro.competitive.PlayerStanding;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class ChampionshipSwissPairingMechanism extends SwissPairingMechanism {
private final Integer _targetRounds;
public ChampionshipSwissPairingMechanism(String registryRepresentation, int targetRounds) {
super(registryRepresentation, targetRounds);
_targetRounds = targetRounds;
}
@Override
public String getPlayOffSystem() {
return "WC Swiss";
}
@Override
public boolean isFinished(int round, Set<String> players, Set<String> droppedPlayers) {
return round >= _targetRounds;
}
}

View File

@@ -26,7 +26,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DefaultTournament implements Tournament {
// 10 minutes
private final int _deckBuildTime = 10 * 60 * 1000;
private long _waitForPairingsTime = 1000 * 60 * 2;
private long _waitForPairingsTime = 1000 * 60 * 1;
private final PairingMechanism _pairingMechanism;
private final TournamentPrizes _tournamentPrizes;
@@ -107,7 +107,7 @@ public class DefaultTournament implements Tournament {
_players);
} else if (_tournamentStage == Stage.DECK_BUILDING) {
_deckBuildStartTime = System.currentTimeMillis();
} else if (_tournamentStage == Stage.AWAITING_KICKOFF) {
} else if (_tournamentStage == Stage.AWAITING_KICKOFF || _tournamentStage == Stage.PAUSED) {
} else if (_tournamentStage == Stage.FINISHED) {
_finishedTournamentMatches.addAll(_tournamentService.getMatches(_tournamentId));
@@ -313,7 +313,7 @@ public class DefaultTournament implements Tournament {
result = true;
}
}
if (_tournamentStage == Stage.AWAITING_KICKOFF) {
if (_tournamentStage == Stage.AWAITING_KICKOFF || _tournamentStage == Stage.PAUSED) {
}
else if (_tournamentStage == Stage.PREPARING) {
@@ -325,7 +325,7 @@ public class DefaultTournament implements Tournament {
if (_pairingMechanism.isFinished(_tournamentRound, _players, _droppedPlayers)) {
finishTournament(tournamentCallback, collectionsManager);
} else {
tournamentCallback.broadcastMessage("Tournament " + _tournamentName + " will start round "+(_tournamentRound+1)+" in 2 minutes");
tournamentCallback.broadcastMessage("Tournament " + _tournamentName + " will start round "+(_tournamentRound+1)+" in 1 minute.");
_nextTask = new PairPlayers();
}
result = true;
@@ -482,7 +482,7 @@ public class DefaultTournament implements Tournament {
if(game == null)
continue;
var gameStart = game.GetUTCStartDate();
var gameEnd = game.GetUTCEndDate();

View File

@@ -18,6 +18,7 @@ public interface Tournament {
DRAFT("Drafting"),
DECK_BUILDING("Deck building"),
AWAITING_KICKOFF("Awaiting kickoff"),
PAUSED("Paused"),
PREPARING("Preparing"),
PLAYING_GAMES("Playing games"),
FINISHED("Finished");
@@ -59,6 +60,8 @@ public interface Tournament {
return new SwissPairingMechanism("swiss");
if (pairingType.equals("swiss-3"))
return new SwissPairingMechanism("swiss-3", 3);
if (pairingType.equals("wc-swiss"))
return new ChampionshipSwissPairingMechanism("wc-swiss", 8);
return null;
}

View File

@@ -0,0 +1,246 @@
package com.gempukku.lotro.tournament;
import com.gempukku.lotro.at.AbstractAtTest;
import com.gempukku.lotro.competitive.BestOfOneStandingsProducer;
import com.gempukku.lotro.competitive.PlayerStanding;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.Assert.*;
public class ChampionshipSwissPairingMechanismTest extends AbstractAtTest {
// @Test
// public void testPairingLargeTournament() {
// int repeatCount = 1;
// int playerCount = 4096*2;
//
// for (int repeat = 0; repeat < repeatCount; repeat++) {
// testSwissPairingForPlayerCount(playerCount);
// }
// }
public void calculateSallyJackChances() {
// 10 Sallys and 250 Jacks play in a tournament with 9 rounds of Swiss and Top 8.
// Sally has 66% chance of winning a game against Jack and 50% against anotherSally, Jack has 50% of winning
// a game against another Jack
int repeatCount = 100000;
int roundCount = 9;
int betterPlayerCount = 10;
int worsePlayerCount = 250;
float chanceToWin = 0.66666f;
int playerCount = betterPlayerCount+worsePlayerCount;
int betterPlayerWins =0;
Set<String> betterPlayers = new HashSet<>();
Set<String> worsePlayers = new HashSet<>();
Random rnd = ThreadLocalRandom.current();
for (int repeat = 0; repeat < repeatCount; repeat++) {
Set<String> players = new HashSet<>();
for (int i = 0; i < playerCount; i++) {
String playerName = String.valueOf(i);
players.add(playerName);
if (i<betterPlayerCount)
betterPlayers.add(playerName);
else
worsePlayers.add(playerName);
}
Set<String> droppedPlayers = new HashSet<>();
Map<String, Integer> byes = new HashMap<>();
Set<TournamentMatch> matches = new HashSet<>();
Map<String, Set<String>> previouslyPaired = new HashMap<>();
for (String player : players)
previouslyPaired.put(player, new HashSet<>());
var pairing = new ChampionshipSwissPairingMechanism("swiss", 8);
for (int i = 1; i <= roundCount; i++) {
if (!pairing.isFinished(i - 1, players, droppedPlayers)) {
List<PlayerStanding> standings = BestOfOneStandingsProducer.produceStandings(players, matches, 3, 0, byes);
Map<String, String> newPairings = new LinkedHashMap<>();
Set<String> newByes = new HashSet<>();
pairing.pairPlayers(i, players, droppedPlayers, byes, standings, previouslyPaired, newPairings, newByes);
if (newByes.size() > 0) {
for (String newBye : newByes) {
byes.put(newBye, 1);
}
}
for (Map.Entry<String, String> newPairing : newPairings.entrySet()) {
String playerOne = newPairing.getKey();
String playerTwo = newPairing.getValue();
String winner = getWinner(rnd, betterPlayers, worsePlayers, playerOne, playerTwo, chanceToWin);
previouslyPaired.get(playerOne).add(playerTwo);
previouslyPaired.get(playerTwo).add(playerOne);
matches.add(new TournamentMatch(playerOne, playerTwo, winner, i));
}
}
}
List<PlayerStanding> standings = BestOfOneStandingsProducer.produceStandings(players, matches, 1, 0, byes);
String firstSemi = getWinner(rnd, betterPlayers, worsePlayers, standings.get(0).playerName(), standings.get(7).playerName(), chanceToWin);
String secondSemi = getWinner(rnd, betterPlayers, worsePlayers, standings.get(1).playerName(), standings.get(6).playerName(), chanceToWin);
String thirdSemi = getWinner(rnd, betterPlayers, worsePlayers, standings.get(2).playerName(), standings.get(5).playerName(), chanceToWin);
String fourthSemi = getWinner(rnd, betterPlayers, worsePlayers, standings.get(3).playerName(), standings.get(4).playerName(), chanceToWin);
String firstFinalist = getWinner(rnd, betterPlayers, worsePlayers, firstSemi, fourthSemi, chanceToWin);
String secondFinalist = getWinner(rnd, betterPlayers, worsePlayers, secondSemi, thirdSemi, chanceToWin);
String winner = getWinner(rnd, betterPlayers, worsePlayers, firstFinalist, secondFinalist, chanceToWin);
if (betterPlayers.contains(winner))
betterPlayerWins++;
}
System.out.println(betterPlayerWins);
}
private String getWinner(Random rnd, Set<String> betterPlayers, Set<String> worsePlayers, String playerOne, String playerTwo, float winChance) {
if (betterPlayers.contains(playerOne) && worsePlayers.contains(playerTwo)) {
if (rnd.nextFloat() < winChance)
return playerOne;
else
return playerTwo;
} else if (betterPlayers.contains(playerTwo) && worsePlayers.contains(playerOne)) {
if (rnd.nextFloat() < winChance)
return playerTwo;
else
return playerOne;
} else {
if (rnd.nextBoolean())
return playerOne;
else
return playerTwo;
}
}
@Test
public void testPairingSmallTournament() {
int repeatCount = 10;
int playerCount = 12;
for (int repeat = 0; repeat < repeatCount; repeat++) {
testSwissPairingForPlayerCount(playerCount);
}
}
@Test
public void testPairingVerySmallTournament() {
int repeatCount = 10;
int playerCount = 8;
for (int repeat = 0; repeat < repeatCount; repeat++) {
testSwissPairingForPlayerCount(playerCount);
}
}
@Test
public void testPairingWCTournament() {
int repeatCount = 10;
int playerCount = 30;
for (int repeat = 0; repeat < repeatCount; repeat++) {
testSwissPairingForPlayerCount(playerCount);
}
}
@Test
public void testPairingSmallTournamentWithOddNumberOfPlayers() {
int repeatCount = 10;
int playerCount = 9;
for (int repeat = 0; repeat < repeatCount; repeat++) {
testSwissPairingForPlayerCount(playerCount);
}
}
private void testSwissPairingForPlayerCount(int playerCount) {
Set<String> players = new HashSet<>();
for (int i = 0; i < playerCount; i++)
players.add("p" + i);
Set<String> droppedPlayers = new HashSet<>();
Map<String, Integer> byes = new HashMap<>();
Set<TournamentMatch> matches = new HashSet<>();
Map<String, Set<String>> previouslyPaired = new HashMap<>();
for (String player : players)
previouslyPaired.put(player, new HashSet<>());
var pairing = new ChampionshipSwissPairingMechanism("swiss", Math.floorDiv(playerCount, 2));
for (int i = 1; i < 20; i++) {
if (!pairing.isFinished(i - 1, players, droppedPlayers)) {
System.out.println("Pairing round " + i);
List<PlayerStanding> standings = BestOfOneStandingsProducer.produceStandings(players, matches, 1, 0, byes);
for (PlayerStanding standing : standings) {
String player = standing.playerName();
log(player + " points - " + standing.points() + " played against: " + StringUtils.join(previouslyPaired.get(player), ","));
}
Map<String, String> newPairings = new LinkedHashMap<>();
Set<String> newByes = new HashSet<>();
assertFalse("Unable to pair for round " + i, pairing.pairPlayers(i, players, droppedPlayers, byes, standings, previouslyPaired, newPairings, newByes));
assertEquals("Invalid number of pairings", playerCount / 2, newPairings.size());
if (playerCount % 2 == 0)
assertEquals("Invalid number of byes", 0, newByes.size());
else {
assertEquals("Invalid number of byes", 1, newByes.size());
String newBye = newByes.iterator().next();
log("Bye - " + newBye);
assertNull("Player already received bye", byes.get(newBye));
byes.put(newBye, 1);
}
for (Map.Entry<String, String> newPairing : newPairings.entrySet()) {
String playerOne = newPairing.getKey();
String playerTwo = newPairing.getValue();
assertFalse(previouslyPaired.get(playerOne).contains(playerTwo));
assertFalse(previouslyPaired.get(playerTwo).contains(playerOne));
System.out.println("Paired " + playerOne + " against " + playerTwo + " points - " + getPlayerPoints(standings, playerOne) + " vs " + getPlayerPoints(standings, playerTwo));
String winner = ThreadLocalRandom.current().nextBoolean() ? playerOne : playerTwo;
log("Winner - " + winner);
previouslyPaired.get(playerOne).add(playerTwo);
previouslyPaired.get(playerTwo).add(playerOne);
matches.add(new TournamentMatch(playerOne, playerTwo, winner, i));
}
}
}
System.out.println("Final standings:");
List<PlayerStanding> standings = BestOfOneStandingsProducer.produceStandings(players, matches, 1, 0, byes);
for (PlayerStanding standing : standings) {
String player = standing.playerName();
System.out.println(standing.standing() + ". " + player + " points - " + standing.points() + " played against: " + StringUtils.join(previouslyPaired.get(player), ","));
}
}
private void log(String s) {
// System.out.println(s);
}
private int getPlayerPoints(List<PlayerStanding> standings, String player) {
for (PlayerStanding standing : standings) {
if (standing.playerName().equals(player))
return standing.points();
}
return -1;
}
}