diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index f63085f73d0..478473896bd 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -116,14 +116,14 @@ public class ComputerPlayer6 extends ComputerPlayer implements } @Override - public void priority(Game game) { + public boolean priority(Game game) { logState(game); game.firePriorityEvent(playerId); switch (game.getTurn().getStepType()) { case UPKEEP: case DRAW: pass(); - break; + return false; case PRECOMBAT_MAIN: case DECLARE_BLOCKERS: case POSTCOMBAT_MAIN: @@ -134,16 +134,17 @@ public class ComputerPlayer6 extends ComputerPlayer implements calculateActions(game); } act(game); + return true; } else { pass(); } - break; + return false; case BEGIN_COMBAT: case FIRST_COMBAT_DAMAGE: case COMBAT_DAMAGE: case END_COMBAT: pass(); - break; + return false; case DECLARE_ATTACKERS: if (!game.getActivePlayerId().equals(playerId)) { printOutState(game, playerId); @@ -152,18 +153,20 @@ public class ComputerPlayer6 extends ComputerPlayer implements calculateActions(game); } act(game); + return true; //printOutState(game, playerId); } else { pass(); } - break; + return false; case END_TURN: pass(); - break; + return false; case CLEANUP: pass(); - break; + return false; } + return false; } protected void printOutState(Game game, UUID playerId) { @@ -264,11 +267,11 @@ public class ComputerPlayer6 extends ComputerPlayer implements test = root; root = root.children.get(0); } - logger.info("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); + logger.info("simlating -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue); if (!suggested.isEmpty()) { return false; } - if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue().hashCode() == test.gameValue) { + if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue(true).hashCode() == test.gameValue) { /* // Try to fix horizon effect @@ -477,7 +480,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements logger.info("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } - node.setGameValue(game.getState().getValue().hashCode()); + node.setGameValue(game.getState().getValue(true).hashCode()); SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get()); //logger.info("simulating -- player " + currentPlayer.getName()); SimulationNode2 bestNode = null; diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java index b85e1cd23c4..358acceb39e 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java @@ -83,7 +83,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { } @Override - public void priority(Game game) { + public boolean priority(Game game) { logState(game); if (logger.isDebugEnabled()) logger.debug("Game State: Turn-" + game.getTurnNum() + " Step-" + game.getTurn().getStepType() + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name); @@ -92,7 +92,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { case UPKEEP: case DRAW: pass(); - break; + return false; case PRECOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { System.out.println("Computer7:"); @@ -102,13 +102,14 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { calculatePreCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case BEGIN_COMBAT: pass(); - break; + return false; case DECLARE_ATTACKERS: if (!game.getActivePlayerId().equals(playerId)) { printOutState(game, playerId); @@ -117,16 +118,17 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { calculatePreCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case DECLARE_BLOCKERS: case FIRST_COMBAT_DAMAGE: case COMBAT_DAMAGE: case END_COMBAT: pass(); - break; + return false; case POSTCOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { printOutState(game, playerId); @@ -135,15 +137,17 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { calculatePostCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case END_TURN: case CLEANUP: pass(); - break; + return false; } + return false; } protected void calculatePreCombatActions(Game game) { 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 fa799bbe82d..3e6935f3112 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 @@ -265,7 +265,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { if (binary.charAt(j) == '1') sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, sim); } - if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) { + if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) { logger.debug("simulating -- found redundant attack combination"); } else { @@ -289,7 +289,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { //add a node with no blockers Game sim = game.copy(); - engagements.put(sim.getCombat().getValue(sim), sim.getCombat()); + engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); List blockers = getAvailableBlockers(game); @@ -310,7 +310,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) { Game sim = game.copy(); sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim); - if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) + if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) logger.debug("simulating -- found redundant block combination"); addBlocker(sim, remaining, engagements); // and recurse minus the used blocker } @@ -360,7 +360,8 @@ public class SimulatedPlayer2 extends ComputerPlayer { } @Override - public void priority(Game game) { + public boolean priority(Game game) { //should never get here + return false; } } diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index b7e0040cc36..af815a53ca7 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -79,6 +79,8 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; /** * @@ -428,7 +430,7 @@ public class ComputerPlayer> extends PlayerImpl i } @Override - public void priority(Game game) { + public boolean priority(Game game) { log.debug("priority"); UUID opponentId = game.getOpponents(playerId).iterator().next(); if (game.getActivePlayerId().equals(playerId)) { @@ -449,12 +451,12 @@ public class ComputerPlayer> extends PlayerImpl i if (ability.canActivate(playerId, game)) { if (ability.getEffects().hasOutcome(Outcome.PutLandInPlay)) { if (this.activateAbility(ability, game)) - return; + return true; } if (ability.getEffects().hasOutcome(Outcome.PutCreatureInPlay)) { if (getOpponentBlockers(opponentId, game).size() <= 1) if (this.activateAbility(ability, game)) - return; + return true; } } } @@ -476,7 +478,7 @@ public class ComputerPlayer> extends PlayerImpl i for (Card card: playableNonInstant) { if (card.getSpellAbility().canActivate(playerId, game)) { if (this.activateAbility(card.getSpellAbility(), game)) - return; + return true; } } } @@ -485,7 +487,7 @@ public class ComputerPlayer> extends PlayerImpl i if (ability.canActivate(playerId, game)) { if (!(ability.getEffects().get(0) instanceof BecomesCreatureSourceEffect)) { if (this.activateAbility(ability, game)) - return; + return true; } } } @@ -512,8 +514,20 @@ public class ComputerPlayer> extends PlayerImpl i } } pass(); + return true; } + + @Override + public boolean activateAbility(ActivatedAbility ability, Game game) { + for (Target target: ability.getModes().getMode().getTargets()) { + for (UUID targetId: target.getTargets()) { + game.fireEvent(GameEvent.getEvent(EventType.TARGETED, targetId, ability.getId(), ability.getControllerId())); + } + } + return super.activateAbility(ability, game); + } + protected void playLand(Game game) { log.debug("playLand"); Set lands = hand.getCards(new FilterLandCard(), game); @@ -699,6 +713,11 @@ public class ComputerPlayer> extends PlayerImpl i } } } + // pay phyrexian life costs + if (cost instanceof PhyrexianManaCost) { + if (cost.pay(null, game, null, playerId, false)) + return true; + } return false; } @@ -1402,7 +1421,7 @@ public class ComputerPlayer> extends PlayerImpl i for (MageObject object: list) { sb.append(object.getName()).append(","); } - log.debug(sb.toString()); + log.info(sb.toString()); } protected void logAbilityList(String message, List list) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java index 1f7a71bdb98..8462270e31e 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java @@ -30,14 +30,15 @@ package mage.player.ai; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.logging.Level; +import mage.Constants.PhaseStep; import mage.Constants.RangeOfInfluence; +import mage.Constants.Zone; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; +import mage.abilities.common.PassAbility; +import mage.cards.Card; import mage.game.Game; import mage.game.combat.Combat; import mage.game.combat.CombatGroup; @@ -51,16 +52,18 @@ import org.apache.log4j.Logger; */ public class ComputerPlayerMCTS extends ComputerPlayer implements Player { + private static final int thinkTimeRatioThreshold = 20; + protected transient MCTSNode root; - protected int thinkTime; + protected int maxThinkTime; private final static transient Logger logger = Logger.getLogger(ComputerPlayerMCTS.class); - private ExecutorService pool; + private transient ExecutorService pool; private int cores; public ComputerPlayerMCTS(String name, RangeOfInfluence range, int skill) { super(name, range); human = false; - thinkTime = skill; + maxThinkTime = (int) (skill * 1.5); cores = Runtime.getRuntime().availableProcessors(); pool = Executors.newFixedThreadPool(cores); } @@ -79,12 +82,18 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple } @Override - public void priority(Game game) { + public boolean priority(Game game) { + if (game.getStep().getType() == PhaseStep.DRAW) + logList("computer player " + name + " hand: ", new ArrayList(hand.getCards(game))); + game.firePriorityEvent(playerId); getNextAction(game, NextAction.PRIORITY); Ability ability = root.getAction(); if (ability == null) logger.fatal("null ability"); activateAbility((ActivatedAbility)ability, game); + if (ability instanceof PassAbility) + return false; + return true; } protected void calculateActions(Game game, NextAction action) { @@ -101,9 +110,13 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple protected void getNextAction(Game game, NextAction nextAction) { if (root != null) { - root = root.getMatchingState(game.getState().getValue().hashCode(), nextAction); - if (root != null) - root.emancipate(); + MCTSNode newRoot = null; + newRoot = root.getMatchingState(game.getState().getValue(false, game)); + if (newRoot != null) + newRoot.emancipate(); + else + logger.info("unable to find matching state"); + root = newRoot; } calculateActions(game, nextAction); } @@ -180,13 +193,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple @Override public void selectAttackers(Game game) { - Game sim = createMCTSGame(game); - getNextAction(sim, NextAction.SELECT_ATTACKERS); -// MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId); -// player.setNextAction(MCTSPlayer.NextAction.SELECT_ATTACKERS); -// root = new MCTSNode(sim); -// applyMCTS(); - Combat combat = root.bestChild().getCombat(); + getNextAction(game, NextAction.SELECT_ATTACKERS); + Combat combat = root.getCombat(); UUID opponentId = game.getCombat().getDefenders().iterator().next(); for (UUID attackerId: combat.getAttackers()) { this.declareAttacker(attackerId, opponentId, game); @@ -195,13 +203,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple @Override public void selectBlockers(Game game) { - Game sim = createMCTSGame(game); - getNextAction(sim, NextAction.SELECT_BLOCKERS); -// MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId); -// player.setNextAction(MCTSPlayer.NextAction.SELECT_BLOCKERS); -// root = new MCTSNode(sim); -// applyMCTS(); - Combat combat = root.bestChild().getCombat(); + getNextAction(game, NextAction.SELECT_BLOCKERS); + Combat combat = root.getCombat(); List groups = game.getCombat().getGroups(); for (int i = 0; i < groups.size(); i++) { if (i < combat.getGroups().size()) { @@ -248,36 +251,83 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple // } protected void applyMCTS(final Game game, final NextAction action) { + int thinkTime = calculateThinkTime(game, action); + long startTime = System.nanoTime(); long endTime = startTime + (thinkTime * 1000000000l); logger.info("applyMCTS - Thinking for " + (endTime - startTime)/1000000000.0 + "s"); - List tasks = new ArrayList(); - for (int i = 0; i < cores; i++) { - Game sim = createMCTSGame(game); - MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId); - player.setNextAction(action); - MCTSExecutor exec = new MCTSExecutor(sim, playerId, thinkTime); - tasks.add(exec); + if (thinkTime > 0) { + List tasks = new ArrayList(); + for (int i = 0; i < cores; i++) { + Game sim = createMCTSGame(game); + MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId); + player.setNextAction(action); + MCTSExecutor exec = new MCTSExecutor(sim, playerId, thinkTime); + tasks.add(exec); + } + + try { + pool.invokeAll(tasks); + } catch (InterruptedException ex) { + logger.warn("applyMCTS interrupted"); + } + + for (MCTSExecutor task: tasks) { + root.merge(task.getRoot()); + task.clear(); + } + tasks.clear(); + + logger.info("Created " + root.getNodeCount() + " nodes - size: " + root.size()); + displayMemory(); } - - try { - pool.invokeAll(tasks); - } catch (InterruptedException ex) { - logger.warn("applyMCTS interrupted"); - } - - for (MCTSExecutor task: tasks) { - root.merge(task.getRoot()); - } - - logger.info("Created " + root.getNodeCount() + " nodes"); + +// root.print(1); return; } + //try to ensure that there are at least 20 simulations per node at all times + private int calculateThinkTime(Game game, NextAction action) { + int thinkTime = 0; + int nodeSizeRatio = 0; + if (root.getNumChildren() > 0) + nodeSizeRatio = root.size() / root.getNumChildren(); + logger.info("Ratio: " + nodeSizeRatio); + PhaseStep curStep = game.getStep().getType(); + if (action == NextAction.SELECT_ATTACKERS || action == NextAction.SELECT_BLOCKERS) { + if (nodeSizeRatio < thinkTimeRatioThreshold) { + thinkTime = maxThinkTime; + } + else { + thinkTime = maxThinkTime / 2; + } + } + else if (game.getActivePlayerId().equals(playerId) && (curStep == PhaseStep.PRECOMBAT_MAIN || curStep == PhaseStep.POSTCOMBAT_MAIN)) { + if (nodeSizeRatio < thinkTimeRatioThreshold) { + thinkTime = maxThinkTime; + } + else { + thinkTime = maxThinkTime / 2; + } + } + else { + if (nodeSizeRatio < thinkTimeRatioThreshold) { + thinkTime = maxThinkTime / 2; + } + else { + thinkTime = 0; + } + } + return thinkTime; + } + /** * Copies game and replaces all players in copy with mcts players * Shuffles each players library so that there is no knowledge of its order + * Swaps all other players hands with random cards from the library so that + * there is no knowledge of what cards are in opponents hands + * The most knowledge that is known is what cards are in an opponents deck * * @param game * @return a new game object with simulated players @@ -289,11 +339,35 @@ public class ComputerPlayerMCTS extends ComputerPlayer imple Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()); MCTSPlayer newPlayer = new MCTSPlayer(copyPlayer.getId()); newPlayer.restore(origPlayer); - newPlayer.getLibrary().shuffle(); + if (!newPlayer.getId().equals(playerId)) { + int handSize = newPlayer.getHand().size(); + newPlayer.getLibrary().addAll(newPlayer.getHand().getCards(mcts), mcts); + newPlayer.getHand().clear(); + newPlayer.getLibrary().shuffle(); + for (int i = 0; i < handSize; i++) { + Card card = newPlayer.getLibrary().removeFromTop(mcts); + mcts.setZone(card.getId(), Zone.HAND); + newPlayer.getHand().add(card); + } + } + else { + newPlayer.getLibrary().shuffle(); + } mcts.getState().getPlayers().put(copyPlayer.getId(), newPlayer); } mcts.setSimulation(true); + mcts.resume(); return mcts; } + protected void displayMemory() { + long heapSize = Runtime.getRuntime().totalMemory(); + long heapMaxSize = Runtime.getRuntime().maxMemory(); + long heapFreeSize = Runtime.getRuntime().freeMemory(); + long heapUsedSize = heapSize - heapFreeSize; + long mb = 1024 * 1024; + + logger.info("Max heap size: " + heapMaxSize/mb + " Heap size: " + heapSize/mb + " Used: " + heapUsedSize/mb); + } + } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSExecutor.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSExecutor.java index c2974b94b9c..7082daf098e 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSExecutor.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSExecutor.java @@ -57,21 +57,22 @@ public class MCTSExecutor implements Callable { long endTime = startTime + (thinkTime * 1000000000l); MCTSNode current; - if (root.getNumChildren() == 1) - //there is only one possible action - return true; +// if (root.getNumChildren() == 1) +// //there is only one possible action - don't spend a lot of time thinking +// endTime = startTime + 1000000000l; // logger.info("applyMCTS - Thinking for " + (endTime - startTime)/1000000000.0 + "s"); while (true) { long currentTime = System.nanoTime(); // logger.info("Remaining time: " + (endTime - currentTime)/1000000000.0 + "s"); if (currentTime > endTime) +// if (root.getNodeCount() > 50) break; current = root; // Selection while (!current.isLeaf()) { - current = current.select(); + current = current.select(this.playerId); } int result; @@ -79,17 +80,17 @@ public class MCTSExecutor implements Callable { // Expansion current.expand(); - if (current == root && current.getNumChildren() == 1) - //there is only one possible action - return true; +// if (current == root && current.getNumChildren() == 1) +// //there is only one possible action - don't spend a lot of time thinking +// endTime = startTime + 1000000000l; // Simulation - current = current.select(); + current = current.select(this.playerId); result = current.simulate(this.playerId); simCount++; } else { - result = current.isWinner(this.playerId)?1:0; + result = current.isWinner(this.playerId)?1:-1; } // Backpropagation current.backpropagate(result); @@ -103,4 +104,8 @@ public class MCTSExecutor implements Callable { return root; } + public void clear() { + root = null; + } + } 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 693c5bece98..578c18176e0 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 @@ -27,14 +27,18 @@ */ package mage.player.ai; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.UUID; import mage.Constants.PhaseStep; +import mage.Constants.Zone; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; +import mage.abilities.PlayLandAbility; +import mage.abilities.common.PassAbility; +import mage.cards.Card; import mage.game.Game; -import mage.game.GameState; import mage.game.combat.Combat; import mage.game.combat.CombatGroup; import mage.game.turn.Step.StepPart; @@ -48,7 +52,8 @@ import org.apache.log4j.Logger; */ public class MCTSNode { - private static final double selectionCoefficient = 1; + private static final double selectionCoefficient = 1.0; + private static final double passRatioTolerance = 0.01; private final static transient Logger logger = Logger.getLogger(MCTSNode.class); private int visits = 0; @@ -56,68 +61,76 @@ public class MCTSNode { private MCTSNode parent; private List children = new ArrayList(); private Ability action; - private Combat combat; +// private Combat combat; private Game game; - private int stateValue; + private String stateValue; + private UUID playerId; private static int nodeCount; public MCTSNode(Game game) { this.game = game; - this.stateValue = game.getState().getValue().hashCode(); + this.stateValue = game.getState().getValue(false, game); + setPlayer(); nodeCount = 1; } - protected MCTSNode(MCTSNode parent, Game game, int state, Ability action) { + protected MCTSNode(MCTSNode parent, Game game, Ability action) { this.game = game; - this.stateValue = state; + this.stateValue = game.getState().getValue(false, game); this.parent = parent; this.action = action; + setPlayer(); nodeCount++; } - protected MCTSNode(MCTSNode parent, Game game, int state, Combat combat) { + protected MCTSNode(MCTSNode parent, Game game) { this.game = game; - this.stateValue = state; + this.stateValue = game.getState().getValue(false, game); this.parent = parent; - this.combat = combat; +// this.combat = game.getCombat(); + setPlayer(); nodeCount++; } - public MCTSNode select() { + private void setPlayer() { + if (game.getStep().getStepPart() == StepPart.PRIORITY) + playerId = game.getPriorityPlayerId(); + else { + if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS) + playerId = game.getCombat().getDefenders().iterator().next(); + else + playerId = game.getActivePlayerId(); + } + } + + public MCTSNode select(UUID targetPlayerId) { double bestValue = Double.NEGATIVE_INFINITY; + boolean isTarget = playerId.equals(targetPlayerId); MCTSNode bestChild = null; -// logger.info("start select"); if (children.size() == 1) { return children.get(0); } for (MCTSNode node: children) { double uct; if (node.visits > 0) - uct = (node.wins / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0))); + if (isTarget) + uct = (node.wins / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0))); + else + uct = ((node.visits - node.wins) / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0))); else // ensure that a random unvisited node is played first uct = 10000 + 1000 * Math.random(); -// logger.info("uct: " + uct); if (uct > bestValue) { bestChild = node; bestValue = uct; } } -// logger.info("stop select"); return bestChild; } public void expand() { - MCTSPlayer player; - if (game.getStep().getStepPart() == StepPart.PRIORITY) - player = (MCTSPlayer) game.getPlayer(game.getPriorityPlayerId()); - else { - if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS) - player = (MCTSPlayer) game.getPlayer(game.getCombat().getDefenders().iterator().next()); - else - player = (MCTSPlayer) game.getPlayer(game.getActivePlayerId()); - } + MCTSPlayer player = (MCTSPlayer) game.getPlayer(playerId); if (player.getNextAction() == null) { logger.fatal("next action is null"); } @@ -127,12 +140,12 @@ public class MCTSNode { List abilities = player.getPlayableOptions(game); for (Ability ability: abilities) { Game sim = game.copy(); - int simState = sim.getState().getValue().hashCode(); +// String simState = sim.getState().getValue(false, sim); // logger.info("expand " + ability.toString()); MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId()); simPlayer.activateAbility((ActivatedAbility)ability, sim); sim.resume(); - children.add(new MCTSNode(this, sim, simState, ability)); + children.add(new MCTSNode(this, sim, ability)); } break; case SELECT_ATTACKERS: @@ -141,13 +154,13 @@ public class MCTSNode { UUID defenderId = game.getOpponents(player.getId()).iterator().next(); for (List attack: attacks) { Game sim = game.copy(); - int simState = sim.getState().getValue().hashCode(); +// String simState = sim.getState().getValue(false, sim); MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId()); for (UUID attackerId: attack) { simPlayer.declareAttacker(attackerId, defenderId, sim); } sim.resume(); - children.add(new MCTSNode(this, sim, simState, sim.getCombat())); + children.add(new MCTSNode(this, sim)); } break; case SELECT_BLOCKERS: @@ -155,7 +168,7 @@ public class MCTSNode { List>> blocks = player.getBlocks(game); for (List> block: blocks) { Game sim = game.copy(); - int simState = sim.getState().getValue().hashCode(); +// String simState = sim.getState().getValue(false, sim); MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId()); List groups = sim.getCombat().getGroups(); for (int i = 0; i < groups.size(); i++) { @@ -166,18 +179,18 @@ public class MCTSNode { } } sim.resume(); - children.add(new MCTSNode(this, sim, simState, sim.getCombat())); + children.add(new MCTSNode(this, sim)); } break; } } public int simulate(UUID playerId) { - long startTime = System.nanoTime(); - Game sim = createSimulation(game); +// long startTime = System.nanoTime(); + Game sim = createSimulation(game, playerId); sim.resume(); - long duration = System.nanoTime() - startTime; - int retVal = 0; +// long duration = System.nanoTime() - startTime; + int retVal = 0; //anything other than a win is a loss for (Player simPlayer: sim.getPlayers().values()) { // logger.info(simPlayer.getName() + " calculated " + ((SimulatedPlayerMCTS)simPlayer).getActionCount() + " actions in " + duration/1000000000.0 + "s"); if (simPlayer.getId().equals(playerId) && simPlayer.hasWon()) { @@ -201,12 +214,34 @@ public class MCTSNode { } public MCTSNode bestChild() { + if (children.size() == 1) + return children.get(0); double bestCount = -1; + double bestRatio = 0; + boolean bestIsPass = false; MCTSNode bestChild = null; for (MCTSNode node: children) { + //favour passing vs any other action except for playing land if ratio is close if (node.visits > bestCount) { + if (bestIsPass) { + double ratio = node.wins/(node.visits * 1.0); + if (ratio < bestRatio + passRatioTolerance) + continue; + } bestChild = node; bestCount = node.visits; + bestRatio = node.wins/(node.visits * 1.0); + bestIsPass = false; + } + else if (node.action instanceof PassAbility && node.visits > 10 && !(bestChild.action instanceof PlayLandAbility)) { + //favour passing vs any other action if ratio is close + double ratio = node.wins/(node.visits * 1.0); + if (ratio > bestRatio - passRatioTolerance) { + bestChild = node; + bestCount = node.visits; + bestRatio = ratio; + bestIsPass = true; + } } } return bestChild; @@ -229,13 +264,17 @@ public class MCTSNode { } public Combat getCombat() { - return combat; + return game.getCombat(); } public int getNodeCount() { return nodeCount; } + public String getStateValue() { + return stateValue; + } + /** * Copies game and replaces all players in copy with simulated players * Shuffles each players library so that there is no knowledge of its order @@ -243,14 +282,27 @@ public class MCTSNode { * @param game * @return a new game object with simulated players */ - protected Game createSimulation(Game game) { + 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); newPlayer.restore(origPlayer); - newPlayer.shuffleLibrary(sim); + if (!newPlayer.getId().equals(playerId)) { + int handSize = newPlayer.getHand().size(); + newPlayer.getLibrary().addAll(newPlayer.getHand().getCards(sim), sim); + newPlayer.getHand().clear(); + newPlayer.getLibrary().shuffle(); + for (int i = 0; i < handSize; i++) { + Card card = newPlayer.getLibrary().removeFromTop(sim); + sim.setZone(card.getId(), Zone.HAND); + newPlayer.getHand().add(card); + } + } + else { + newPlayer.getLibrary().shuffle(); + } sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); } sim.setSimulation(true); @@ -268,29 +320,35 @@ public class MCTSNode { return false; } - public MCTSNode getMatchingState(int state, NextAction nextAction) { - for (MCTSNode node: children) { - if (node.stateValue == state && node.action != null) { - MCTSPlayer player; - if (game.getStep().getStepPart() == StepPart.PRIORITY) - player = (MCTSPlayer) game.getPlayer(game.getPriorityPlayerId()); - else { - if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS) - player = (MCTSPlayer) game.getPlayer(game.getCombat().getDefenders().iterator().next()); - else - player = (MCTSPlayer) game.getPlayer(game.getActivePlayerId()); - } - if (player.getNextAction() == nextAction) - return node; + /** + * + * performs a breadth first search for a matching game state + * + * @param state - the game state that we are looking for + * @param nextAction - the next action that will be performed + * @return the matching state or null if no match is found + */ + public MCTSNode getMatchingState(String state) { + ArrayDeque queue = new ArrayDeque(); + queue.add(this); + + while (!queue.isEmpty()) { + MCTSNode current = queue.remove(); + if (current.stateValue.equals(state)) + return current; + for (MCTSNode child: current.children) { + queue.add(child); } - MCTSNode match = node.getMatchingState(state, nextAction); - if (match != null) - return node; } return null; } - + public void merge(MCTSNode merge) { + if (!stateValue.equals(merge.stateValue)) { + logger.info("mismatched merge states"); + return; + } + this.visits += merge.visits; this.wins += merge.wins; @@ -301,16 +359,61 @@ public class MCTSNode { for (MCTSNode child: children) { for (MCTSNode mergeChild: mergeChildren) { - if (mergeChild.stateValue == child.stateValue) { - child.merge(mergeChild); - mergeChildren.remove(mergeChild); - break; + if (mergeChild.action != null && child.action != null) { + if (mergeChild.action.toString().equals(child.action.toString())) { + if (!mergeChild.stateValue.equals(child.stateValue)) { + logger.info("mismatched merge states"); + mergeChildren.remove(mergeChild); + } + else { + child.merge(mergeChild); + mergeChildren.remove(mergeChild); + } + break; + } + } + else { + if (mergeChild.game.getCombat().getValue().equals(child.game.getCombat().getValue())) { + if (!mergeChild.stateValue.equals(child.stateValue)) { + logger.info("mismatched merge states"); + mergeChildren.remove(mergeChild); + } + else { + child.merge(mergeChild); + mergeChildren.remove(mergeChild); + } + break; + } } } } if (!mergeChildren.isEmpty()) { - children.addAll(mergeChildren); + for (MCTSNode child: mergeChildren) { + child.parent = this; + children.add(child); + } } } + + public void print(int depth) { + String indent = String.format("%1$-" + depth + "s", ""); + StringBuilder sb = new StringBuilder(); + MCTSPlayer player = (MCTSPlayer) game.getPlayer(playerId); + sb.append(indent).append(player.getName()).append(" ").append(visits).append(":").append(wins).append(" - "); + if (action != null) + sb.append(action.toString()); + System.out.println(sb.toString()); + for (MCTSNode child: children) { + child.print(depth + 1); + } + } + + public int size() { + int num = 1; + for (MCTSNode child: children) { + num += child.size(); + } + return num; + } } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java index ef9f94e245a..24e482a8166 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java @@ -201,10 +201,11 @@ public class MCTSPlayer extends ComputerPlayer { } @Override - public void priority(Game game) { + public boolean priority(Game game) { // logger.info("Paused for Priority for player:" + getName()); game.pause(); nextAction = NextAction.PRIORITY; + return false; } // @Override 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 0be830f21a9..278b1a18e01 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 @@ -42,6 +42,7 @@ import mage.abilities.Mode; import mage.abilities.Modes; import mage.abilities.TriggeredAbilities; import mage.abilities.TriggeredAbility; +import mage.abilities.common.PassAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -56,6 +57,7 @@ import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.TargetAmount; @@ -99,11 +101,24 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { } @Override - public void priority(Game game) { + public boolean priority(Game game) { // logger.info("priority"); + boolean didSomething = false; + Ability ability = getAction(game); +// logger.info("simulate " + ability.toString()); + if (!(ability instanceof PassAbility)) + didSomething = true; + + activateAbility((ActivatedAbility) ability, game); + + actionCount++; + return didSomething; + } + + private Ability getAction(Game game) { + List playables = getPlayableAbilities(game); + Ability ability; while (true) { - List playables = getPlayableAbilities(game); - Ability ability; if (playables.size() == 1) ability = playables.get(0); else @@ -120,13 +135,23 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { if (amount > 0) ability.addManaCost(new GenericManaCost(rnd.nextInt(amount))); } -// logger.info("simulate " + ability.toString()); - activateAbility((ActivatedAbility) ability, game); - - actionCount++; - if (ability.isUsesStack()) + // check if ability kills player, if not then it's ok to play +// if (ability.isUsesStack()) { +// Game testSim = game.copy(); +// activateAbility((ActivatedAbility) ability, testSim); +// StackObject testAbility = testSim.getStack().pop(); +// testAbility.resolve(testSim); +// testSim.applyEffects(); +// testSim.checkStateAndTriggered(); +// if (!testSim.getPlayer(playerId).hasLost()) { +// break; +// } +// } +// else { break; +// } } + return ability; } @Override 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 bf1beceb819..d6f25af6266 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 @@ -131,14 +131,14 @@ public class ComputerPlayer2 extends ComputerPlayer implements } @Override - public void priority(Game game) { + public boolean priority(Game game) { logState(game); game.firePriorityEvent(playerId); switch (game.getTurn().getStepType()) { case UPKEEP: case DRAW: pass(); - break; + return false; case PRECOMBAT_MAIN: case BEGIN_COMBAT: case DECLARE_ATTACKERS: @@ -151,12 +151,13 @@ public class ComputerPlayer2 extends ComputerPlayer implements calculateActions(game); } act(game); - break; + return true; case END_TURN: case CLEANUP: pass(); - break; + return false; } + return false; } protected void act(Game game) { @@ -210,8 +211,8 @@ public class ComputerPlayer2 extends ComputerPlayer implements test = root; root = root.children.get(0); } - logger.debug("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); - if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue().hashCode() == test.gameValue) { + logger.debug("simlating -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue); + if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue(true).hashCode() == test.gameValue) { logger.debug("simulating -- continuing previous action chain"); actions = new LinkedList(root.abilities); combat = root.combat; @@ -412,7 +413,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - node.setGameValue(game.getState().getValue().hashCode()); + node.setGameValue(game.getState().getValue(true).hashCode()); SimulatedPlayer currentPlayer = (SimulatedPlayer) game.getPlayer(game.getPlayerList().get()); boolean isSimulatedPlayer = currentPlayer.getId().equals(playerId); logger.debug(indent(node.depth) + "simulating priority -- player " + currentPlayer.getName()); diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java index f44232a1d8b..83c9d01e0fa 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java @@ -96,7 +96,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } @Override - public void priority(Game game) { + public boolean priority(Game game) { logState(game); if (logger.isDebugEnabled()) logger.debug("Game State: Turn-" + game.getTurnNum() + " Step-" + game.getTurn().getStepType() + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name); @@ -105,51 +105,55 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { case UPKEEP: case DRAW: pass(); - break; + return false; case PRECOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { if (actions.size() == 0) { calculatePreCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case BEGIN_COMBAT: pass(); - break; + return false; case DECLARE_ATTACKERS: if (!game.getActivePlayerId().equals(playerId)) { if (actions.size() == 0) { calculatePreCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case DECLARE_BLOCKERS: case FIRST_COMBAT_DAMAGE: case COMBAT_DAMAGE: case END_COMBAT: pass(); - break; + return false; case POSTCOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { if (actions.size() == 0) { calculatePostCombatActions(game); } act(game); + return true; } else pass(); - break; + return false; case END_TURN: case CLEANUP: pass(); - break; + return false; } + return false; } protected void calculatePreCombatActions(Game game) { 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 e9bb049c604..e795d96a575 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 @@ -188,7 +188,7 @@ public class SimulatedPlayer extends ComputerPlayer { if (binary.charAt(j) == '1') sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, sim); } - if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) { + if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) { logger.debug("simulating -- found redundant attack combination"); } else if (logger.isDebugEnabled()) { @@ -205,7 +205,7 @@ public class SimulatedPlayer extends ComputerPlayer { //add a node with no blockers Game sim = game.copy(); - engagements.put(sim.getCombat().getValue(sim), sim.getCombat()); + engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); List blockers = getAvailableBlockers(game); @@ -227,7 +227,7 @@ public class SimulatedPlayer extends ComputerPlayer { if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) { Game sim = game.copy(); sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim); - if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) + if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) logger.debug("simulating -- found redundant block combination"); addBlocker(sim, remaining, engagements); // and recurse minus the used blocker } @@ -277,8 +277,9 @@ public class SimulatedPlayer extends ComputerPlayer { } @Override - public void priority(Game game) { + public boolean priority(Game game) { //should never get here + return false; } protected String indent(int num) { diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 9b90df3b3ec..5f7f63fc850 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -374,20 +374,22 @@ public class HumanPlayer extends PlayerImpl { } @Override - public void priority(Game game) { + public boolean priority(Game game) { passed = false; if (!abort) { if (passedTurn && game.getStack().isEmpty()) { pass(); - return; + return false; } game.firePriorityEvent(playerId); waitForResponse(); if (response.getBoolean() != null) { pass(); + return false; } else if (response.getInteger() != null) { pass(); passedTurn = true; + return false; } else if (response.getString() != null && response.getString().equals("special")) { specialAction(game); } else if (response.getUUID() != null) { @@ -403,7 +405,9 @@ public class HumanPlayer extends PlayerImpl { } } } + return true; } + return false; } @Override diff --git a/Mage.Server/plugins/mage-player-ai-ma.jar b/Mage.Server/plugins/mage-player-ai-ma.jar index c2468a1b93c..b3bc0b4151f 100644 Binary files a/Mage.Server/plugins/mage-player-ai-ma.jar and b/Mage.Server/plugins/mage-player-ai-ma.jar differ diff --git a/Mage.Server/plugins/mage-player-ai.jar b/Mage.Server/plugins/mage-player-ai.jar index 4eab33a7589..c17e2480315 100644 Binary files a/Mage.Server/plugins/mage-player-ai.jar and b/Mage.Server/plugins/mage-player-ai.jar differ diff --git a/Mage.Server/plugins/mage-player-aimcts.jar b/Mage.Server/plugins/mage-player-aimcts.jar index 5cc37f6447d..65749101e25 100644 Binary files a/Mage.Server/plugins/mage-player-aimcts.jar and b/Mage.Server/plugins/mage-player-aimcts.jar differ diff --git a/Mage.Server/plugins/mage-player-aiminimax.jar b/Mage.Server/plugins/mage-player-aiminimax.jar index be792e4a2dd..a386de17138 100644 Binary files a/Mage.Server/plugins/mage-player-aiminimax.jar and b/Mage.Server/plugins/mage-player-aiminimax.jar differ diff --git a/Mage.Server/plugins/mage-player-human.jar b/Mage.Server/plugins/mage-player-human.jar index e96938bf36e..8c88d8c21b2 100644 Binary files a/Mage.Server/plugins/mage-player-human.jar and b/Mage.Server/plugins/mage-player-human.jar differ diff --git a/Mage.Sets/src/mage/sets/newphyrexia/PhyrexianMetamorph.java b/Mage.Sets/src/mage/sets/newphyrexia/PhyrexianMetamorph.java index ef5011a6121..a896de5b7df 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/PhyrexianMetamorph.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/PhyrexianMetamorph.java @@ -96,36 +96,37 @@ class PhyrexianMetamorphEffect extends ContinuousEffectImpl { - boolean used = false; +// boolean used = false; public DiesTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, Zone.GRAVEYARD, effect, "When {this} dies, ", optional); @@ -59,18 +59,18 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility { public PassAbility() { super(Zone.ALL, new PassEffect()); + this.usesStack = false; } public PassAbility(final PassAbility ability) { diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 25d08ffdf93..6be7c09078d 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -139,10 +139,10 @@ public abstract class GameImpl> implements Game, Serializa this.attackOption = game.attackOption; this.state = game.state.copy(); // Issue 350 - //this.gameCards = game.gameCards; - for (Map.Entry entry: game.gameCards.entrySet()) { - this.gameCards.put(entry.getKey(), entry.getValue().copy()); - } + this.gameCards = game.gameCards; +// for (Map.Entry entry: game.gameCards.entrySet()) { +// this.gameCards.put(entry.getKey(), entry.getValue().copy()); +// } this.simulation = game.simulation; this.gameOptions = game.gameOptions; this.lki.putAll(game.lki); @@ -386,13 +386,14 @@ public abstract class GameImpl> implements Game, Serializa public void resume() { PlayerList players = state.getPlayerList(state.getActivePlayerId()); Player player = getPlayer(players.get()); + boolean wasPaused = state.isPaused(); state.resume(); if (!isGameOver()) { // if (simulation) // logger.info("Turn " + Integer.toString(state.getTurnNum())); fireInformEvent("Turn " + Integer.toString(state.getTurnNum())); if (checkStopOnTurnOption()) return; - state.getTurn().resumePlay(this); + state.getTurn().resumePlay(this, wasPaused); if (!isPaused() && !isGameOver()) { endOfTurn(); player = players.getNext(this); @@ -623,12 +624,13 @@ public abstract class GameImpl> implements Game, Serializa checkStateAndTriggered(); if (isPaused() || isGameOver()) return; // resetPassed should be called if player performs any action - player.priority(this); + if (player.priority(this)) + applyEffects(); if (isPaused()) return; } resuming = false; - applyEffects(); } + resuming = false; if (isPaused() || isGameOver()) return; if (allPassed()) { if (!state.getStack().isEmpty()) { @@ -781,7 +783,7 @@ public abstract class GameImpl> implements Game, Serializa for (Permanent perm: getBattlefield().getAllActivePermanents()) { if (perm.getCardType().contains(CardType.CREATURE)) { //20091005 - 704.5f - if (perm.getToughness().getValue() == 0) { + if (perm.getToughness().getValue() <= 0) { if (perm.moveToZone(Zone.GRAVEYARD, null, this, false)) { somethingHappened = true; continue; diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index ff5bdda0c56..104f7c6419e 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -32,7 +32,10 @@ import mage.abilities.TriggeredAbility; import mage.game.events.GameEvent; import mage.game.stack.SpellStack; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import mage.Constants.Zone; @@ -42,10 +45,13 @@ import mage.abilities.Ability; import mage.abilities.ActivatedAbility; import mage.abilities.DelayedTriggeredAbilities; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Mode; import mage.abilities.SpecialActions; import mage.abilities.TriggeredAbilities; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffects; +import mage.cards.Card; +import mage.choices.Choice; import mage.game.combat.Combat; import mage.game.combat.CombatGroup; import mage.game.command.Command; @@ -60,6 +66,7 @@ import mage.game.turn.TurnMods; import mage.players.Player; import mage.players.PlayerList; import mage.players.Players; +import mage.target.Target; import mage.util.Copyable; import mage.watchers.Watchers; @@ -146,6 +153,7 @@ public class GameState implements Serializable, Copyable { for (Map.Entry> entry: state.otherAbilities.entrySet()) { otherAbilities.put(entry.getKey(), entry.getValue().copy()); } + this.paused = state.paused; } @Override @@ -158,34 +166,100 @@ public class GameState implements Serializable, Copyable { playerList.add(player.getId()); } - public String getValue() { + public String getValue(boolean useHidden) { StringBuilder sb = new StringBuilder(1024); sb.append(turnNum).append(turn.getPhaseType()).append(turn.getStepType()).append(activePlayerId).append(priorityPlayerId); for (Player player: players.values()) { - sb.append("player").append(player.getLife()).append("hand").append(player.getHand()).append("library").append(player.getLibrary().size()).append("graveyard").append(player.getGraveyard()); + sb.append("player").append(player.getLife()).append("hand"); + if (useHidden) + sb.append(player.getHand()); + else + sb.append(player.getHand().size()); + sb.append("library").append(player.getLibrary().size()).append("graveyard").append(player.getGraveyard()); } - for (UUID permanentId: battlefield.getAllPermanentIds()) { - sb.append("permanent").append(permanentId); + sb.append("permanents"); + for (Permanent permanent: battlefield.getAllPermanents()) { + sb.append(permanent.getValue()); } + sb.append("spells"); for (StackObject spell: stack) { - sb.append("spell").append(spell.getId()); + sb.append(spell.getControllerId()).append(spell.getName()); } for (ExileZone zone: exile.getExileZones()) { sb.append("exile").append(zone.getName()).append(zone); } + sb.append("combat"); for (CombatGroup group: combat.getGroups()) { - sb.append("combat").append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers()); + sb.append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers()); } return sb.toString(); } + public String getValue(boolean useHidden, Game game) { + StringBuilder sb = new StringBuilder(1024); + + sb.append(turnNum).append(turn.getPhaseType()).append(turn.getStepType()).append(activePlayerId).append(priorityPlayerId); + + for (Player player: players.values()) { + sb.append("player").append(player.isPassed()).append(player.getLife()).append("hand"); + if (useHidden) + sb.append(player.getHand()); + else + sb.append(player.getHand().size()); + sb.append("library").append(player.getLibrary().size()); + sb.append("graveyard"); + for (Card card: player.getGraveyard().getCards(game)) { + sb.append(card.getName()); + } + } + + sb.append("permanents"); + List perms = new ArrayList(); + for (Permanent permanent: battlefield.getAllPermanents()) { + perms.add(permanent.getValue()); + } + Collections.sort(perms); + sb.append(perms); + + sb.append("spells"); + for (StackObject spell: stack) { + sb.append(spell.getControllerId()).append(spell.getName()); + sb.append(spell.getStackAbility().toString()); + for (Mode mode: spell.getStackAbility().getModes().values()) { + if (!mode.getTargets().isEmpty()) { + sb.append("targets"); + for (Target target: mode.getTargets()) { + sb.append(target.getTargets()); + } + } + if (!mode.getChoices().isEmpty()) { + sb.append("choices"); + for (Choice choice: mode.getChoices()) { + sb.append(choice.getChoice()); + } + } + } + } + + for (ExileZone zone: exile.getExileZones()) { + sb.append("exile").append(zone.getName()).append(zone); + } + + sb.append("combat"); + for (CombatGroup group: combat.getGroups()) { + sb.append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers()); + } + + return sb.toString(); + } + public Players getPlayers() { return players; } diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index a0604c105e0..8bbd339ecf2 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -107,13 +107,13 @@ public class Combat implements Serializable, Copyable { attackerId = null; } - public int getValue(Game game) { + public String getValue() { StringBuilder sb = new StringBuilder(); sb.append(attackerId).append(defenders); for (CombatGroup group : groups) { - sb.append(group.getValue(game)); + sb.append(group.defenderId).append(group.attackers).append(group.attackerOrder).append(group.blockers).append(group.blockerOrder); } - return sb.toString().hashCode(); + return sb.toString(); } public void setAttacker(UUID playerId) { @@ -128,16 +128,21 @@ public class Combat implements Serializable, Copyable { player.selectAttackers(game); if (game.isPaused() || game.isGameOver()) return; - for (CombatGroup group: groups) { - for (UUID attacker: group.getAttackers()) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId)); - } - } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); - game.fireInformEvent(player.getName() + " attacks with " + groups.size() + " creatures"); + resumeSelectAttackers(game); } } - + + public void resumeSelectAttackers(Game game) { + Player player = game.getPlayer(attackerId); + for (CombatGroup group: groups) { + for (UUID attacker: group.getAttackers()) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId)); + } + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); + game.fireInformEvent(player.getName() + " attacks with " + groups.size() + " creatures"); + } + protected void checkAttackRequirements(Player player, Game game) { //20101001 - 508.1d for (Permanent creature : player.getAvailableAttackers(game)) { @@ -176,6 +181,13 @@ public class Combat implements Serializable, Copyable { } } + public void resumeSelectBlockers(Game game) { + //TODO: this isn't quite right - but will work fine for two-player games + for (UUID defenderId : getPlayerDefenders(game)) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); + } + } + protected void checkBlockRequirements(Player player, Game game) { //20101001 - 509.1c //TODO: handle case where more than one attacker must be blocked diff --git a/Mage/src/mage/game/combat/CombatGroup.java b/Mage/src/mage/game/combat/CombatGroup.java index 9c050ef327f..9fa6453e800 100644 --- a/Mage/src/mage/game/combat/CombatGroup.java +++ b/Mage/src/mage/game/combat/CombatGroup.java @@ -74,21 +74,21 @@ public class CombatGroup implements Serializable, Copyable { this.players.putAll(group.players); } - protected String getValue(Game game) { - StringBuilder sb = new StringBuilder(1024); - for (UUID attackerId: attackers) { - getPermanentValue(attackerId, sb, game); - } - for (UUID blockerId: blockers) { - getPermanentValue(blockerId, sb, game); - } - return sb.toString(); - } - - private void getPermanentValue(UUID permId, StringBuilder sb, Game game) { - Permanent perm = game.getPermanent(permId); - sb.append(perm.getValue()); - } +// protected String getValue(Game game) { +// StringBuilder sb = new StringBuilder(1024); +// for (UUID attackerId: attackers) { +// getPermanentValue(attackerId, sb, game); +// } +// for (UUID blockerId: blockers) { +// getPermanentValue(blockerId, sb, game); +// } +// return sb.toString(); +// } +// +// private void getPermanentValue(UUID permId, StringBuilder sb, Game game) { +// Permanent perm = game.getPermanent(permId); +// sb.append(perm.getValue()); +// } public boolean hasFirstOrDoubleStrike(Game game) { for (UUID permId: attackers) { diff --git a/Mage/src/mage/game/permanent/PermanentCard.java b/Mage/src/mage/game/permanent/PermanentCard.java index 8265fbd3e39..878dae3217a 100644 --- a/Mage/src/mage/game/permanent/PermanentCard.java +++ b/Mage/src/mage/game/permanent/PermanentCard.java @@ -79,7 +79,7 @@ public class PermanentCard extends PermanentImpl { public PermanentCard(final PermanentCard permanent) { super(permanent); - this.card = permanent.card; + this.card = permanent.card.copy(); this.maxLevelCounters = permanent.maxLevelCounters; } diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index cb95e4d4e18..a9a3d771b0e 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -152,6 +152,9 @@ public abstract class PermanentImpl> extends CardImpl sb.append(controllerId).append(name).append(tapped).append(damage); sb.append(subtype).append(supertype).append(power.getValue()).append(toughness.getValue()); sb.append(abilities); + for (Counter counter: counters.values()) { + sb.append(counter.getName()).append(counter.getCount()); + } return sb.toString(); } diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index cbb2c71f8ba..6efc4606cf7 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -182,21 +182,21 @@ public class Spell> implements StackObject, Card { String name = null; if (object == null) { Player targetPlayer = game.getPlayer(targetId); - if (player != null) name = targetPlayer.getName(); + if (targetPlayer != null) name = targetPlayer.getName(); } else { name = object.getName(); } if (name != null && player.chooseUse(ability.getEffects().get(0).getOutcome(), "Change target from " + name + "?", game)) { if (!player.chooseTarget(ability.getEffects().get(0).getOutcome(), newTarget, ability, game)) - newTarget.addTarget(targetId, ability, game); + newTarget.addTarget(targetId, ability, game, false); } else { - newTarget.addTarget(targetId, ability, game); + newTarget.addTarget(targetId, ability, game, false); } } target.clearChosen(); for (UUID newTargetId: newTarget.getTargets()) { - target.addTarget(newTargetId, ability, game); + target.addTarget(newTargetId, ability, game, false); } } return true; diff --git a/Mage/src/mage/game/turn/DeclareAttackersStep.java b/Mage/src/mage/game/turn/DeclareAttackersStep.java index b51b2601936..8b9f50914a8 100644 --- a/Mage/src/mage/game/turn/DeclareAttackersStep.java +++ b/Mage/src/mage/game/turn/DeclareAttackersStep.java @@ -63,6 +63,12 @@ public class DeclareAttackersStep extends Step { game.getCombat().selectAttackers(game); } + @Override + public void resumeBeginStep(Game game, UUID activePlayerId) { + super.resumeBeginStep(game, activePlayerId); + game.getCombat().resumeSelectAttackers(game); + } + @Override public DeclareAttackersStep copy() { return new DeclareAttackersStep(this); diff --git a/Mage/src/mage/game/turn/DeclareBlockersStep.java b/Mage/src/mage/game/turn/DeclareBlockersStep.java index 02bf01d72d7..3dd0d8ccb92 100644 --- a/Mage/src/mage/game/turn/DeclareBlockersStep.java +++ b/Mage/src/mage/game/turn/DeclareBlockersStep.java @@ -61,10 +61,20 @@ public class DeclareBlockersStep extends Step { public void beginStep(Game game, UUID activePlayerId) { super.beginStep(game, activePlayerId); game.getCombat().selectBlockers(game); - game.getCombat().checkBlockRestrictions(game); - game.getCombat().damageAssignmentOrder(game); + if (!game.isPaused()) { + game.getCombat().checkBlockRestrictions(game); + game.getCombat().damageAssignmentOrder(game); + } } + @Override + public void resumeBeginStep(Game game, UUID activePlayerId) { + super.resumeBeginStep(game, activePlayerId); + game.getCombat().resumeSelectBlockers(game); + game.getCombat().checkBlockRestrictions(game); + game.getCombat().damageAssignmentOrder(game); + } + @Override public DeclareBlockersStep copy() { return new DeclareBlockersStep(this); diff --git a/Mage/src/mage/game/turn/Phase.java b/Mage/src/mage/game/turn/Phase.java index 2e4356ef948..c9eb1cd61fa 100644 --- a/Mage/src/mage/game/turn/Phase.java +++ b/Mage/src/mage/game/turn/Phase.java @@ -117,7 +117,7 @@ public abstract class Phase> implements Serializable { return false; } - public boolean resumePlay(Game game, PhaseStep stepType) { + public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) { if (game.isPaused() || game.isGameOver()) return false; @@ -128,7 +128,7 @@ public abstract class Phase> implements Serializable { step = it.next(); currentStep = step; } while (step.getType() != stepType); - resumeStep(game); + resumeStep(game, wasPaused); while (it.hasNext()) { step = it.next(); if (game.isPaused() || game.isGameOver()) @@ -179,13 +179,20 @@ public abstract class Phase> implements Serializable { } } - protected void resumeStep(Game game) { + protected void resumeStep(Game game, boolean wasPaused) { + boolean resuming = true; switch (currentStep.getStepPart()) { case PRE: - prePriority(game, activePlayerId); + if (wasPaused) { + currentStep.resumeBeginStep(game, activePlayerId); + resuming = false; + } + else { + prePriority(game, activePlayerId); + } case PRIORITY: if (!game.isPaused() && !game.isGameOver()) - currentStep.priority(game, activePlayerId, true); + currentStep.priority(game, activePlayerId, resuming); case POST: if (!game.isPaused() && !game.isGameOver()) postPriority(game, activePlayerId); diff --git a/Mage/src/mage/game/turn/Step.java b/Mage/src/mage/game/turn/Step.java index fd9b8768300..67b23c16eb0 100644 --- a/Mage/src/mage/game/turn/Step.java +++ b/Mage/src/mage/game/turn/Step.java @@ -76,6 +76,10 @@ public abstract class Step> implements Serializable { stepPart = StepPart.PRE; game.fireEvent(new GameEvent(preStepEvent, null, null, activePlayerId)); } + + public void resumeBeginStep(Game game, UUID activePlayerId) { + stepPart = StepPart.PRE; + } public void priority(Game game, UUID activePlayerId, boolean resuming) { if (hasPriority) { diff --git a/Mage/src/mage/game/turn/Turn.java b/Mage/src/mage/game/turn/Turn.java index 9702988235c..21166ae6b7e 100644 --- a/Mage/src/mage/game/turn/Turn.java +++ b/Mage/src/mage/game/turn/Turn.java @@ -133,7 +133,7 @@ public class Turn implements Serializable { playExtraTurns(game); } - public void resumePlay(Game game) { + public void resumePlay(Game game, boolean wasPaused) { activePlayerId = game.getActivePlayerId(); UUID priorityPlayerId = game.getPriorityPlayerId(); TurnPhase phaseType = game.getPhase().getType(); @@ -145,7 +145,7 @@ public class Turn implements Serializable { phase = it.next(); currentPhase = phase; } while (phase.type != phaseType); - if (phase.resumePlay(game, stepType)) { + if (phase.resumePlay(game, stepType, wasPaused)) { //20091005 - 500.4/703.4n game.emptyManaPools(); game.saveState(); diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 3c2bcecb69e..f6a4d98cfc8 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -210,7 +210,7 @@ public interface Player extends MageItem, Copyable { public void setResponseBoolean(Boolean responseBoolean); public void setResponseInteger(Integer data); - public abstract void priority(Game game); + public abstract boolean priority(Game game); public abstract boolean choose(Outcome outcome, Target target, UUID sourceId, Game game); public abstract boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options); public abstract boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index de0cc796dd4..3afd4ce259e 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -137,7 +137,7 @@ public abstract class PlayerImpl> implements Player, Ser this.name = player.name; this.human = player.human; this.life = player.life; - this.wins = player.loses; + this.wins = player.wins; this.loses = player.loses; this.library = player.library.copy(); this.hand = player.hand.copy(); @@ -355,6 +355,7 @@ public abstract class PlayerImpl> implements Player, Ser public boolean putInHand(Card card, Game game) { if (card.getOwnerId().equals(playerId)) { this.hand.add(card); + game.setZone(card.getId(), Zone.HAND); } else { return game.getPlayer(card.getOwnerId()).putInHand(card, game); } @@ -557,10 +558,9 @@ public abstract class PlayerImpl> implements Player, Ser int bookmark = game.bookmarkState(); ability.newId(); game.getStack().push(new StackAbility(ability, playerId)); - String message = ability.getActivatedMessage(game); if (ability.activate(game, false)) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, ability.getId(), ability.getSourceId(), playerId)); - game.fireInformEvent(name + message); + game.fireInformEvent(name + ability.getActivatedMessage(game)); game.removeBookmark(bookmark); return true; } @@ -946,19 +946,31 @@ public abstract class PlayerImpl> implements Player, Ser @Override public void restore(Player player) { - this.library = player.getLibrary(); - this.hand = player.getHand(); - this.graveyard = player.getGraveyard(); - this.abilities = player.getAbilities(); - this.manaPool = player.getManaPool(); + this.library = player.getLibrary().copy(); + this.hand = player.getHand().copy(); + this.graveyard = player.getGraveyard().copy(); + this.abilities = player.getAbilities().copy(); + this.manaPool = player.getManaPool().copy(); this.life = player.getLife(); - this.counters = player.getCounters(); - this.inRange = player.getInRange(); + this.counters = player.getCounters().copy(); + this.inRange.clear(); + this.inRange.addAll(player.getInRange()); this.landsPlayed = player.getLandsPlayed(); this.name = player.getName(); this.range = player.getRange(); this.passed = player.isPassed(); - } + this.human = player.isHuman(); + this.wins = player.hasWon(); + this.loses = player.hasLost(); + this.landsPerTurn = player.getLandsPerTurn(); + this.maxHandSize = player.getMaxHandSize(); + this.left = player.hasLeft(); + this.canGainLife = player.isCanGainLife(); + this.canLoseLife = player.isCanLoseLife(); + this.attachments.clear(); + this.attachments.addAll(player.getAttachments()); + this.userData = player.getUserData(); + } @Override public boolean isPassed() { @@ -1255,7 +1267,7 @@ public abstract class PlayerImpl> implements Player, Ser private void addTargetOptions(List options, Ability option, int targetNum, Game game) { for (UUID targetId: option.getTargets().getUnchosen().get(targetNum).possibleTargets(option.getSourceId(), playerId, game)) { Ability newOption = option.copy(); - newOption.getTargets().get(targetNum).addTarget(targetId, option, game); + newOption.getTargets().get(targetNum).addTarget(targetId, option, game, true); if (targetNum < option.getTargets().size() - 2) { //addTargetOptions(options, newOption, targetNum + 1, game); // ayrat: bug fix @@ -1291,7 +1303,7 @@ public abstract class PlayerImpl> implements Player, Ser private void addCostTargetOptions(List options, Ability option, int targetNum, Game game) { for (UUID targetId: option.getCosts().getTargets().get(targetNum).possibleTargets(option.getSourceId(), playerId, game)) { Ability newOption = option.copy(); - newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game); + newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game, true); if (targetNum < option.getCosts().getTargets().size() - 1) { addCostTargetOptions(options, newOption, targetNum + 1, game); } diff --git a/Mage/src/mage/target/Target.java b/Mage/src/mage/target/Target.java index a9161f51817..9d10d04bd16 100644 --- a/Mage/src/mage/target/Target.java +++ b/Mage/src/mage/target/Target.java @@ -56,6 +56,8 @@ public interface Target extends Serializable { public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game); public void addTarget(UUID id, Ability source, Game game); public void addTarget(UUID id, int amount, Ability source, Game game); + public void addTarget(UUID id, Ability source, Game game, boolean skipEvent); + public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent); public boolean canTarget(UUID id, Game game); public boolean canTarget(UUID id, Ability source, Game game); public boolean isLegal(Ability source, Game game); diff --git a/Mage/src/mage/target/TargetAmount.java b/Mage/src/mage/target/TargetAmount.java index c915bbf7465..079cb430688 100644 --- a/Mage/src/mage/target/TargetAmount.java +++ b/Mage/src/mage/target/TargetAmount.java @@ -76,9 +76,9 @@ public abstract class TargetAmount> extends TargetImpl } @Override - public void addTarget(UUID id, int amount, Ability source, Game game) { + public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) { if (amount <= remainingAmount) { - super.addTarget(id, amount, source, game); + super.addTarget(id, amount, source, game, skipEvent); remainingAmount -= amount; } } diff --git a/Mage/src/mage/target/TargetImpl.java b/Mage/src/mage/target/TargetImpl.java index 9b2ddcb4725..7eaec494faa 100644 --- a/Mage/src/mage/target/TargetImpl.java +++ b/Mage/src/mage/target/TargetImpl.java @@ -176,6 +176,11 @@ public abstract class TargetImpl> implements Target { @Override public void addTarget(UUID id, Ability source, Game game) { + addTarget(id, source, game, false); + } + + @Override + public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { //20100423 - 113.3 if (maxNumberOfTargets == 0 || targets.size() < maxNumberOfTargets) { if (!targets.containsKey(id)) { @@ -183,7 +188,8 @@ public abstract class TargetImpl> implements Target { if (!game.replaceEvent(GameEvent.getEvent(EventType.TARGET, id, source.getId(), source.getControllerId()))) { targets.put(id, 0); chosen = targets.size() >= minNumberOfTargets; - game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId())); + if (!skipEvent) + game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId())); } } else { @@ -193,8 +199,13 @@ public abstract class TargetImpl> implements Target { } } - @Override + @Override public void addTarget(UUID id, int amount, Ability source, Game game) { + addTarget(id, amount, source, game, false); + } + + @Override + public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) { if (targets.containsKey(id)) { amount += targets.get(id); } @@ -202,14 +213,15 @@ public abstract class TargetImpl> implements Target { if (!game.replaceEvent(GameEvent.getEvent(EventType.TARGET, id, source.getId(), source.getControllerId()))) { targets.put(id, amount); chosen = targets.size() >= minNumberOfTargets; - game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId())); + if (!skipEvent) + game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId())); } } else { targets.put(id, amount); } } - + @Override public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Game game) { Player player = game.getPlayer(playerId);