From 9450074b7043283c0acff2bbed1968c0cdfdc6c9 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 14 Mar 2013 23:55:14 +0100 Subject: [PATCH] Changed AI X costs handling according human handling. Fixed AI handling of modal spells. Changed some AI setting. Changes to AI lgging. --- .../src/mage/player/ai/ComputerPlayer6.java | 157 ++++++++++-------- .../src/mage/player/ai/ComputerPlayer7.java | 80 +++++---- .../src/mage/player/ai/Config2.java | 2 +- .../src/mage/player/ai/SimulatedPlayer2.java | 101 ++++++----- .../java/mage/player/ai/ComputerPlayer.java | 29 ++-- .../src/mage/player/human/HumanPlayer.java | 26 +-- .../org/mage/test/player/RandomPlayer.java | 18 -- Mage/src/mage/abilities/AbilityImpl.java | 37 +++-- .../abilities/costs/mana/ManaCostsImpl.java | 19 ++- Mage/src/mage/players/Player.java | 4 +- Mage/src/mage/players/PlayerImpl.java | 36 +++- 11 files changed, 287 insertions(+), 222 deletions(-) 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 3df928d7bef..5fb8c89a1df 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 @@ -35,11 +35,8 @@ import mage.Constants.PhaseStep; import mage.Constants.RangeOfInfluence; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; +import mage.abilities.SpellAbility; import mage.abilities.common.PassAbility; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.costs.mana.ManaCost; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.effects.Effect; import mage.abilities.effects.SearchEffect; import mage.abilities.keyword.*; @@ -63,6 +60,7 @@ import mage.player.ai.util.CombatUtil; import mage.players.Player; import mage.target.Target; import mage.target.TargetCard; +import mage.target.Targets; /** @@ -87,7 +85,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements protected Set actionCache; private static final List optimizers = new ArrayList(); - private int lastTurnOutput = 0; + protected int lastLoggedTurn = 0; static { optimizers.add(new LevelUpOptimizer()); @@ -97,7 +95,11 @@ public class ComputerPlayer6 extends ComputerPlayer implements public ComputerPlayer6(String name, RangeOfInfluence range, int skill) { super(name, range); - maxDepth = skill * 2; + if (skill < 4) { + maxDepth = 4; + } else { + maxDepth = skill; + } maxThink = skill * 3; maxNodes = Config2.maxNodes; getSuggestedActions(); @@ -136,10 +138,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements case POSTCOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { if (logger.isInfoEnabled()) { - printOutState(game, playerId); - for (UUID opponentId : game.getOpponents(playerId)) { - printOutState(game, opponentId); - } + printOutState(game); } if (actions.size() == 0) { calculateActions(game); @@ -182,14 +181,23 @@ public class ComputerPlayer6 extends ComputerPlayer implements return false; } + protected void printOutState(Game game) { + if (logger.isInfoEnabled()) { + printOutState(game, playerId); + for (UUID opponentId : game.getOpponents(playerId)) { + printOutState(game, opponentId); + } + } + } + protected void printOutState(Game game, UUID playerId) { - if (lastTurnOutput != game.getTurnNum()) { - lastTurnOutput = game.getTurnNum(); - logger.info(new StringBuilder("------------------------ ").append("Turn: ").append(game.getTurnNum()).append(" --------------------------------------------------------------").toString()); + if (lastLoggedTurn != game.getTurnNum()) { + lastLoggedTurn = game.getTurnNum(); + logger.info(new StringBuilder("------------------------ ").append("Turn: ").append(game.getTurnNum()).append("] --------------------------------------------------------------").toString()); } Player player = game.getPlayer(playerId); - logger.info(new StringBuilder("[").append(game.getPlayer(playerId).getName()).append("] ").append(game.getTurn().getStepType().name()).append(", life=").append(player.getLife()).toString()); + logger.info(new StringBuilder("[").append(game.getPlayer(playerId).getName()).append("], life = ").append(player.getLife()).toString()); StringBuilder sb = new StringBuilder("-> Hand: ["); for (Card card : player.getHand().getCards(game)) { sb.append(card.getName()).append(";"); @@ -219,7 +227,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements boolean usedStack = false; while (actions.peek() != null) { Ability ability = actions.poll(); - logger.info(new StringBuilder("Act -------------> [").append(game.getPlayer(playerId).getName()).append("] Action: ").append(ability.toString()).toString()); + logger.info(new StringBuilder("===> Act [").append(game.getPlayer(playerId).getName()).append("] Action: ").append(ability.toString()).toString()); if (ability.getTargets().size() > 0) { for (Target target : ability.getTargets()) { for (UUID id : target.getTargets()) { @@ -240,9 +248,9 @@ public class ComputerPlayer6 extends ComputerPlayer implements while (it.hasNext()) { Card card = game.getCard(ability.getSourceId()); String action = it.next(); - logger.info("action=" + action + ";card=" + card); + logger.info("Suggested action=" + action + ";card=" + card); if (action.equals(card.getName())) { - logger.info("removed from suggested=" + action); + logger.info("-> removed from suggested=" + action); it.remove(); } } @@ -287,7 +295,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements test = root; root = root.children.get(0); } - logger.debug("simlating -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue); + logger.trace("Sim getNextAction -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue); if (!suggested.isEmpty()) { return false; } @@ -318,7 +326,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements } protected int minimaxAB(SimulationNode2 node, int depth, int alpha, int beta) { - logger.info("Simulating minimaxAB -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " node-score: " + (node != null ? node.getScore() : "null")); + logger.trace("Sim minimaxAB ["+depth+"] -- a: " + alpha + " b: " + beta + " <" + (node != null ? node.getScore() : "null")+">"); UUID currentPlayerId = node.getGame().getPlayerList().get(); SimulationNode2 bestChild = null; for (SimulationNode2 child : node.getChildren()) { @@ -400,7 +408,7 @@ public class ComputerPlayer6 extends ComputerPlayer implements SimulationNode2 newNode = new SimulationNode2(node, sim, depth, ability.getControllerId()); node.children.add(newNode); newNode.getTargets().add(targetId); - logger.debug("simulating search -- node#: " + SimulationNode2.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); + logger.trace("Sim search -- node#: " + SimulationNode2.getCount() + " for player: " + sim.getPlayer(ability.getControllerId()).getName()); } return; } @@ -532,24 +540,55 @@ public class ComputerPlayer6 extends ComputerPlayer implements sim.getPlayerList().getNext(); } SimulationNode2 newNode = new SimulationNode2(node, sim, action, depth, currentPlayer.getId()); + logger.trace(new StringBuilder("Sim Prio [").append(depth).append("]#").append(counter).append(" -- newNode (").append(action.toString()).append(") ").append(newNode.hashCode()).append(" parent node ").append(node.hashCode())); // int testVal = GameStateEvaluator2.evaluate(currentPlayer.getId(), sim); - + sim.checkStateAndTriggered(); int val = addActions(newNode, depth - 1, alpha, beta); if (logger.isInfoEnabled() && depth == maxDepth) { - StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter).append(" -- val = ").append(val).append(" (").append(action).append(")"); + StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter) + .append(" <").append(val).append("> (").append(action) + .append(action.isModal() ? " Mode = "+action.getModes().getMode().toString():"") + .append(listTargets(game, action.getTargets())).append(")") + .append(logger.isTraceEnabled()?" #" +newNode.hashCode():""); SimulationNode2 logNode = newNode; while (logNode.getChildren() != null && logNode.getChildren().size()>0) { logNode = logNode.getChildren().get(0); if (logNode.getAbilities() != null && logNode.getAbilities().size()>0) { - sb.append(" --> ").append(logNode.getAbilities().get(0).toString()); + sb.append(" -> [").append(logNode.getDepth()).append("]").append(logNode.getAbilities().toString()).append("<").append(logNode.getScore()).append(">"); } } - logger.info(sb.toString()); + logger.info(sb); } - if (!currentPlayer.getId().equals(playerId)) { + if (currentPlayer.getId().equals(playerId)) { + if (val > alpha) { + alpha = val; + bestNode = newNode; + bestNode.setScore(val); + if (newNode.getChildren().size() > 0) { + bestNode.setCombat(newNode.getChildren().get(0).getCombat()); + } + /* + * if (node.getTargets().size() > 0) targets = + * node.getTargets(); if (node.getChoices().size() > 0) + * choices = node.getChoices(); + */ + if (depth == maxDepth) { + logger.info(new StringBuilder("Sim Prio [").append(depth).append("] -- Saved best node yet <").append(bestNode.getScore()).append("> ").append(bestNode.getAbilities().toString()).toString()); + node.children.clear(); + node.children.add(bestNode); + node.setScore(bestNode.getScore()); + } + } + + // no need to check other actions + if (val == GameStateEvaluator2.WIN_GAME_SCORE) { + logger.debug("Sim Prio -- win - break"); + break; + } + } else { if (val < beta) { beta = val; bestNode = newNode; @@ -564,32 +603,6 @@ public class ComputerPlayer6 extends ComputerPlayer implements logger.debug("Sim Prio -- lose - break"); break; } - } else { - if (val > alpha) { - alpha = val; - bestNode = newNode; - bestNode.setScore(val); - if (newNode.getChildren().size() > 0) { - bestNode.setCombat(newNode.getChildren().get(0).getCombat()); - } - /* - * if (node.getTargets().size() > 0) targets = - * node.getTargets(); if (node.getChoices().size() > 0) - * choices = node.getChoices(); - */ - if (depth == maxDepth) { - logger.info(new StringBuilder("Sim Prio [").append(depth).append("] -- Saved best node yet with score: ").append(bestNode.getScore()).append(" abilities: ").append(bestNode.getAbilities().toString()).toString()); - node.children.clear(); - node.children.add(bestNode); - node.setScore(bestNode.getScore()); - } - } - - // no need to check other actions - if (val == GameStateEvaluator2.WIN_GAME_SCORE) { - logger.debug("Sim Prio -- win - break"); - break; - } } if (alpha >= beta) { //logger.info("Sim Prio -- pruning"); @@ -600,16 +613,16 @@ public class ComputerPlayer6 extends ComputerPlayer implements break; } } - } // end for allActions + } // end of for (allActions) if (depth == maxDepth) { - logger.info(new StringBuilder("Sim Prio [").append(depth).append("] -- End for Max Depth").toString()); + logger.info(new StringBuilder("Sim Prio [").append(depth).append("] -- End for Max Depth -- Nodes calculated: ").append(SimulationNode2.nodeCount)); } if (bestNode != null) { node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); if (logger.isTraceEnabled() && !bestNode.getAbilities().toString().equals("[Pass]") ) { - logger.trace(new StringBuilder("Sim Prio [").append(depth).append("] -- Set after (depth=").append(depth).append(") Score: ").append(bestNode.getScore()).append(" abilities: ").append(bestNode.getAbilities().toString()).toString()); + logger.trace(new StringBuilder("Sim Prio [").append(depth).append("] -- Set after (depth=").append(depth).append(") <").append(bestNode.getScore()).append("> ").append(bestNode.getAbilities().toString()).toString()); } } @@ -733,21 +746,6 @@ public class ComputerPlayer6 extends ComputerPlayer implements return true; } - @Override - public boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game) { - //SimulatedPlayer.simulateVariableCosts method adds a generic mana cost for each option - for (ManaCost manaCost : costs) { - if (manaCost instanceof GenericManaCost) { - cost.setPayment(manaCost.getPayment()); - logger.debug("using X = " + cost.getPayment().count()); - break; - } - } - game.informPlayers(getName() + " payed " + cost.getPayment().count() + " for " + cost.getText()); - cost.setPaid(); - return true; - } - public void playNext(Game game, UUID activePlayerId, SimulationNode2 node) { boolean skip = false; while (true) { @@ -1288,7 +1286,9 @@ public class ComputerPlayer6 extends ComputerPlayer implements Game sim = game.copy(); for (Player copyPlayer : sim.getState().getPlayers().values()) { Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()).copy(); - logger.debug(origPlayer.getName() + " suggested: " + suggested); + if (!suggested.isEmpty()) { + logger.debug(origPlayer.getName() + " suggested: " + suggested); + } SimulatedPlayer2 newPlayer = new SimulatedPlayer2(copyPlayer.getId(), copyPlayer.getId().equals(playerId), suggested); newPlayer.restore(origPlayer); sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); @@ -1297,7 +1297,8 @@ public class ComputerPlayer6 extends ComputerPlayer implements } private boolean checkForRepeatedAction(Game sim, SimulationNode2 node, Ability action, UUID playerId) { - if (action instanceof PassAbility) { + // pass or casting two times a spell multiple times on hand is ok + if (action instanceof PassAbility || action instanceof SpellAbility) { return false; } int newVal = GameStateEvaluator2.evaluate(playerId, sim); @@ -1352,4 +1353,18 @@ public class ComputerPlayer6 extends ComputerPlayer implements suggested.add(action.substring(5, action.length())); } } + + protected String listTargets(Game game, Targets targets) { + StringBuilder sb = new StringBuilder(); + if (targets != null) { + for (Target target: targets) { + sb.append("[").append(target.getTargetedName(game)).append("]"); + } + if (sb.length()>0) { + sb.insert(0," targeting "); + } + } + return sb.toString(); + } + } 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 ab4adb4e3b7..7b4c4eb8aca 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 @@ -66,8 +66,12 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { @Override public boolean priority(Game game) { + if (lastLoggedTurn != game.getTurnNum()) { + lastLoggedTurn = game.getTurnNum(); + logger.info(new StringBuilder("------------------------ ").append("Turn: ").append(game.getTurnNum()).append(" [").append(game.getPlayer(game.getActivePlayerId()).getName()).append("----------------------------------------------------").toString()); + } logState(game); - logger.debug("Game State: Turn-" + game.getTurnNum() + " Step-" + game.getTurn().getStepType() + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name); + logger.debug("Priority -- Step: " + (game.getTurn().getStepType() + " ").substring(0,25) + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name); game.getState().setPriorityPlayerId(playerId); game.firePriorityEvent(playerId); switch (game.getTurn().getStepType()) { @@ -77,8 +81,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { return false; case PRECOMBAT_MAIN: if (game.getActivePlayerId().equals(playerId)) { - printOutState(game, playerId); - printOutState(game, game.getOpponents(playerId).iterator().next()); + printOutState(game); if (actions.size() == 0) { calculatePreCombatActions(game); } @@ -94,8 +97,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { return false; case DECLARE_ATTACKERS: if (!game.getActivePlayerId().equals(playerId)) { - printOutState(game, playerId); - printOutState(game, game.getOpponents(playerId).iterator().next()); + printOutState(game); if (actions.size() == 0) { calculatePreCombatActions(game); } @@ -113,19 +115,18 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { pass(); return false; case POSTCOMBAT_MAIN: - if (game.getActivePlayerId().equals(playerId)) { - printOutState(game, playerId); - printOutState(game, game.getOpponents(playerId).iterator().next()); +// if (game.getActivePlayerId().equals(playerId)) { + printOutState(game); if (actions.size() == 0) { calculatePostCombatActions(game); } act(game); return true; - } - else { - pass(); - } - return false; +// } +// else { +// pass(); +// } +// return false; case END_TURN: case CLEANUP: actionCache.clear(); @@ -172,13 +173,12 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { if (root.children.size() > 0) { root = root.children.get(0); int bestScore = root.getScore(); - //if (bestScore > currentScore || allowBadMoves) { + if (bestScore > currentScore || allowBadMoves) { actions = new LinkedList(root.abilities); combat = root.combat; - logger.debug("final score: " + bestScore); - //} else { - //System.out.println("[" + game.getPlayer(playerId).getName() + "][post] Action: not better score"); - //} + } else { + logger.debug("[" + game.getPlayer(playerId).getName() + "] no better score current: " + currentScore + " bestScore: " + bestScore ); + } } else { logger.debug("[" + game.getPlayer(playerId).getName() + "][post] Action: skip"); } @@ -198,21 +198,30 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } + // Condition to stop deeper simulation if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.isGameOver()) { val = GameStateEvaluator2.evaluate(playerId, game); - if (logger.isDebugEnabled()) { - StringBuilder sb = new StringBuilder("Add Action [").append(depth).append("] -- reached end state val = ").append(val); + if (logger.isTraceEnabled()) { + StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append(">"); SimulationNode2 logNode = node; - StringBuilder sb2 = new StringBuilder(" --> ").append(node.getAbilities().get(0).toString()); - while(logNode.getParent() != null) { + do { + sb.append(new StringBuilder(" <- ["+logNode.getDepth()+"]" + (logNode.getAbilities() != null ? logNode.getAbilities().toString():"[empty]"))); logNode = logNode.getParent(); - sb2.insert(0,"["+node.getDepth()+"] s:" + logNode.score).insert(0," (0/"+node.getAbilities().size()+" " + node.getAbilities().get(0).toString()).insert(0, ") --> "); - } - logger.debug(sb.append(sb2)); + } while((logNode.getParent() != null)); + logger.trace(sb); + } + } else if (node.getChildren().size() > 0) { + if (logger.isDebugEnabled()) { + StringBuilder sb = new StringBuilder("Add Action [").append(depth) + .append("] -- something added children ") + .append(node.getAbilities() != null ? node.getAbilities().toString():"null") + .append(" added children: ").append(node.getChildren().size()).append(" ("); + for (SimulationNode2 logNode: node.getChildren()) { + sb.append(logNode.getAbilities() != null ? logNode.getAbilities().toString():"null").append(", "); + } + sb.append(")"); + logger.debug(sb); } - } - else if (node.getChildren().size() > 0) { - logger.debug("Add Action -- something added children:" + node.getChildren().size()); val = minimaxAB(node, depth-1, alpha, beta); } else { @@ -263,14 +272,25 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player { } } else if (node.getChildren().size() > 0) { - logger.debug("Add Action -- trigger added children:" + node.getChildren().size()); + if (logger.isDebugEnabled()) { + StringBuilder sb = new StringBuilder("Add Action [").append(depth) + .append("] -- trigger ") + .append(node.getAbilities() != null ? node.getAbilities().toString():"null") + .append(" added children: ").append(node.getChildren().size()).append(" ("); + for (SimulationNode2 logNode: node.getChildren()) { + sb.append(logNode.getAbilities() != null ? logNode.getAbilities().toString():"null").append(", "); + } + sb.append(")"); + logger.debug(sb); + } + val = minimaxAB(node, depth, alpha, beta); } else { val = simulatePriority(node, game, depth, alpha, beta); } } - + node.setScore(val); // test logger.trace("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); return val; diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/Config2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/Config2.java index 7483e7dad46..6123868991c 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/Config2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/Config2.java @@ -62,7 +62,7 @@ public class Config2 { p.load(new FileInputStream(propertiesFile)); } else { // p.setProperty("maxDepth", "10"); - p.setProperty("maxNodes", "5000"); + p.setProperty("maxNodes", "50000"); p.setProperty("evaluatorLifeFactor", "2"); p.setProperty("evaluatorPermanentFactor", "1"); p.setProperty("evaluatorCreatureFactor", "1"); 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 83fde106c2f..524bd0f4845 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 @@ -28,6 +28,9 @@ package mage.player.ai; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; @@ -47,8 +50,6 @@ import mage.players.Player; import mage.target.Target; import org.apache.log4j.Logger; -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; /** * @@ -123,23 +124,13 @@ public class SimulatedPlayer2 extends ComputerPlayer { options = filterOptions(game, options, ability, suggested); options = optimizeOptions(game, options, ability); if (options.isEmpty()) { - if (ability.getManaCosts().getVariableCosts().size() > 0) { - simulateVariableCosts(ability, game); - } - else { - allActions.add(ability); - } + allActions.add(ability); // simulateAction(game, previousActions, ability); } else { // ExecutorService simulationExecutor = Executors.newFixedThreadPool(4); for (Ability option: options) { - if (ability.getManaCosts().getVariableCosts().size() > 0) { - simulateVariableCosts(option, game); - } - else { - allActions.add(option); - } + allActions.add(option); // SimulationWorker worker = new SimulationWorker(game, this, previousActions, option); // simulationExecutor.submit(worker); } @@ -149,6 +140,53 @@ public class SimulatedPlayer2 extends ComputerPlayer { } } + @Override + protected void addVariableXOptions(List options, Ability ability, int targetNum, Game game) { + // calculate the mana that can be used for the x part + int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().convertedManaCost(); + + Card card = game.getCard(ability.getSourceId()); + if (card != null && numAvailable > 0) { + // check if variable mana costs is included and get the multiplier + VariableManaCost variableManaCost = null; + for (ManaCost cost: ability.getManaCostsToPay()) { + if (cost instanceof VariableManaCost && !cost.isPaid()) { + variableManaCost = (VariableManaCost) cost; + break; // only one VariableManCost per spell (or is it possible to have more?) + } + } + if (variableManaCost != null) { + int multiplier = variableManaCost.getMultiplier(); + + for (int mana = 0; mana <= numAvailable; mana++) { + if (mana % multiplier == 0) { // use only values dependant from muliplier + int xAmount = mana / multiplier; + Ability newAbility = ability.copy(); + VariableManaCost varCost = null; + for (ManaCost cost: newAbility.getManaCostsToPay()) { + if (cost instanceof VariableManaCost && !cost.isPaid()) { + varCost = (VariableManaCost) cost; + break; // only one VariableManCost per spell (or is it possible to have more?) + } + } + // add the specific value for x + newAbility.getManaCostsToPay().add(new ManaCostsImpl(new StringBuilder("{").append(xAmount).append("}").toString())); + newAbility.getManaCostsToPay().setX(xAmount); + varCost.setPaid(); + card.adjustTargets(newAbility, game); + // add the different possible target option for the specific X value + if (newAbility.getTargets().getUnchosen().size() > 0) { + addTargetOptions(options, newAbility, targetNum, game); + } + } + + } + } + + } + + } + // protected void simulateAction(Game game, SimulatedAction previousActions, Ability action) { // List actions = new ArrayList(previousActions.getAbilities()); // actions.add(action); @@ -160,6 +198,14 @@ public class SimulatedPlayer2 extends ComputerPlayer { // } // } + /** + * if suggested abilities exist, return only those from playables + * + * @param game + * @param playables + * @param suggested + * @return + */ protected List filterAbilities(Game game, List playables, List suggested) { if (playables.isEmpty()) { return playables; @@ -258,31 +304,6 @@ public class SimulatedPlayer2 extends ComputerPlayer { return options; } - //add a generic mana cost for each amount possible - protected void simulateVariableCosts(Ability ability, Game game) { - int numAvailable = getAvailableManaProducers(game).size(); - // Start with X = {1} - for (int i = 1; i < numAvailable; i++) { - Ability newAbility = ability.copy(); - newAbility.getManaCostsToPay().add(new GenericManaCost(i)); - allActions.add(newAbility); - } - } - - @Override - public boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game) { - //simulateVariableCosts method adds a generic mana cost for each option - for (ManaCost manaCost: costs) { - if (manaCost instanceof GenericManaCost) { - cost.setPayment(manaCost.getPayment()); - logger.debug("simulating -- X = " + cost.getPayment().count()); - break; - } - } - cost.setPaid(); - return true; - } - public List addAttackers(Game game) { Map engagements = new HashMap(); //useful only for two player games - will only attack first opponent @@ -401,4 +422,6 @@ public class SimulatedPlayer2 extends ComputerPlayer { //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 3f85df795fc..3609687e0d4 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 @@ -1043,23 +1043,14 @@ public class ComputerPlayer> extends PlayerImpl i } @Override - public boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game) { - log.debug("playXMana"); - //put everything into X - for (Permanent perm: this.getAvailableManaProducers(game)) { - for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) { - activateAbility(ability, game); - } + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + log.debug("announceXMana"); + //TODO: improve this + int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().convertedManaCost(); + if (numAvailable < 0) { + numAvailable = 0; } - - // don't allow X=0 - if (getManaPool().count() == 0) { - return false; - } - - game.informPlayers(getName() + " payed " + cost.getPayment().count() + " for " + cost.getText()); - cost.setPaid(); - return true; + return numAvailable; } @Override @@ -1262,13 +1253,17 @@ public class ComputerPlayer> extends PlayerImpl i @Override public Mode chooseMode(Modes modes, Ability source, Game game) { log.debug("chooseMode"); + if (modes.getMode() != null) { + // mode was already set by the AI + return modes.getMode(); + } //TODO: improve this; return modes.values().iterator().next(); } @Override public TriggeredAbility chooseTriggeredAbility(List abilities, Game game) { - log.debug("chooseTriggeredAbility"); + log.debug("chooseTriggeredAbility: " + abilities.toString()); //TODO: improve this if (abilities.size() > 0) { return abilities.get(0); 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 e90cdac9888..249611718b9 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 @@ -472,20 +472,20 @@ public class HumanPlayer extends PlayerImpl { return true; } + /** + * Gets the amount of mana the player want to spent for a x spell + * @param min + * @param max + * @param message + * @param game + * @return + */ @Override - public boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game) { - updateGameStatePriority("playXMana", game); - game.firePlayXManaEvent(playerId, "Pay {X}: {X}=" + cost.getAmount()); - waitForResponse(); - if (response.getBoolean() != null) { - if (!response.getBoolean()) - return false; - game.informPlayers(getName() + " payed " + cost.getPayment().count() + " for " + cost.getText()); - cost.setPaid(); - } else if (response.getUUID() != null) { - playManaAbilities(game); - } - return true; + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + updateGameStatePriority("announceXMana", game); + game.fireGetAmountEvent(playerId, message, min, max); + waitForIntegerResponse(); + return response.getInteger(); } protected void playManaAbilities(Game game) { diff --git a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java index d771ac81a56..c77ed48997d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java @@ -362,24 +362,6 @@ public class RandomPlayer extends ComputerPlayer { return true; } - @Override - public boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game) { - for (Permanent perm: this.getAvailableManaProducers(game)) { - for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) { - if (rnd.nextBoolean()) - activateAbility(ability, game); - } - } - - // don't allow X=0 - if (getManaPool().count() == 0) { - return false; - } - - cost.setPaid(); - return true; - } - @Override public int chooseEffect(List rEffects, Game game) { return rnd.nextInt(rEffects.size()); diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 101bc0eb2fb..57f2f334b99 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -191,23 +191,26 @@ public abstract class AbilityImpl> implements Ability { // If the spell has a variable cost that will be paid as it's being cast (such as an {X} in // its mana cost; see rule 107.3), the player announces the value of that variable. // TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost - if (game.getPlayer(this.controllerId).isHuman()) { - // AI can't handle this yet. Uses old way of playXMana - VariableManaCost variableManaCost = null; - for (ManaCost cost: manaCostsToPay) { - if (cost instanceof VariableManaCost && !cost.isPaid()) { - variableManaCost = (VariableManaCost) cost; - break; // only one VariableManCost per spell (or is it possible to have more?) - } + VariableManaCost variableManaCost = null; + for (ManaCost cost: manaCostsToPay) { + if (cost instanceof VariableManaCost) { + variableManaCost = (VariableManaCost) cost; + break; // only one VariableManCost per spell (or is it possible to have more?) } - if (variableManaCost != null) { - int amount = game.getPlayer(this.controllerId).getAmount(variableManaCost.getMinX(), Integer.MAX_VALUE, "Announce the value for " + variableManaCost.getText(), game); - game.informPlayers(new StringBuilder(game.getPlayer(this.controllerId).getName()).append(" announced a value of ").append(amount).append(" for ").append(variableManaCost.getText()).toString()); - amount *= variableManaCost.getMultiplier(); - manaCostsToPay.add(new ManaCostsImpl(new StringBuilder("{").append(amount).append("}").toString())); - manaCostsToPay.setX(amount); + } + if (variableManaCost != null) { + int xValue; + if (!variableManaCost.isPaid()) { // should only happen for human players + if (!noMana) { + xValue = game.getPlayer(this.controllerId).announceXMana(variableManaCost.getMinX(), Integer.MAX_VALUE, "Announce the value for " + variableManaCost.getText(), game, this); + int amountMana = xValue * variableManaCost.getMultiplier(); + manaCostsToPay.add(new ManaCostsImpl(new StringBuilder("{").append(amountMana).append("}").toString())); + manaCostsToPay.setX(amountMana); + } variableManaCost.setPaid(); } + xValue = getManaCostsToPay().getX(); + game.informPlayers(new StringBuilder(game.getPlayer(this.controllerId).getName()).append(" announced a value of ").append(xValue).append(" for ").append(variableManaCost.getText()).toString()); } //20121001 - 601.2c @@ -230,7 +233,11 @@ public abstract class AbilityImpl> implements Ability { card.adjustTargets(this, game); } if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) { - logger.debug("activate failed - target"); + if (variableManaCost != null) { + game.informPlayers(new StringBuilder(card.getName()).append(": no valid targets with this value of X").toString()); + } else { + logger.debug("activate failed - target"); + } return false; } diff --git a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java index 2d9cc272875..1b986e32406 100644 --- a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java @@ -120,15 +120,16 @@ public class ManaCostsImpl extends ArrayList implements M else return false; } - for (ManaCost cost : this.getUnpaidVariableCosts()) { - VariableManaCost vCost = (VariableManaCost) cost; - while (!vCost.isPaid()) { - if (player.playXMana(vCost, (ManaCosts) this, game)) - vCost.assignPayment(game, ability, player.getManaPool()); - else - return false; - } - } + // no more needed because X costs are added to the cost during announcing of the X costs +// for (ManaCost cost : this.getUnpaidVariableCosts()) { +// VariableManaCost vCost = (VariableManaCost) cost; +// while (!vCost.isPaid()) { +// if (player.playXMana(vCost, (ManaCosts) this, game)) +// vCost.assignPayment(game, ability, player.getManaPool()); +// else +// return false; +// } +// } return true; } diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 088388e916e..41aaf02c743 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -228,7 +228,9 @@ public interface Player extends MageItem, Copyable { boolean choose(Outcome outcome, Choice choice, Game game); boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game); boolean playMana(ManaCost unpaid, Game game); - boolean playXMana(VariableManaCost cost, ManaCosts costs, Game game); + // set the value for X spells and abilities + int announceXMana(int min, int max, String message, Game game, Ability ability); + int chooseEffect(List rEffects, Game game); TriggeredAbility chooseTriggeredAbility(List abilities, Game game); Mode chooseMode(Modes modes, Ability source, Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 9b125f532b0..31241e46069 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -1387,6 +1387,13 @@ public abstract class PlayerImpl> implements Player, Ser return playable; } + /** + * Only used for AIs + * + * @param ability + * @param game + * @return + */ @Override public List getPlayableOptions(Ability ability, Game game) { List options = new ArrayList(); @@ -1394,7 +1401,12 @@ public abstract class PlayerImpl> implements Player, Ser if (ability.isModal()) { addModeOptions(options, ability, game); } else if (ability.getTargets().getUnchosen().size() > 0) { - addTargetOptions(options, ability, 0, game); + // TODO: Handle other variable costs than mana costs + if (ability.getManaCosts().getVariableCosts().size() > 0) { + addVariableXOptions(options, ability, 0, game); + } else { + addTargetOptions(options, ability, 0, game); + } } else if (ability.getChoices().getUnchosen().size() > 0) { addChoiceOptions(options, ability, 0, game); } else if (ability.getCosts().getTargets().getUnchosen().size() > 0) { @@ -1408,19 +1420,27 @@ public abstract class PlayerImpl> implements Player, Ser for (Mode mode: option.getModes().values()) { Ability newOption = option.copy(); newOption.getModes().setMode(mode); - if (option.getTargets().getUnchosen().size() > 0) { - addTargetOptions(options, option, 0, game); - } else if (option.getChoices().getUnchosen().size() > 0) { - addChoiceOptions(options, option, 0, game); - } else if (option.getCosts().getTargets().getUnchosen().size() > 0) { - addCostTargetOptions(options, option, 0, game); + if (newOption.getTargets().getUnchosen().size() > 0) { + if (newOption.getManaCosts().getVariableCosts().size() > 0) { + addVariableXOptions(options, newOption, 0, game); + } else { + addTargetOptions(options, newOption, 0, game); + } + } else if (newOption.getChoices().getUnchosen().size() > 0) { + addChoiceOptions(options, newOption, 0, game); + } else if (newOption.getCosts().getTargets().getUnchosen().size() > 0) { + addCostTargetOptions(options, newOption, 0, game); } else { options.add(newOption); } } } - private void addTargetOptions(List options, Ability option, int targetNum, Game game) { + protected void addVariableXOptions(List options, Ability option, int targetNum, Game game) { + addTargetOptions(options, option, targetNum, game); + } + + protected void addTargetOptions(List options, Ability option, int targetNum, Game game) { for (Target target: option.getTargets().getUnchosen().get(targetNum).getTargetOptions(option, game)) { Ability newOption = option.copy(); if (target instanceof TargetAmount) {