From ac8d3de4741921c8d4d510a73b18a793075afc4f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 24 Jul 2021 14:24:17 +0400 Subject: [PATCH] * Game: tiny leaders game mode improves (fixed AI games errors, fixed commander dupes on game restart with Karn Liberated, #6113); --- .../src/mage/player/ai/SimulatedPlayer2.java | 2 + .../src/mage/player/ai/MCTSNode.java | 8 +-- .../mage/player/ai/SimulatedPlayerMCTS.java | 6 ++- .../src/mage/player/ai/ComputerPlayer2.java | 8 +-- .../src/mage/player/ai/SimulatedPlayer.java | 7 ++- .../java/mage/game/GameTinyLeadersImpl.java | 50 +++++++++++++------ .../java/mage/game/match/MatchPlayer.java | 25 ++++++++-- .../main/java/mage/players/PlayerImpl.java | 2 + 8 files changed, 75 insertions(+), 33 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index cd5b1a6430..58e2bd5714 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -15,6 +15,7 @@ import mage.constants.AbilityType; import mage.game.Game; import mage.game.combat.Combat; import mage.game.events.GameEvent; +import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; import mage.players.Player; @@ -43,6 +44,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { this.isSimulatedPlayer = isSimulatedPlayer; this.suggested = suggested; this.userData = UserData.getDefaultUserDataView(); + this.matchPlayer = new MatchPlayer(originalPlayer.getMatchPlayer(), this); } public SimulatedPlayer2(final SimulatedPlayer2 player) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java index 071c6947f7..1300780a94 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java @@ -304,11 +304,11 @@ public class MCTSNode { protected Game createSimulation(Game game, UUID playerId) { Game sim = game.copy(); - for (Player copyPlayer: sim.getState().getPlayers().values()) { - Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()).copy(); - SimulatedPlayerMCTS newPlayer = new SimulatedPlayerMCTS(copyPlayer.getId(), true); + for (Player oldPlayer: sim.getState().getPlayers().values()) { + Player origPlayer = game.getState().getPlayers().get(oldPlayer.getId()).copy(); + SimulatedPlayerMCTS newPlayer = new SimulatedPlayerMCTS(oldPlayer, true); newPlayer.restore(origPlayer); - sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); + sim.getState().getPlayers().put(oldPlayer.getId(), newPlayer); } randomizePlayers(sim, playerId); sim.setSimulation(true); diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java index dee2778f88..f887ca55c9 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java @@ -10,6 +10,7 @@ import mage.constants.Outcome; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.GameEvent; +import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; import mage.players.Player; @@ -33,9 +34,10 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { private int actionCount = 0; private static final Logger logger = Logger.getLogger(SimulatedPlayerMCTS.class); - public SimulatedPlayerMCTS(UUID id, boolean isSimulatedPlayer) { - super(id); + public SimulatedPlayerMCTS(Player originalPlayer, boolean isSimulatedPlayer) { + super(originalPlayer.getId()); this.isSimulatedPlayer = isSimulatedPlayer; + this.matchPlayer = new MatchPlayer(originalPlayer.getMatchPlayer(), this); } public SimulatedPlayerMCTS(final SimulatedPlayerMCTS player) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java index 6a6e2d642e..6feb0d0eec 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java @@ -670,11 +670,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { protected Game createSimulation(Game game) { Game sim = game.copy(); - for (Player copyPlayer: sim.getState().getPlayers().values()) { - Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()).copy(); - SimulatedPlayer newPlayer = new SimulatedPlayer(copyPlayer.getId(), copyPlayer.getId().equals(playerId), maxDepth); + for (Player oldPlayer: sim.getState().getPlayers().values()) { + Player origPlayer = game.getState().getPlayers().get(oldPlayer.getId()).copy(); + SimulatedPlayer newPlayer = new SimulatedPlayer(oldPlayer, oldPlayer.getId().equals(playerId), maxDepth); newPlayer.restore(origPlayer); - sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); + sim.getState().getPlayers().put(oldPlayer.getId(), newPlayer); } sim.setSimulation(true); return sim; diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java index f0f11fb19d..46fc0c4e6e 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java @@ -11,8 +11,10 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.game.Game; import mage.game.combat.Combat; import mage.game.events.GameEvent; +import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; +import mage.players.Player; import mage.target.Target; import org.apache.log4j.Logger; @@ -31,11 +33,12 @@ public class SimulatedPlayer extends ComputerPlayer { private static PassAbility pass = new PassAbility(); protected int maxDepth; - public SimulatedPlayer(UUID id, boolean isSimulatedPlayer, int maxDepth) { - super(id); + public SimulatedPlayer(Player originalPlayer, boolean isSimulatedPlayer, int maxDepth) { + super(originalPlayer.getId()); this.maxDepth = maxDepth; pass.setControllerId(playerId); this.isSimulatedPlayer = isSimulatedPlayer; + this.matchPlayer = new MatchPlayer(originalPlayer.getMatchPlayer(), this); } public SimulatedPlayer(final SimulatedPlayer player) { diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index 467fdf3d23..1efc6a37f8 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -15,6 +15,7 @@ import mage.constants.*; import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; +import mage.util.CardUtil; import mage.watchers.common.CommanderInfoWatcher; import mage.watchers.common.CommanderPlaysCountWatcher; @@ -50,27 +51,36 @@ public abstract class GameTinyLeadersImpl extends GameImpl { for (UUID playerId : state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); if (player != null) { - Card commander = getCommanderCard(player.getMatchPlayer().getDeck().getName(), player.getId()); + String commanderName = player.getMatchPlayer().getDeck().getName(); + Card commander = findCommander(this, player, commanderName); if (commander != null) { - Set cards = new HashSet<>(); - cards.add(commander); - this.loadCards(cards, playerId); - player.addCommanderId(commander.getId()); + // already exists - just move to zone (example: game restart by Karn Liberated) commander.moveToZone(Zone.COMMAND, null, this, true); - Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); - ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary, false, "Commander")); - ability.addEffect(new CommanderCostModification(commander)); - // Commander rule #4 was removed Jan. 18, 2016 - // ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); - CommanderInfoWatcher watcher = new CommanderInfoWatcher("Commander", commander.getId(), false); - getState().addWatcher(watcher); - watcher.addCardInfoToCommander(this); - this.getState().addAbility(ability, null); } else { - throw new UnknownError("Commander card could not be created. Name: [" + player.getMatchPlayer().getDeck().getName() + ']'); + // create new commander + commander = getCommanderCard(commanderName, player.getId()); + if (commander != null) { + Set cards = new HashSet<>(); + cards.add(commander); + this.loadCards(cards, playerId); + player.addCommanderId(commander.getId()); + commander.moveToZone(Zone.COMMAND, null, this, true); + Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects")); + ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary, false, "Commander")); + ability.addEffect(new CommanderCostModification(commander)); + // Commander rule #4 was removed Jan. 18, 2016 + // ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); + CommanderInfoWatcher watcher = new CommanderInfoWatcher("Commander", commander.getId(), false); + getState().addWatcher(watcher); + watcher.addCardInfoToCommander(this); + this.getState().addAbility(ability, null); + } else { + // TODO: can't see that error in game logs at all, wtf? See GameWorker.call + // Test use case: create tiny game with random generated deck - game freezes with empty battlefield + throw new IllegalStateException("Commander card could not be created. Name: [" + player.getMatchPlayer().getDeck().getName() + ']'); + } } } - } super.init(choosingPlayerId); if (startingPlayerSkipsDraw) { @@ -78,6 +88,14 @@ public abstract class GameTinyLeadersImpl extends GameImpl { } } + private Card findCommander(Game game, Player player, String commanderName) { + return game.getCommanderCardsFromAnyZones(player, CommanderCardType.ANY, Zone.ALL) + .stream() + .filter(c -> CardUtil.haveSameNames(c, commanderName, game)) + .findFirst() + .orElse(null); + } + /** * Name of Tiny Leader comes from the deck name (it's not in the sideboard) * Additionally, it was taken into account that WOTC had missed a few color diff --git a/Mage/src/main/java/mage/game/match/MatchPlayer.java b/Mage/src/main/java/mage/game/match/MatchPlayer.java index e7b876ceb6..be7fdb77e5 100644 --- a/Mage/src/main/java/mage/game/match/MatchPlayer.java +++ b/Mage/src/main/java/mage/game/match/MatchPlayer.java @@ -12,18 +12,15 @@ import java.io.Serializable; */ public class MatchPlayer implements Serializable { - private static final long serialVersionUID = 42L; - private int wins; private int winsNeeded; private boolean matchWinner; private Deck deck; private Player player; - private final String name; + private String name; private boolean quit; - //private final boolean timerTimeout; private boolean doneSideboarding; private int priorityTimeLeft; @@ -34,11 +31,29 @@ public class MatchPlayer implements Serializable { this.winsNeeded = match.getWinsNeeded(); this.doneSideboarding = true; this.quit = false; - //this.timerTimeout = false; this.name = player.getName(); this.matchWinner = false; } + /** + * Create match player's copy for simulated/ai games, + * so game and cards can get access to player's deck + * + * @param newPlayer + * @return + */ + public MatchPlayer(final MatchPlayer source, Player newPlayer) { + this.wins = source.wins; + this.winsNeeded = source.winsNeeded; + this.matchWinner = source.matchWinner; + this.deck = source.deck; + this.player = newPlayer; // new + this.name = newPlayer.getName(); // new + this.quit = source.quit; + this.doneSideboarding = source.doneSideboarding; + this.priorityTimeLeft = source.priorityTimeLeft; + } + public int getPriorityTimeLeft() { return priorityTimeLeft; } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 668f56fcb9..8bc341c907 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -245,6 +245,8 @@ public abstract class PlayerImpl implements Player, Serializable { this.inRange.addAll(player.inRange); this.userData = player.userData; + this.matchPlayer = player.matchPlayer; + this.canPayLifeCost = player.canPayLifeCost; this.sacrificeCostFilter = player.sacrificeCostFilter; this.alternativeSourceCosts.addAll(player.alternativeSourceCosts);