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 b5e0f68f5a..717d65fc40 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 @@ -29,6 +29,7 @@ package mage.player.ai; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -39,6 +40,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.logging.Level; import mage.Constants.Outcome; import mage.Constants.PhaseStep; import mage.Constants.RangeOfInfluence; @@ -182,14 +184,18 @@ public class ComputerPlayer2 extends ComputerPlayer implements if (!getNextAction(game)) { Game sim = createSimulation(game); SimulationNode.resetCount(); - root = new SimulationNode(null, sim, maxDepth, playerId); + root = new SimulationNode(null, sim, playerId); logger.debug("simulating actions"); addActionsTimed(new FilterAbility()); if (root.children.size() > 0) { root = root.children.get(0); actions = new LinkedList(root.abilities); combat = root.combat; + if (logger.isDebugEnabled()) + logger.debug("adding actions:" + actions); } + else + logger.debug("no actions added"); } } @@ -215,20 +221,20 @@ public class ComputerPlayer2 extends ComputerPlayer implements return false; } - protected int minimaxAB(SimulationNode node, FilterAbility filter, int depth, int alpha, int beta) { + protected int minimaxAB(SimulationNode node, FilterAbility filter, int alpha, int beta) { UUID currentPlayerId = node.getGame().getPlayerList().get(); SimulationNode bestChild = null; boolean isSimulatedPlayer = currentPlayerId.equals(playerId); for (SimulationNode child: node.getChildren()) { if (alpha >= beta) { - logger.debug("alpha beta pruning"); + logger.debug(indent(node.depth) + "alpha beta pruning"); break; } - if (SimulationNode.nodeCount > maxNodes) { - logger.debug("simulating -- reached end-state"); - break; - } - int val = addActions(child, filter, depth-1, alpha, beta); +// if (SimulationNode.nodeCount > maxNodes) { +// logger.debug(indent(node.depth) + "simulating -- reached end-state"); +// break; +// } + int val = addActions(child, filter, alpha, beta); if (!isSimulatedPlayer) { if (val < beta) { beta = val; @@ -236,7 +242,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements node.setCombat(child.getCombat()); } if (val == GameStateEvaluator.LOSE_SCORE) { - logger.debug("simulating -- lose, can't do worse than this"); + logger.debug(indent(node.depth) + "simulating -- lose, can't do worse than this"); break; } } @@ -247,7 +253,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements node.setCombat(child.getCombat()); } if (val == GameStateEvaluator.WIN_SCORE) { - logger.debug("simulating -- win, can't do better than this"); + logger.debug(indent(node.depth) + "simulating -- win, can't do better than this"); break; } } @@ -256,11 +262,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements if (bestChild != null) node.children.add(bestChild); if (!isSimulatedPlayer) { - logger.debug("returning minimax beta: " + beta); + logger.debug(indent(node.depth) + "returning minimax beta: " + beta); return beta; } else { - logger.debug("returning minimax alpha: " + alpha); + logger.debug(indent(node.depth) + "returning minimax alpha: " + alpha); return alpha; } } @@ -274,7 +280,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements return null; } - protected void resolve(SimulationNode node, int depth, Game game) { + protected void resolve(SimulationNode node, Game game) { StackObject ability = game.getStack().pop(); if (ability instanceof StackAbility) { SearchEffect effect = getSearchEffect((StackAbility) ability); @@ -287,16 +293,16 @@ public class ComputerPlayer2 extends ComputerPlayer implements SearchEffect newEffect = getSearchEffect((StackAbility) newAbility); newEffect.getTarget().addTarget(targetId, newAbility, sim); sim.getStack().push(newAbility); - SimulationNode newNode = new SimulationNode(node, sim, depth, ability.getControllerId()); + SimulationNode newNode = new SimulationNode(node, sim, ability.getControllerId()); node.children.add(newNode); newNode.getTargets().add(targetId); - logger.debug("simulating search -- node#: " + SimulationNode.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); + logger.debug(indent(node.depth) + "simulating search -- node#: " + SimulationNode.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); } return; } } } - logger.debug("simulating resolve "); + logger.debug(indent(node.depth) + "simulating resolve "); ability.resolve(game); game.applyEffects(); game.getPlayers().resetPassed(); @@ -307,7 +313,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements FutureTask task = new FutureTask(new Callable() { public Integer call() throws Exception { - return addActions(root, filter, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE); + return addActions(root, filter, Integer.MIN_VALUE, Integer.MAX_VALUE); } }); pool.execute(task); @@ -316,6 +322,12 @@ public class ComputerPlayer2 extends ComputerPlayer implements } catch (TimeoutException e) { logger.debug("simulating - timed out"); task.cancel(true); + // sleep for 1 second to allow cleanup to finish + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + logger.fatal("can't sleep"); + } } catch (ExecutionException e) { logger.fatal("Simulation error", e); task.cancel(true); @@ -325,28 +337,28 @@ public class ComputerPlayer2 extends ComputerPlayer implements } } - protected int addActions(SimulationNode node, FilterAbility filter, int depth, int alpha, int beta) { + protected int addActions(SimulationNode node, FilterAbility filter, int alpha, int beta) { Game game = node.getGame(); - int val; if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - if (depth <= 0 || SimulationNode.nodeCount > maxNodes || game.isGameOver()) { - logger.debug("simulating -- reached end state"); + int val; + if (node.depth > maxDepth || game.isGameOver()) { + logger.debug(indent(node.depth) + "simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } else if (node.getChildren().size() > 0) { - logger.debug("simulating -- somthing added children:" + node.getChildren().size()); - val = minimaxAB(node, filter, depth-1, alpha, beta); + logger.debug(indent(node.depth) + "simulating -- somthing added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, alpha, beta); } else { if (logger.isDebugEnabled()) - logger.debug("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId)?"yes":"no")); + logger.debug(indent(node.depth) + "simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + node.depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId)?"yes":"no")); if (allPassed(game)) { if (!game.getStack().isEmpty()) { - resolve(node, depth, game); + resolve(node, game); } else { // int testScore = GameStateEvaluator.evaluate(playerId, game); @@ -365,55 +377,57 @@ public class ComputerPlayer2 extends ComputerPlayer implements } else if (node.getChildren().size() > 0) { //declared attackers or blockers or triggered abilities - logger.debug("simulating -- attack/block/trigger added children:" + node.getChildren().size()); - val = minimaxAB(node, filter, depth-1, alpha, beta); + logger.debug(indent(node.depth) + "simulating -- attack/block/trigger added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, alpha, beta); } else { - val = simulatePriority(node, game, filter, depth, alpha, beta); + val = simulatePriority(node, game, filter, alpha, beta); } } if (logger.isDebugEnabled()) - logger.debug("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- score: " + val + " depth:" + node.depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } - protected int simulatePriority(SimulationNode node, Game game, FilterAbility filter, int depth, int alpha, int beta) { + protected int simulatePriority(SimulationNode node, Game game, FilterAbility filter, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } node.setGameValue(game.getState().getValue()); SimulatedPlayer currentPlayer = (SimulatedPlayer) game.getPlayer(game.getPlayerList().get()); boolean isSimulatedPlayer = currentPlayer.getId().equals(playerId); - logger.debug("simulating -- player " + currentPlayer.getName()); + logger.debug(indent(node.depth) + "simulating -- player " + currentPlayer.getName()); SimulationNode bestNode = null; List allActions = currentPlayer.simulatePriority(game, filter); if (logger.isDebugEnabled()) - logger.debug("simulating -- adding " + allActions.size() + " children:" + allActions); + logger.debug(indent(node.depth) + "simulating -- adding " + allActions.size() + " children:" + allActions); for (Ability action: allActions) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); break; } Game sim = game.copy(); if (sim.getPlayer(currentPlayer.getId()).activateAbility((ActivatedAbility) action.copy(), sim)) { sim.applyEffects(); - if (checkForRepeatedAction(sim, node, action, currentPlayer.getId())) + if (checkForUselessAction(sim, node, action, currentPlayer.getId())) { + logger.debug(indent(node.depth) + "found useless action: " + action); continue; + } if (!sim.isGameOver() && action.isUsesStack()) { // only pass if the last action uses the stack sim.getPlayer(currentPlayer.getId()).pass(); sim.getPlayerList().getNext(); } - SimulationNode newNode = new SimulationNode(node, sim, action, depth, currentPlayer.getId()); + SimulationNode newNode = new SimulationNode(node, sim, action, currentPlayer.getId()); if (logger.isDebugEnabled()) - logger.debug("simulating -- node #:" + SimulationNode.getCount() + " actions:" + action); + logger.debug(indent(node.depth) + "simulating -- node #:" + SimulationNode.getCount() + " actions:" + action); sim.checkStateAndTriggered(); - int val = addActions(newNode, filter, depth-1, alpha, beta); + int val = addActions(newNode, filter, alpha, beta); if (!isSimulatedPlayer) { if (val < beta) { beta = val; @@ -421,7 +435,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements node.setCombat(newNode.getCombat()); } if (val == GameStateEvaluator.LOSE_SCORE) { - logger.debug("simulating -- lose, can't do worse than this"); + logger.debug(indent(node.depth) + "simulating -- lose, can't do worse than this"); break; } } @@ -436,18 +450,18 @@ public class ComputerPlayer2 extends ComputerPlayer implements choices = node.getChoices(); } if (val == GameStateEvaluator.WIN_SCORE) { - logger.debug("simulating -- win, can't do better than this"); + logger.debug(indent(node.depth) + "simulating -- win, can't do better than this"); break; } } if (alpha >= beta) { - logger.debug("simulating -- pruning"); - break; - } - if (SimulationNode.nodeCount > maxNodes) { - logger.debug("simulating -- reached end-state"); + logger.debug(indent(node.depth) + "simulating -- pruning"); break; } +// if (SimulationNode.nodeCount > maxNodes) { +// logger.debug(indent(node.depth) + "simulating -- reached end-state"); +// break; +// } } } if (bestNode != null) { @@ -455,11 +469,11 @@ public class ComputerPlayer2 extends ComputerPlayer implements node.children.add(bestNode); } if (!isSimulatedPlayer) { - logger.debug("returning priority beta: " + beta); + logger.debug(indent(node.depth) + "returning priority beta: " + beta); return beta; } else { - logger.debug("returning priority alpha: " + alpha); + logger.debug(indent(node.depth) + "returning priority alpha: " + alpha); return alpha; } } @@ -606,8 +620,8 @@ public class ComputerPlayer2 extends ComputerPlayer implements } } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, playerId, playerId)); - SimulationNode newNode = new SimulationNode(node, sim, node.getDepth()-1, activePlayerId); - logger.debug("simulating -- node #:" + SimulationNode.getCount() + " declare attakers"); + SimulationNode newNode = new SimulationNode(node, sim, activePlayerId); + logger.debug(indent(node.depth) + "simulating -- node #:" + SimulationNode.getCount() + " declare attakers"); newNode.setCombat(sim.getCombat()); node.children.add(newNode); } @@ -627,8 +641,8 @@ public class ComputerPlayer2 extends ComputerPlayer implements } } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); - SimulationNode newNode = new SimulationNode(node, sim, node.getDepth()-1, defenderId); - logger.debug("simulating -- node #:" + SimulationNode.getCount() + " declare blockers"); + SimulationNode newNode = new SimulationNode(node, sim, defenderId); + logger.debug(indent(node.depth) + "simulating -- node #:" + SimulationNode.getCount() + " declare blockers"); newNode.setCombat(sim.getCombat()); node.children.add(newNode); } @@ -698,22 +712,43 @@ public class ComputerPlayer2 extends ComputerPlayer implements return sim; } - private boolean checkForRepeatedAction(Game sim, SimulationNode node, Ability action, UUID playerId) { + /** + * resolve current ability on the stack if there is one, then + * check if current game state is the same as the previous, if so then + * action has no effect and is not useful + * + * @param sim + * @param node + * @param action + * @param playerId + * @return + */ + private boolean checkForUselessAction(Game sim, SimulationNode node, Ability action, UUID playerId) { + int currentVal = 0; + int prevVal = 0; if (action instanceof PassAbility) return false; - int val = GameStateEvaluator.evaluate(playerId, sim); SimulationNode test = node.getParent(); - while (test != null && !test.getPlayerId().equals(playerId)) { - test = test.getParent(); + if (test == null) + return false; + if (action.isUsesStack()) { + Game testSim = sim.copy(); + StackObject ability = testSim.getStack().pop(); + ability.resolve(testSim); + testSim.applyEffects(); + currentVal = GameStateEvaluator.evaluate(playerId, testSim, true); } - if (test != null && test.getAbilities() != null && test.getAbilities().size() == 1) { - if (action.toString().equals(test.getAbilities().get(0).toString()) && GameStateEvaluator.evaluate(playerId, sim) == val) { - if (logger.isDebugEnabled()) - logger.debug("found repeated action " + action); - return true; - } + else { + currentVal = GameStateEvaluator.evaluate(playerId, sim, true); } - return false; + prevVal = GameStateEvaluator.evaluate(playerId, test.getGame(), true); + return currentVal == prevVal; + } + + protected String indent(int num) { + char[] fill = new char[num]; + Arrays.fill(fill, ' '); + return Integer.toString(num) + new String(fill); } } 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 ec2291780d..571c7e0d6c 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 @@ -156,7 +156,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { currentScore = GameStateEvaluator.evaluate(playerId, game); Game sim = createSimulation(game); SimulationNode.resetCount(); - root = new SimulationNode(null, sim, maxDepth, playerId); + root = new SimulationNode(null, sim, playerId); logger.debug("simulating pre combat actions -----------------------------------------------------------------------------------------"); addActionsTimed(new FilterAbility()); @@ -165,7 +165,11 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { root = root.children.get(0); actions = new LinkedList(root.abilities); combat = root.combat; + if (logger.isDebugEnabled()) + logger.debug("adding pre-combat actions:" + actions); } + else + logger.debug("no pre-combat actions added"); } } @@ -174,7 +178,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { currentScore = GameStateEvaluator.evaluate(playerId, game); Game sim = createSimulation(game); SimulationNode.resetCount(); - root = new SimulationNode(null, sim, maxDepth, playerId); + root = new SimulationNode(null, sim, playerId); logger.debug("simulating post combat actions ----------------------------------------------------------------------------------------"); addActionsTimed(new FilterAbility()); // addActions(root, new FilterAbility(), maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE); @@ -182,34 +186,38 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { root = root.children.get(0); actions = new LinkedList(root.abilities); combat = root.combat; + if (logger.isDebugEnabled()) + logger.debug("adding post-combat actions:" + actions); } + else + logger.debug("no post-combat actions added"); } } @Override - protected int addActions(SimulationNode node, FilterAbility filter, int depth, int alpha, int beta) { + protected int addActions(SimulationNode node, FilterAbility filter, int alpha, int beta) { boolean stepFinished = false; int val; Game game = node.getGame(); if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - if (depth <= 0 || SimulationNode.nodeCount > maxNodes || game.isGameOver()) { - logger.debug("simulating -- reached end state"); + if (node.depth > maxDepth || game.isGameOver()) { + logger.debug(indent(node.depth) + "simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } else if (node.getChildren().size() > 0) { - logger.debug("simulating -- somthing added children:" + node.getChildren().size()); - val = minimaxAB(node, filter, depth-1, alpha, beta); + logger.debug(indent(node.depth) + "simulating -- somthing added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, alpha, beta); } else { if (logger.isDebugEnabled()) - logger.debug("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(game.getPlayerList().get()).getName()); + logger.debug(indent(node.depth) + "simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + node.depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(game.getPlayerList().get()).getName()); if (allPassed(game)) { if (!game.getStack().isEmpty()) { - resolve(node, depth, game); + resolve(node, game); } else { stepFinished = true; @@ -220,21 +228,21 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { val = GameStateEvaluator.evaluate(playerId, game); } else if (stepFinished) { - logger.debug("step finished"); + logger.debug(indent(node.depth) + "step finished"); int testScore = GameStateEvaluator.evaluate(playerId, game); if (game.getActivePlayerId().equals(playerId)) { if (testScore < currentScore) { // if score at end of step is worse than original score don't check further - logger.debug("simulating -- abandoning check, no immediate benefit"); + logger.debug(indent(node.depth) + "simulating -- abandoning check, no immediate benefit"); val = testScore; } else { switch (game.getTurn().getStepType()) { case PRECOMBAT_MAIN: - val = simulateCombat(game, node, depth-1, alpha, beta, false); + val = simulateCombat(game, node, alpha, beta, false); break; case POSTCOMBAT_MAIN: - val = simulateCounterAttack(game, node, depth-1, alpha, beta); + val = simulateCounterAttack(game, node, alpha, beta); break; default: val = GameStateEvaluator.evaluate(playerId, game); @@ -244,31 +252,31 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } else { if (game.getTurn().getStepType() == PhaseStep.DECLARE_ATTACKERS) - val = simulateBlockers(game, node, playerId, depth-1, alpha, beta, true); + val = simulateBlockers(game, node, playerId, alpha, beta, true); else val = GameStateEvaluator.evaluate(playerId, game); } } else if (node.getChildren().size() > 0) { - logger.debug("simulating -- trigger added children:" + node.getChildren().size()); - val = minimaxAB(node, filter, depth, alpha, beta); + logger.debug(indent(node.depth) + "simulating -- trigger added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, alpha, beta); } else { - val = simulatePriority(node, game, filter, depth, alpha, beta); + val = simulatePriority(node, game, filter, alpha, beta); } } if (logger.isDebugEnabled()) - logger.debug("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- score: " + val + " depth:" + node.depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } - protected int simulateCombat(Game game, SimulationNode node, int depth, int alpha, int beta, boolean counter) { + protected int simulateCombat(Game game, SimulationNode node, int alpha, int beta, boolean counter) { Integer val = null; if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } if (game.getTurn().getStepType() != PhaseStep.DECLARE_BLOCKERS) { @@ -279,12 +287,12 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { if (!game.getStep().skipStep(game, game.getActivePlayerId())) { game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, game.getActivePlayerId())); if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, game.getActivePlayerId(), game.getActivePlayerId()))) { - val = simulateAttackers(game, node, game.getActivePlayerId(), depth, alpha, beta, counter); + val = simulateAttackers(game, node, game.getActivePlayerId(), alpha, beta, counter); } } else if (!counter) { simulateToEnd(game); - val = simulatePostCombatMain(game, node, depth, alpha, beta); + val = simulatePostCombatMain(game, node, alpha, beta); } } } @@ -293,26 +301,26 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_PRE, null, null, game.getActivePlayerId())); if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, game.getActivePlayerId(), game.getActivePlayerId()))) { //only suitable for two player games - only simulates blocks for 1st defender - val = simulateBlockers(game, node, game.getCombat().getDefenders().iterator().next(), depth, alpha, beta, counter); + val = simulateBlockers(game, node, game.getCombat().getDefenders().iterator().next(), alpha, beta, counter); } } else if (!counter) { finishCombat(game); - val = simulateCounterAttack(game, node, depth, alpha, beta); + val = simulateCounterAttack(game, node, alpha, beta); } } if (val == null) val = GameStateEvaluator.evaluate(playerId, game); if (logger.isDebugEnabled()) - logger.debug("returning -- combat score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- combat score: " + val + " depth:" + node.depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } - protected int simulateAttackers(Game game, SimulationNode node, UUID attackerId, int depth, int alpha, int beta, boolean counter) { + protected int simulateAttackers(Game game, SimulationNode node, UUID attackerId, int alpha, int beta, boolean counter) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; @@ -320,10 +328,10 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { SimulatedPlayer attacker = (SimulatedPlayer) game.getPlayer(attackerId); if (logger.isDebugEnabled()) - logger.debug(attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(game)); + logger.debug(indent(node.depth) + attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(game)); for (Combat engagement: attacker.addAttackers(game)) { if (alpha >= beta) { - logger.debug("simulating -- pruning attackers"); + logger.debug(indent(node.depth) + "simulating -- pruning attackers"); break; } Game sim = game.copy(); @@ -334,19 +342,19 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); - SimulationNode newNode = new SimulationNode(node, sim, depth, attackerId); + SimulationNode newNode = new SimulationNode(node, sim, attackerId); if (logger.isDebugEnabled()) - logger.debug("simulating attack for player:" + game.getPlayer(attackerId).getName()); + logger.debug(indent(node.depth) + "simulating attack for player:" + game.getPlayer(attackerId).getName()); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); - logger.debug("resolving triggered abilities"); + logger.debug(indent(node.depth) + "resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); sim.getPhase().setStep(new DeclareBlockersStep()); - val = simulateCombat(sim, newNode, depth-1, alpha, beta, counter); + val = simulateCombat(sim, newNode, alpha, beta, counter); if (!attackerId.equals(playerId)) { if (val < beta) { beta = val; @@ -369,14 +377,14 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { node.children.add(bestNode); } if (logger.isDebugEnabled()) - logger.debug("returning -- combat attacker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- combat attacker score: " + val + " depth:" + node.depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } - protected int simulateBlockers(Game game, SimulationNode node, UUID defenderId, int depth, int alpha, int beta, boolean counter) { + protected int simulateBlockers(Game game, SimulationNode node, UUID defenderId, int alpha, int beta, boolean counter) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; @@ -385,10 +393,10 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { if (game.getCombat().isAttacked(defenderId, game)) { SimulatedPlayer defender = (SimulatedPlayer) game.getPlayer(defenderId); if (logger.isDebugEnabled()) - logger.debug(defender.getName() + "'s possible blockers: " + defender.getAvailableBlockers(game)); + logger.debug(indent(node.depth) + defender.getName() + "'s possible blockers: " + defender.getAvailableBlockers(game)); for (Combat engagement: defender.addBlockers(game)) { if (alpha >= beta) { - logger.debug("simulating -- pruning blockers"); + logger.debug(indent(node.depth) + "simulating -- pruning blockers"); break; } Game sim = game.copy(); @@ -401,13 +409,13 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); - SimulationNode newNode = new SimulationNode(node, sim, depth, defenderId); + SimulationNode newNode = new SimulationNode(node, sim, defenderId); if (logger.isDebugEnabled()) - logger.debug("simulating block for player:" + game.getPlayer(defenderId).getName()); + logger.debug(indent(node.depth) + "simulating block for player:" + game.getPlayer(defenderId).getName()); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); - logger.debug("resolving triggered abilities"); + logger.debug(indent(node.depth) + "resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); @@ -417,7 +425,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { val = GameStateEvaluator.evaluate(playerId, sim); } else if (!counter) { - val = simulatePostCombatMain(sim, newNode, depth-1, alpha, beta); + val = simulatePostCombatMain(sim, newNode, alpha, beta); } else val = GameStateEvaluator.evaluate(playerId, sim); @@ -444,21 +452,21 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { node.children.add(bestNode); } if (logger.isDebugEnabled()) - logger.debug("returning -- combat blocker score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- combat blocker score: " + val + " depth:" + node.depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; } - protected int simulateCounterAttack(Game game, SimulationNode node, int depth, int alpha, int beta) { + protected int simulateCounterAttack(Game game, SimulationNode node, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; if (!game.isGameOver()) { simulateToEnd(game); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); - logger.debug("simulating -- counter attack for player " + game.getPlayer(game.getActivePlayerId()).getName()); + logger.debug(indent(node.depth) + "simulating -- counter attack for player " + game.getPlayer(game.getActivePlayerId()).getName()); game.getTurn().setPhase(new BeginningPhase()); if (game.getPhase().beginPhase(game, game.getActivePlayerId())) { simulateStep(game, new UntapStep()); @@ -466,9 +474,9 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { simulateStep(game, new DrawStep()); game.getPhase().endPhase(game, game.getActivePlayerId()); } - val = simulateCombat(game, node, depth-1, alpha, beta, true); + val = simulateCombat(game, node, alpha, beta, true); if (logger.isDebugEnabled()) - logger.debug("returning -- counter attack score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + logger.debug(indent(node.depth) + "returning -- counter attack score: " + val + " depth:" + node.depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } if (val == null) val = GameStateEvaluator.evaluate(playerId, game); @@ -506,21 +514,21 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { simulateStep(game, new EndOfCombatStep()); } - protected int simulatePostCombatMain(Game game, SimulationNode node, int depth, int alpha, int beta) { + protected int simulatePostCombatMain(Game game, SimulationNode node, int alpha, int beta) { if (Thread.interrupted()) { Thread.currentThread().interrupt(); - logger.debug("interrupted"); + logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - logger.debug("simulating -- post combat main"); + logger.debug(indent(node.depth) + "simulating -- post combat main"); game.getTurn().setPhase(new PostCombatMainPhase()); if (game.getPhase().beginPhase(game, game.getActivePlayerId())) { game.getPhase().setStep(new PostCombatMainStep()); game.getStep().beginStep(game, playerId); game.getPlayers().resetPassed(); - return addActions(node, new FilterAbility(), depth, alpha, beta); + return addActions(node, new FilterAbility(), alpha, beta); } - return simulateCounterAttack(game, node, depth, alpha, beta); + return simulateCounterAttack(game, node, alpha, beta); } protected void simulateToEnd(Game game) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java index eb31cf6f04..59aa730248 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java @@ -63,6 +63,10 @@ public class GameStateEvaluator { public static final int LOSE_SCORE = Integer.MIN_VALUE + 1; public static int evaluate(UUID playerId, Game game) { + return evaluate(playerId, game, false); + } + + public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) { Player player = game.getPlayer(playerId); Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); if (game.isGameOver()) { @@ -74,10 +78,10 @@ public class GameStateEvaluator { int lifeScore = (player.getLife() - opponent.getLife()) * LIFE_FACTOR; int permanentScore = 0; for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) { - permanentScore += evaluatePermanent(permanent, game); + permanentScore += evaluatePermanent(permanent, game, ignoreTapped); } for (Permanent permanent: game.getBattlefield().getAllActivePermanents(opponent.getId())) { - permanentScore -= evaluatePermanent(permanent, game); + permanentScore -= evaluatePermanent(permanent, game, ignoreTapped); } permanentScore *= PERMANENT_FACTOR; @@ -91,8 +95,12 @@ public class GameStateEvaluator { return score; } - public static int evaluatePermanent(Permanent permanent, Game game) { - int value = permanent.isTapped()?4:5; + public static int evaluatePermanent(Permanent permanent, Game game, boolean ignoreTapped) { + int value = 0; + if (ignoreTapped) + value = 5; + else + value = permanent.isTapped()?4:5; if (permanent.getCardType().contains(CardType.CREATURE)) { value += evaluateCreature(permanent, game) * CREATURE_FACTOR; } 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 5ae447975f..9e4212be3d 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 @@ -29,6 +29,7 @@ package mage.player.ai; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -64,9 +65,11 @@ public class SimulatedPlayer extends ComputerPlayer { private FilterAbility filter; private transient ConcurrentLinkedQueue allActions; private static PassAbility pass = new PassAbility(); + protected int maxDepth; public SimulatedPlayer(UUID id, boolean isSimulatedPlayer) { super(id); + maxDepth = Config.maxDepth; pass.setControllerId(playerId); this.isSimulatedPlayer = isSimulatedPlayer; } @@ -91,6 +94,7 @@ public class SimulatedPlayer extends ComputerPlayer { simulateOptions(sim, pass); ArrayList list = new ArrayList(allActions); + //Collections.shuffle(list); Collections.reverse(list); return list; } @@ -244,23 +248,22 @@ public class SimulatedPlayer extends ComputerPlayer { } else { SimulationNode parent = (SimulationNode) game.getCustomData(); - int depth = parent.getDepth() - 1; - if (depth == 0) return true; - logger.debug("simulating -- triggered ability - adding children:" + options.size()); + if (parent.getDepth() == maxDepth) return true; + logger.debug(indent(parent.getDepth()) + "simulating -- triggered ability - adding children:" + options.size()); for (Ability option: options) { - addAbilityNode(parent, option, depth, game); + addAbilityNode(parent, option, game); } } return true; } - protected void addAbilityNode(SimulationNode parent, Ability ability, int depth, Game game) { + protected void addAbilityNode(SimulationNode parent, Ability ability, Game game) { Game sim = game.copy(); sim.getStack().push(new StackAbility(ability, playerId)); ability.activate(sim, false); sim.applyEffects(); - SimulationNode newNode = new SimulationNode(parent, sim, depth, playerId); - logger.debug("simulating -- node #:" + SimulationNode.getCount() + " triggered ability option"); + SimulationNode newNode = new SimulationNode(parent, sim, playerId); + logger.debug(indent(newNode.getDepth()) + "simulating -- node #:" + SimulationNode.getCount() + " triggered ability option"); for (Target target: ability.getTargets()) { for (UUID targetId: target.getTargets()) { newNode.getTargets().add(targetId); @@ -277,4 +280,10 @@ public class SimulatedPlayer extends ComputerPlayer { //should never get here } + protected String indent(int num) { + char[] fill = new char[num]; + Arrays.fill(fill, ' '); + return new String(fill); + } + } diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulationNode.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulationNode.java index 002ce922ab..34e2b5ccbd 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulationNode.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulationNode.java @@ -55,22 +55,25 @@ public class SimulationNode implements Serializable { protected UUID playerId; protected Combat combat; - public SimulationNode(SimulationNode parent, Game game, int depth, UUID playerId) { + public SimulationNode(SimulationNode parent, Game game, UUID playerId) { this.parent = parent; this.game = game; - this.depth = depth; + if (parent == null) + this.depth = 1; + else + this.depth = parent.getDepth() + 1; this.playerId = playerId; game.setCustomData(this); nodeCount++; } - public SimulationNode(SimulationNode parent, Game game, List abilities, int depth, UUID playerId) { - this(parent, game, depth, playerId); + public SimulationNode(SimulationNode parent, Game game, List abilities, UUID playerId) { + this(parent, game, playerId); this.abilities = abilities; } - public SimulationNode(SimulationNode parent, Game game, Ability ability, int depth, UUID playerId) { - this(parent, game, depth, playerId); + public SimulationNode(SimulationNode parent, Game game, Ability ability, UUID playerId) { + this(parent, game, playerId); this.abilities = new ArrayList(); abilities.add(ability); } diff --git a/Mage.Server/plugins/mage-player-aiminimax.jar b/Mage.Server/plugins/mage-player-aiminimax.jar index e72f7fc423..7e6aedbce4 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.Tests/plugins/mage-player-aiminimax.jar b/Mage.Tests/plugins/mage-player-aiminimax.jar index e72f7fc423..7e6aedbce4 100644 Binary files a/Mage.Tests/plugins/mage-player-aiminimax.jar and b/Mage.Tests/plugins/mage-player-aiminimax.jar differ diff --git a/Mage/src/mage/abilities/costs/common/PayLifeCost.java b/Mage/src/mage/abilities/costs/common/PayLifeCost.java index 4029811618..6c06822472 100644 --- a/Mage/src/mage/abilities/costs/common/PayLifeCost.java +++ b/Mage/src/mage/abilities/costs/common/PayLifeCost.java @@ -53,7 +53,7 @@ public class PayLifeCost extends CostImpl { @Override public boolean canPay(UUID sourceId, UUID controllerId, Game game) { - return game.getPlayer(controllerId).getLife() >= amount; + return game.getPlayer(controllerId).getLife() > amount; } @Override