game, refactor: improved player related code, fixed miss reset fields between games like commanderIds (#11081, #11628)

This commit is contained in:
Oleg Agafonov 2024-01-15 00:25:51 +04:00
parent a090a2a9d0
commit 6ac2f44cc1
7 changed files with 92 additions and 71 deletions

View file

@ -44,7 +44,7 @@ public class TableController {
private static final Logger logger = Logger.getLogger(TableController.class); private static final Logger logger = Logger.getLogger(TableController.class);
private final ManagerFactory managerFactory; private final ManagerFactory managerFactory;
private final UUID userId; private final UUID userId; // table owner/creator (null in tourney's table)
private final UUID chatId; private final UUID chatId;
private final String controllerName; private final String controllerName;
private final Table table; private final Table table;
@ -604,17 +604,17 @@ public class TableController {
if (table.getState() == TableState.STARTING) { if (table.getState() == TableState.STARTING) {
try { try {
if (table.isTournamentSubTable()) { if (table.isTournamentSubTable()) {
logger.info("Tourn. match started id:" + match.getId() + " tournId: " + table.getTournament().getId()); logger.info("Tourney MATCH started id:" + match.getId() + " tournId: " + table.getTournament().getId());
} else { } else {
managerFactory.userManager().getUser(userId).ifPresent(user -> { managerFactory.userManager().getUser(userId).ifPresent(user -> {
logger.info("MATCH started [" + match.getName() + "] " + match.getId() + " (" + user.getName() + ')'); logger.info("Single MATCH started [" + match.getName() + "] " + match.getId() + " (" + user.getName() + ')');
logger.debug("- " + match.getOptions().getGameType() + " - " + match.getOptions().getDeckType()); logger.debug("- " + match.getOptions().getGameType() + " - " + match.getOptions().getDeckType());
}); });
} }
match.startMatch(); match.startMatch();
startGame(null); startGame(null);
} catch (GameException ex) { } catch (GameException e) {
logger.fatal("Error starting match ", ex); logger.fatal("Error starting match: " + e, e);
match.endGame(); match.endGame();
} }
} }

View file

@ -102,13 +102,14 @@ public class GameController implements GameCallback {
public void cleanUp() { public void cleanUp() {
cancelTimeout(); cancelTimeout();
for (GameSessionPlayer gameSessionPlayer : getGameSessions()) {
gameSessionPlayer.cleanUp();
}
managerFactory.chatManager().destroyChatSession(chatId);
for (PriorityTimer priorityTimer : timers.values()) { for (PriorityTimer priorityTimer : timers.values()) {
priorityTimer.cancel(); priorityTimer.cancel();
} }
getGameSessions().forEach(GameSessionPlayer::cleanUp);
getGameSessionWatchers().forEach(GameSessionWatcher::cleanUp);
managerFactory.chatManager().destroyChatSession(chatId);
} }
private void init() { private void init() {

View file

@ -2962,12 +2962,6 @@ public class TestPlayer implements Player {
computerPlayer.init(game); computerPlayer.init(game);
} }
@Override
public void init(Game game, boolean testMode) {
initialTurns = 0;
computerPlayer.init(game, testMode);
}
@Override @Override
public void reset() { public void reset() {
computerPlayer.reset(); computerPlayer.reset();

View file

@ -525,11 +525,6 @@ public class PlayerStub implements Player {
} }
@Override
public void init(Game game, boolean testMode) {
}
@Override @Override
public void useDeck(Deck deck, Game game) { public void useDeck(Deck deck, Game game) {

View file

@ -150,11 +150,6 @@ public class MatchPlayer implements Serializable {
// this.player = null; // this.player = null;
} }
public void cleanUp() {
// Free resources that are not needed after match end
this.player = null;
}
public String getName() { public String getName() {
return name; return name;
} }

View file

@ -370,10 +370,11 @@ public interface Player extends MageItem, Copyable<Player> {
void setAllowBadMoves(boolean allowBadMoves); void setAllowBadMoves(boolean allowBadMoves);
/**
* Reset values before new game, e.g. for next game
*/
void init(Game game); void init(Game game);
void init(Game game, boolean testMode);
void useDeck(Deck deck, Game game); void useDeck(Deck deck, Game game);
/** /**

View file

@ -70,10 +70,21 @@ import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* Server: basic player implementation, shared for human and AI
* <p>
* WARNING, if you add new fields then sync it with constructor, restore, reset and init methods
*/
public abstract class PlayerImpl implements Player, Serializable { public abstract class PlayerImpl implements Player, Serializable {
private static final Logger logger = Logger.getLogger(PlayerImpl.class); private static final Logger logger = Logger.getLogger(PlayerImpl.class);
/**
* During some steps we can't play anything
*/
final static Map<PhaseStep, Step.StepPart> SILENT_PHASES_STEPS = ImmutableMap.<PhaseStep, Step.StepPart>builder().
put(PhaseStep.DECLARE_ATTACKERS, Step.StepPart.PRE).build();
/** /**
* Used to cancel waiting requests send to the player * Used to cancel waiting requests send to the player
*/ */
@ -82,15 +93,18 @@ public abstract class PlayerImpl implements Player, Serializable {
protected final UUID playerId; protected final UUID playerId;
protected String name; protected String name;
protected boolean human; protected boolean human;
protected int life; protected int life;
protected boolean wins; protected boolean wins;
protected boolean draws; protected boolean draws;
protected boolean loses; protected boolean loses;
protected Library library; protected Library library;
protected Cards sideboard; protected Cards sideboard;
protected Cards hand; protected Cards hand;
protected Graveyard graveyard; protected Graveyard graveyard;
protected Set<UUID> commandersIds = new HashSet<>(0); protected Set<UUID> commandersIds = new HashSet<>(0);
protected Abilities<Ability> abilities; protected Abilities<Ability> abilities;
protected Counters counters; protected Counters counters;
protected int landsPlayed; protected int landsPlayed;
@ -98,6 +112,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected int maxHandSize = 7; protected int maxHandSize = 7;
protected int maxAttackedBy = Integer.MAX_VALUE; protected int maxAttackedBy = Integer.MAX_VALUE;
protected ManaPool manaPool; protected ManaPool manaPool;
// priority control // priority control
protected boolean passed; // player passed priority protected boolean passed; // player passed priority
protected boolean passedTurn; // F4 protected boolean passedTurn; // F4
@ -142,7 +157,6 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean drawsOnOpponentsTurn = false; protected boolean drawsOnOpponentsTurn = false;
protected FilterPermanent sacrificeCostFilter; protected FilterPermanent sacrificeCostFilter;
protected final List<AlternativeSourceCosts> alternativeSourceCosts = new ArrayList<>(); protected final List<AlternativeSourceCosts> alternativeSourceCosts = new ArrayList<>();
protected boolean isGameUnderControl = true; protected boolean isGameUnderControl = true;
@ -151,9 +165,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected Set<UUID> playersUnderYourControl = new HashSet<>(); protected Set<UUID> playersUnderYourControl = new HashSet<>();
protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>(); protected Set<UUID> usersAllowedToSeeHandCards = new HashSet<>();
protected List<UUID> attachments = new ArrayList<>(); protected List<UUID> attachments = new ArrayList<>();
protected boolean topCardRevealed = false; protected boolean topCardRevealed = false;
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn // 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
@ -184,12 +196,6 @@ public abstract class PlayerImpl implements Player, Serializable {
// Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy) // Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy)
protected final List<List<Mana>> availableTriggeredManaList = new ArrayList<>(); protected final List<List<Mana>> availableTriggeredManaList = new ArrayList<>();
/**
* During some steps we can't play anything
*/
protected final Map<PhaseStep, Step.StepPart> silentPhaseSteps = ImmutableMap.<PhaseStep, Step.StepPart>builder().
put(PhaseStep.DECLARE_ATTACKERS, Step.StepPart.PRE).build();
protected PlayerImpl(String name, RangeOfInfluence range) { protected PlayerImpl(String name, RangeOfInfluence range) {
this(UUID.randomUUID()); this(UUID.randomUUID());
this.name = name; this.name = name;
@ -205,7 +211,7 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
protected PlayerImpl(UUID id) { protected PlayerImpl(UUID id) {
this.playerId = id; this.playerId = id; // TODO: miss another fields init?
} }
protected PlayerImpl(final PlayerImpl player) { protected PlayerImpl(final PlayerImpl player) {
@ -291,6 +297,11 @@ public abstract class PlayerImpl implements Player, Serializable {
this.designations = CardUtil.deepCopyObject(player.designations); this.designations = CardUtil.deepCopyObject(player.designations);
} }
/**
* Restore on rollback
*
* @param player
*/
@Override @Override
public void restore(Player player) { public void restore(Player player) {
this.name = player.getName(); this.name = player.getName();
@ -398,43 +409,35 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Card card : deck.getSideboard()) { for (Card card : deck.getSideboard()) {
sideboard.add(card); sideboard.add(card);
} }
//TODO ARTI initialize extra decks here!
} }
/**
* Cast e.g. from Karn Liberated to restart the current game
*
* @param game
*/
@Override @Override
public void init(Game game) { public void init(Game game) {
init(game, false);
}
@Override
public void init(Game game, boolean testMode) {
this.abort = false; this.abort = false;
if (!testMode) {
this.hand.clear(); // keep old
this.graveyard.clear(); //this.playerId;
} //this.name;
this.library.reset(); //this.human;
this.abilities.clear();
this.counters.clear(); this.life = game.getStartingLife();
this.wins = false; this.wins = false;
this.draws = false; this.draws = false;
this.loses = false; this.loses = false;
this.left = false;
// reset is necessary because in tournament player will be used for each round
this.quit = false;
this.timerTimeout = false;
this.idleTimeout = false;
this.turns = 0; this.library.reset();
this.isGameUnderControl = true; this.sideboard.clear();
this.turnController = this.getId(); this.hand.clear();
this.turnControllers.clear(); this.graveyard.clear();
this.playersUnderYourControl.clear(); this.commandersIds.clear();
this.abilities.clear();
this.counters.clear();
this.landsPlayed = 0;
this.landsPerTurn = 1;
this.maxHandSize = 7;
this.maxAttackedBy = Integer.MAX_VALUE;
this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left
this.passed = false; this.passed = false;
this.passedTurn = false; this.passedTurn = false;
@ -448,19 +451,51 @@ public abstract class PlayerImpl implements Player, Serializable {
this.passedAllTurns = false; this.passedAllTurns = false;
this.justActivatedType = null; this.justActivatedType = null;
this.turns = 0;
this.storedBookmark = -1;
this.priorityTimeLeft = Integer.MAX_VALUE;
this.bufferTimeLeft = 0;
// reset is necessary because in tournament player will be used for each round
this.left = false;
this.quit = false;
this.timerTimeout = false;
this.idleTimeout = false;
//this.range; // must keep
this.inRange.clear();
//this.isTestMode // must keep
this.canGainLife = true; this.canGainLife = true;
this.canLoseLife = true; this.canLoseLife = true;
this.payLifeCostLevel = PayLifeCostLevel.allAbilities;
this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = true;
this.drawsOnOpponentsTurn = false;
this.sacrificeCostFilter = null;
this.alternativeSourceCosts.clear();
this.isGameUnderControl = true;
this.turnController = null;
this.turnControllers.clear();
this.playersUnderYourControl.clear();
//this.usersAllowedToSeeHandCards; // must keep
this.attachments.clear();
this.topCardRevealed = false; this.topCardRevealed = false;
this.payManaMode = false;
this.setLife(game.getStartingLife(), game, null);
this.setReachedNextTurnAfterLeaving(false);
this.reachedNextTurnAfterLeaving = false;
this.clearCastSourceIdManaCosts(); this.clearCastSourceIdManaCosts();
this.payManaMode = false;
this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left // must keep
this.phyrexianColors = null; //this.userData;
//this.matchPlayer;
this.designations.clear(); this.designations.clear();
this.phyrexianColors = null;
this.availableTriggeredManaList.clear();
} }
/** /**
@ -4246,7 +4281,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (game.getStep() == null) { // happens at the start of the game if (game.getStep() == null) { // happens at the start of the game
return true; return true;
} }
for (Entry<PhaseStep, Step.StepPart> phaseStep : silentPhaseSteps.entrySet()) { for (Entry<PhaseStep, Step.StepPart> phaseStep : SILENT_PHASES_STEPS.entrySet()) {
if (game.getPhase() != null if (game.getPhase() != null
&& game.getPhase().getStep() != null && game.getPhase().getStep() != null
&& phaseStep.getKey() == game.getPhase().getStep().getType()) { && phaseStep.getKey() == game.getPhase().getStep().getType()) {