GUI, table: added drafts with single multiplayer and multiple 1vs1 games support (#13701)

- new tourney's single game mode allow to play tourneys/drafts with all players in one game without multiple rounds;
- it's allow to setup classic drafts with 1 vs 1 games and multiple rounds
- added AI opponents supported including draft bots from a public servers;
This commit is contained in:
Oleg Agafonov 2025-05-31 10:39:23 +04:00 committed by GitHub
parent a61851db09
commit f2826cc676
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 247 additions and 256 deletions

View file

@ -11,7 +11,7 @@ import mage.game.match.MatchOptions;
public class FakeMatch extends MatchImpl {
public FakeMatch() {
super(new MatchOptions("fake match", "fake game type", true, 2));
super(new MatchOptions("fake match", "fake game type", false));
}
@Override

View file

@ -31,8 +31,7 @@ public class MatchOptions implements Serializable {
protected String deckType;
protected boolean limited;
protected List<PlayerType> playerTypes = new ArrayList<>();
protected boolean multiPlayer;
protected int numSeats;
protected boolean multiPlayer; // allow to play single game with all tourney's players
protected String password;
protected SkillLevel skillLevel = SkillLevel.CASUAL;
protected boolean rollbackTurnsAllowed;
@ -51,27 +50,14 @@ public class MatchOptions implements Serializable {
protected Collection<DeckCardInfo> perPlayerEmblemCards = Collections.emptySet();
protected Collection<DeckCardInfo> globalEmblemCards = Collections.emptySet();
public MatchOptions(String name, String gameType, boolean multiPlayer, int numSeats) {
public MatchOptions(String name, String gameType, boolean multiPlayer) {
this.name = name;
this.gameType = gameType;
this.password = "";
this.multiPlayer = multiPlayer;
this.numSeats = numSeats;
}
public void setNumSeats(int numSeats) {
this.numSeats = numSeats;
}
public int getNumSeats() {
return numSeats;
}
public void setMultiPlayer(boolean multiPlayer) {
this.multiPlayer = multiPlayer;
}
public boolean getMultiPlayer() {
public boolean isSingleGameTourney() {
return multiPlayer;
}

View file

@ -15,28 +15,19 @@ public class MultiplayerRound {
private final int roundNum;
private final Tournament tournament;
private final int numSeats;
private final List<TournamentPlayer> allPlayers = new ArrayList<>();
private Match match;
private UUID tableId;
public MultiplayerRound(int roundNum, Tournament tournament, int numSeats) {
public MultiplayerRound(int roundNum, Tournament tournament) {
this.roundNum = roundNum;
this.tournament = tournament;
this.numSeats = numSeats;
}
public List<TournamentPlayer> getAllPlayers () {
return allPlayers;
}
public TournamentPlayer getPlayer (int i) {
if (i >= 0 && i < numSeats && i < allPlayers.size()) {
return allPlayers.get(i);
}
return null;
}
public void addPairing(TournamentPairing match) {
this.allPlayers.add(match.getPlayer1());

View file

@ -25,6 +25,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public abstract class TournamentImpl implements Tournament {
private static final Logger logger = Logger.getLogger(TournamentImpl.class);
protected UUID id = UUID.randomUUID();
protected UUID tableId = null; // assign on table create
protected List<Round> rounds = new CopyOnWriteArrayList<>();
@ -223,9 +225,11 @@ public abstract class TournamentImpl implements Tournament {
}
protected void playMultiplayerRound(MultiplayerRound round) {
// it's a single game, so tourney will be ends immediately
// and will keep one game active for better performance
// and less max tourney restrictions on the server
updateResults();
playMultiPlayerMatch(round);
updateResults(); // show points from byes
}
protected List<TournamentPlayer> getActivePlayers() {
@ -248,6 +252,8 @@ public abstract class TournamentImpl implements Tournament {
player.setPoints(0);
player.setStateInfo("");
}
// multiple games tourney
for (Round round : rounds) {
for (TournamentPairing pair : round.getPairs()) {
Match match = pair.getMatch();
@ -256,8 +262,10 @@ public abstract class TournamentImpl implements Tournament {
TournamentPlayer tp2 = pair.getPlayer2();
MatchPlayer mp1 = match.getPlayer(pair.getPlayer1().getPlayer().getId());
MatchPlayer mp2 = match.getPlayer(pair.getPlayer2().getPlayer().getId());
// set player state if they finished the round
if (round.getRoundNumber() == rounds.size()) { // for elimination getRoundNumber = 0 so never true here
// for elimination getRoundNumber = 0 so never true here
if (round.getRoundNumber() == rounds.size()) {
match.setTournamentRound(round.getRoundNumber());
if (tp1.getState() == TournamentPlayerState.DUELING) {
if (round.getRoundNumber() == getNumberRounds()) {
@ -274,6 +282,7 @@ public abstract class TournamentImpl implements Tournament {
}
}
}
// Add round result
tp1.setResults(addRoundResult(round.getRoundNumber(), pair, tp1, tp2));
tp2.setResults(addRoundResult(round.getRoundNumber(), pair, tp2, tp1));

View file

@ -26,9 +26,9 @@ public class TournamentOptions implements Serializable {
protected int quitRatio;
protected int minimumRating;
public TournamentOptions(String name, String matchType, int numSeats) {
public TournamentOptions(String name, String matchType, boolean isSingleMultiplayerGame) {
this.name = name;
this.matchOptions = new MatchOptions("", matchType, numSeats > 2, numSeats);
this.matchOptions = new MatchOptions("", matchType, isSingleMultiplayerGame);
}
public String getName() {

View file

@ -8,8 +8,8 @@ package mage.game.tournament;
*/
public class TournamentSealedOptions extends TournamentOptions {
public TournamentSealedOptions(String name, String matchType, int numSeats) {
super(name, matchType, numSeats);
public TournamentSealedOptions(String name, String matchType, boolean isSingleMultiplayerGame) {
super(name, matchType, isSingleMultiplayerGame);
}
}

View file

@ -2,13 +2,13 @@
package mage.game.tournament;
import java.util.Map;
import java.util.UUID;
import mage.constants.MultiplayerAttackOption;
import mage.game.events.TableEvent;
import java.util.Map;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class TournamentSingleElimination extends TournamentImpl {
@ -19,38 +19,39 @@ public abstract class TournamentSingleElimination extends TournamentImpl {
@Override
protected void runTournament() {
for (Map.Entry<UUID, TournamentPlayer> entry: players.entrySet()) {
for (Map.Entry<UUID, TournamentPlayer> entry : players.entrySet()) {
if (entry.getValue().getPlayer().autoLoseGame()) {
entry.getValue().setEliminated();
entry.getValue().setResults("Auto Eliminated");
}
}
if (options.matchOptions.getNumSeats() == 2) {
}
if (options.matchOptions.isSingleGameTourney()) {
// one game with all players
options.matchOptions.setAttackOption(MultiplayerAttackOption.MULTIPLE);
MultiplayerRound round = new MultiplayerRound(0, this);
for (TournamentPlayer player : getActivePlayers()) {
round.addPlayer(player);
}
playMultiplayerRound(round);
} else {
// split to multiple rounds/games
while (this.getActivePlayers().size() > 1) {
// check if some player got killed / disconnected meanwhile and update their state
tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS);
Round round = createRoundRandom();
playRound(round);
eliminatePlayers(round);
}
} else {
options.matchOptions.setAttackOption(MultiplayerAttackOption.MULTIPLE);
MultiplayerRound round = new MultiplayerRound(0, this, options.matchOptions.getNumSeats());
for (TournamentPlayer player : getActivePlayers()) {
round.addPlayer(player);
}
playMultiplayerRound(round);
}
nextStep();
}
private void eliminatePlayers(Round round) {
for (TournamentPairing pair: round.getPairs()) {
for (TournamentPairing pair : round.getPairs()) {
pair.eliminatePlayers();
}
}
}

View file

@ -1,19 +1,18 @@
package mage.game.tournament;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import mage.constants.MultiplayerAttackOption;
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;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class TournamentSwiss extends TournamentImpl {
@ -31,82 +30,65 @@ public abstract class TournamentSwiss extends TournamentImpl {
}
}
if (options.matchOptions.getNumSeats() == 2) {
if (options.matchOptions.isSingleGameTourney()) {
// one game with all players (it's same as normal tourney's mode)
options.matchOptions.setAttackOption(MultiplayerAttackOption.MULTIPLE);
MultiplayerRound round = new MultiplayerRound(0, this);
for (TournamentPlayer player : getActivePlayers()) {
round.addPlayer(player);
}
playMultiplayerRound(round);
} else {
// split to multiple rounds/games
while (this.getActivePlayers().size() > 1 && this.getNumberRounds() > this.getRounds().size()) {
// check if some player got killed / disconnected meanwhile and update their state
tableEventSource.fireTableEvent(TableEvent.EventType.CHECK_STATE_PLAYERS);
// Swiss pairing
Round round = createRoundSwiss();
playRound(round);
}
} else {
options.matchOptions.setAttackOption(MultiplayerAttackOption.MULTIPLE);
MultiplayerRound round = createMultiplayerRound();
playMultiplayerRound(round);
}
nextStep();
}
protected Round createRoundSwiss() {
// multiple rounds, can be called multiple times per tourney
List<TournamentPlayer> roundPlayers = getActivePlayers();
boolean isLastRound = (rounds.size() + 1 == getNumberRounds());
if (options.matchOptions.isSingleGameTourney()) {
throw new IllegalStateException("Wrong code usage: multi rounds for non single game tourneys only (e.g. with two seats)");
}
Round round = null;
if (options.matchOptions.getNumSeats() == 2) {
RoundPairings roundPairings;
if (roundPlayers.size() <= 16) {
SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound);
roundPairings = swissPairing.getRoundPairings();
} else {
SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds);
roundPairings = swissPairing.getRoundPairings();
}
round = new Round(rounds.size() + 1, this);
rounds.add(round);
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 (isLastRound) {
playerBye.setState(TournamentPlayerState.FINISHED);
} else {
playerBye.setState(TournamentPlayerState.WAITING);
}
playerBye.setStateInfo("Round Bye");
updateResults();
RoundPairings roundPairings;
if (roundPlayers.size() <= 16) {
SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound);
roundPairings = swissPairing.getRoundPairings();
} else {
SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds);
roundPairings = swissPairing.getRoundPairings();
}
round = new Round(rounds.size() + 1, this);
rounds.add(round);
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 (isLastRound) {
playerBye.setState(TournamentPlayerState.FINISHED);
} else {
playerBye.setState(TournamentPlayerState.WAITING);
}
playerBye.setStateInfo("Round Bye");
updateResults();
}
return round;
}
public MultiplayerRound createMultiplayerRound() {
List<TournamentPlayer> roundPlayers = getActivePlayers();
boolean isLastRound = (rounds.size() + 1 == getNumberRounds());
MultiplayerRound round = null;
if (options.matchOptions.getNumSeats() > 2) {
options.matchOptions.setAttackOption(MultiplayerAttackOption.MULTIPLE);
RoundPairings roundPairings;
if (roundPlayers.size() <= 16) {
SwissPairingMinimalWeightMatching swissPairing = new SwissPairingMinimalWeightMatching(roundPlayers, rounds, isLastRound);
roundPairings = swissPairing.getRoundPairings();
} else {
SwissPairingSimple swissPairing = new SwissPairingSimple(roundPlayers, rounds);
roundPairings = swissPairing.getRoundPairings();
}
round = new MultiplayerRound(rounds.size() + 1, this, options.matchOptions.getNumSeats());
for (TournamentPairing pairing : roundPairings.getPairings()) {
round.addPairing(pairing);
}
}
return round;
}
}