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 new file mode 100644 index 0000000000..340b290b97 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -0,0 +1,656 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.player.ai; + +import mage.Constants.Outcome; +import mage.Constants.PhaseStep; +import mage.Constants.RangeOfInfluence; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.SearchEffect; +import mage.cards.Card; +import mage.cards.Cards; +import mage.choices.Choice; +import mage.filter.FilterAbility; +import mage.game.Game; +import mage.game.combat.Combat; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; +import mage.game.turn.*; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetCard; +import mage.util.Logging; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author nantuko + */ +public class ComputerPlayer6 extends ComputerPlayer implements Player { + + private static final transient Logger logger = Logging.getLogger(ComputerPlayer6.class.getName()); + private static final ExecutorService pool = Executors.newFixedThreadPool(1); + + protected int maxDepth; + protected int maxNodes; + protected LinkedList actions = new LinkedList(); + protected List targets = new ArrayList(); + protected List choices = new ArrayList(); + protected Combat combat; + protected int currentScore; + protected SimulationNode2 root; + + public ComputerPlayer6(String name, RangeOfInfluence range) { + super(name, range); + maxDepth = Config2.maxDepth; + maxNodes = Config2.maxNodes; + } + + public ComputerPlayer6(final ComputerPlayer6 player) { + super(player); + this.maxDepth = player.maxDepth; + this.currentScore = player.currentScore; + if (player.combat != null) + this.combat = player.combat.copy(); + for (Ability ability: player.actions) { + actions.add(ability); + } + for (UUID targetId: player.targets) { + targets.add(targetId); + } + for (String choice: player.choices) { + choices.add(choice); + } + } + + @Override + public ComputerPlayer6 copy() { + return new ComputerPlayer6(this); + } + + @Override + public void priority(Game game) { + logState(game); + game.firePriorityEvent(playerId); + switch (game.getTurn().getStepType()) { + case UPKEEP: + case DRAW: + pass(); + break; + case PRECOMBAT_MAIN: + case POSTCOMBAT_MAIN: + if (game.getActivePlayerId().equals(playerId)) { + Player player = game.getPlayer(playerId); + System.out.println("Turn::"+game.getTurnNum()); + System.out.println("[" + game.getPlayer(playerId).getName() + "] " + game.getTurn().getStepType().name() +", life=" + player.getLife()); + String s = "["; + for (Card card : player.getHand().getCards(game)) { + s += card.getName() + ";"; + } + s += "]"; + System.out.println("Hand: " + s); + s = "["; + for (Permanent permanent : game.getBattlefield().getAllPermanents()) { + if (permanent.getOwnerId().equals(player.getId())) { + s += permanent.getName() + ";"; + } + } + s += "]"; + System.out.println("Permanents: " + s); + } + if (actions.size() == 0) { + calculateActions(game); + } + act(game); + break; + case BEGIN_COMBAT: + case DECLARE_ATTACKERS: + case DECLARE_BLOCKERS: + case COMBAT_DAMAGE: + case END_COMBAT: + case END_TURN: + pass(); + break; + case CLEANUP: + pass(); + break; + } + } + + protected void act(Game game) { + if (actions == null || actions.size() == 0) + pass(); + else { + boolean usedStack = false; + while (actions.peek() != null) { + Ability ability = actions.poll(); + System.out.println("[" + game.getPlayer(playerId).getName() + "] Action: " + ability.toString()); + this.activateAbility((ActivatedAbility) ability, game); + if (ability.isUsesStack()) + usedStack = true; + } + if (usedStack) + pass(); + } + } + + protected void calculateActions(Game game) { + currentScore = GameStateEvaluator2.evaluate(playerId, game); + if (!getNextAction(game)) { + Game sim = createSimulation(game); + SimulationNode2.resetCount(); + root = new SimulationNode2(sim, maxDepth, playerId); + logger.info("simulating actions"); + addActionsTimed(new FilterAbility()); + if (root.children.size() > 0) { + root = root.children.get(0); + int bestScore = GameStateEvaluator2.evaluate(playerId, root.getGame()); + if (bestScore > currentScore) { + actions = new LinkedList(root.abilities); + combat = root.combat; + } + } + } + } + + protected boolean getNextAction(Game game) { + if (root != null && root.children.size() > 0) { + SimulationNode2 test = root; + root = root.children.get(0); + while (root.children.size() > 0 && !root.playerId.equals(playerId)) { + test = root; + root = root.children.get(0); + } + logger.info("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue); + if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue() == test.gameValue) { + logger.info("simulating -- continuing previous action chain"); + actions = new LinkedList(root.abilities); + combat = root.combat; + return true; + } + else { + return false; + } + } + return false; + } + + protected int minimaxAB(SimulationNode2 node, FilterAbility filter, int depth, int alpha, int beta) { + UUID currentPlayerId = node.getGame().getPlayerList().get(); + SimulationNode2 bestChild = null; + for (SimulationNode2 child: node.getChildren()) { + if (alpha >= beta) { + logger.info("alpha beta pruning"); + break; + } + if (SimulationNode2.nodeCount > maxNodes) { + logger.info("simulating -- reached end-state, count=" + SimulationNode2.nodeCount); + break; + } + int val = addActions(child, filter, depth-1, alpha, beta); + if (!currentPlayerId.equals(playerId)) { + if (val < beta) { + beta = val; + bestChild = child; + if (node.getCombat() == null) + node.setCombat(child.getCombat()); + } + } + else { + if (val > alpha) { + alpha = val; + bestChild = child; + if (node.getCombat() == null) + node.setCombat(child.getCombat()); + } + } + } + node.children.clear(); + if (bestChild != null) + node.children.add(bestChild); + if (!currentPlayerId.equals(playerId)) { + //logger.info("returning minimax beta: " + beta); + return beta; + } + else { + //logger.info("returning minimax alpha: " + alpha); + return alpha; + } + } + + protected SearchEffect getSearchEffect(StackAbility ability) { + for (Effect effect: ability.getEffects()) { + if (effect instanceof SearchEffect) { + return (SearchEffect) effect; + } + } + return null; + } + + protected void resolve(SimulationNode2 node, int depth, Game game) { + StackObject ability = game.getStack().pop(); + if (ability instanceof StackAbility) { + SearchEffect effect = getSearchEffect((StackAbility) ability); + if (effect != null && ability.getControllerId().equals(playerId)) { + Target target = effect.getTarget(); + if (!target.doneChosing()) { + for (UUID targetId: target.possibleTargets(ability.getSourceId(), ability.getControllerId(), game)) { + Game sim = game.copy(); + StackAbility newAbility = (StackAbility) ability.copy(); + SearchEffect newEffect = getSearchEffect((StackAbility) newAbility); + newEffect.getTarget().addTarget(targetId, newAbility, sim); + sim.getStack().push(newAbility); + SimulationNode2 newNode = new SimulationNode2(sim, depth, ability.getControllerId()); + node.children.add(newNode); + newNode.getTargets().add(targetId); + logger.fine("simulating search -- node#: " + SimulationNode2.getCount() + "for player: " + sim.getPlayer(ability.getControllerId()).getName()); + } + return; + } + } + } + //logger.info("simulating resolve "); + ability.resolve(game); + game.applyEffects(); + game.getPlayers().resetPassed(); + game.getPlayerList().setCurrent(game.getActivePlayerId()); + } + + protected void addActionsTimed(final FilterAbility filter) { + FutureTask task = new FutureTask(new Callable() { + public Integer call() throws Exception + { + return addActions(root, filter, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + }); + pool.execute(task); + try { + task.get(Config2.maxThinkSeconds, TimeUnit.MINUTES); + } catch (TimeoutException e) { + logger.info("simulating - timed out"); + task.cancel(true); + } catch (ExecutionException e) { + e.printStackTrace(); + task.cancel(true); + } catch (InterruptedException e) { + e.printStackTrace(); + task.cancel(true); + } + } + + protected int addActions(SimulationNode2 node, FilterAbility filter, int depth, int alpha, int beta) { + logger.fine("addActions: " + depth + ", alpha=" + alpha + ", beta=" + beta); + Game game = node.getGame(); + int val; + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + logger.info("interrupted"); + val = GameStateEvaluator2.evaluate(playerId, game); + return val; + } + if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.isGameOver()) { + logger.fine("simulating -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth); + val = GameStateEvaluator2.evaluate(playerId, game); + return val; + } + else if (node.getChildren().size() > 0) { + logger.fine("simulating -- somthing added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, depth-1, alpha, beta); + return val; + } + else { + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- alpha: " + alpha + " beta: " + beta + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + (node.getPlayerId().equals(playerId) ? "yes" : "no")); + if (allPassed(game)) { + if (!game.getStack().isEmpty()) { + resolve(node, depth, game); + } + else { + game.getPlayers().resetPassed(); + playNext(game, game.getActivePlayerId(), node); + } + } + + if (game.isGameOver()) { + val = GameStateEvaluator2.evaluate(playerId, game); + } + else if (node.getChildren().size() > 0) { + //declared attackers or blockers or triggered abilities + logger.fine("simulating -- attack/block/trigger added children:" + node.getChildren().size()); + val = minimaxAB(node, filter, depth-1, alpha, beta); + } + else { + val = simulatePriority(node, game, filter, depth, alpha, beta); + } + } + + if (logger.isLoggable(Level.FINE)) + logger.fine("returning -- score: " + val + " depth:" + depth + " step:" + game.getTurn().getStepType() + " for player:" + game.getPlayer(node.getPlayerId()).getName()); + return val; + + } + + protected int simulatePriority(SimulationNode2 node, Game game, FilterAbility filter, int depth, int alpha, int beta) { + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + logger.info("interrupted"); + return GameStateEvaluator2.evaluate(playerId, game); + } + node.setGameValue(game.getState().getValue()); + SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get()); + //logger.info("simulating -- player " + currentPlayer.getName()); + SimulationNode2 bestNode = null; + List allActions = currentPlayer.simulatePriority(game, filter); + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- adding " + allActions.size() + " children:" + allActions); + for (Ability action: allActions) { + Game sim = game.copy(); + if (sim.getPlayer(currentPlayer.getId()).activateAbility((ActivatedAbility) action.copy(), sim)) { + sim.applyEffects(); + if (!sim.isGameOver() && action.isUsesStack()) { + // only pass if the last action uses the stack + sim.getPlayer(currentPlayer.getId()).pass(); + sim.getPlayerList().getNext(); + } + SimulationNode2 newNode = new SimulationNode2(sim, action, depth, currentPlayer.getId()); + if (logger.isLoggable(Level.FINE)) + logger.fine("simulating -- node #:" + SimulationNode2.getCount() + " actions:" + action); + sim.checkStateAndTriggered(); + int val = addActions(newNode, filter, depth-1, alpha, beta); + if (!currentPlayer.getId().equals(playerId)) { + if (val < beta) { + beta = val; + bestNode = newNode; + node.setCombat(newNode.getCombat()); + } + } + else { + if (val > alpha) { + alpha = val; + bestNode = newNode; + node.setCombat(newNode.getCombat()); + if (node.getTargets().size() > 0) + targets = node.getTargets(); + if (node.getChoices().size() > 0) + choices = node.getChoices(); + } + } + if (alpha >= beta) { + //logger.info("simulating -- pruning"); + break; + } + if (SimulationNode2.nodeCount > maxNodes) { + logger.fine("simulating -- reached end-state"); + break; + } + } + } + if (bestNode != null) { + node.children.clear(); + node.children.add(bestNode); + } + if (!currentPlayer.getId().equals(playerId)) { + //logger.info("returning priority beta: " + beta); + return beta; + } + else { + //logger.info("returning priority alpha: " + alpha); + return alpha; + } + } + + protected boolean allPassed(Game game) { + for (Player player: game.getPlayers().values()) { + if (!player.isPassed() && !player.hasLost() && !player.hasLeft()) + return false; + } + return true; + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + if (choices.size() == 0) + return super.choose(outcome, choice, game); + if (!choice.isChosen()) { + for (String achoice: choices) { + choice.setChoice(achoice); + if (choice.isChosen()) { + choices.clear(); + return true; + } + } + return false; + } + return true; + } + + @Override + public boolean chooseTarget(Cards cards, TargetCard target, Ability source, Game game) { + if (targets.size() == 0) + return super.chooseTarget(cards, target, source, game); + if (!target.doneChosing()) { + for (UUID targetId: targets) { + target.addTarget(targetId, source, game); + if (target.doneChosing()) { + targets.clear(); + return true; + } + } + return false; + } + return true; + } + + @Override + public boolean choose(Cards cards, TargetCard target, Game game) { + if (targets.size() == 0) + return super.choose(cards, target, game); + if (!target.doneChosing()) { + for (UUID targetId: targets) { + target.add(targetId, game); + if (target.doneChosing()) { + targets.clear(); + return true; + } + } + return false; + } + return true; + } + + public void playNext(Game game, UUID activePlayerId, SimulationNode2 node) { + boolean skip = false; + while (true) { + Phase currentPhase = game.getPhase(); + if (!skip) + currentPhase.getStep().endStep(game, activePlayerId); + game.applyEffects(); + switch (currentPhase.getStep().getType()) { + case UNTAP: + game.getPhase().setStep(new UpkeepStep()); + break; + case UPKEEP: + game.getPhase().setStep(new DrawStep()); + break; + case DRAW: + game.getTurn().setPhase(new PreCombatMainPhase()); + game.getPhase().setStep(new PreCombatMainStep()); + break; + case PRECOMBAT_MAIN: + game.getTurn().setPhase(new CombatPhase()); + game.getPhase().setStep(new BeginCombatStep()); + break; + case BEGIN_COMBAT: + game.getPhase().setStep(new DeclareAttackersStep()); + break; + case DECLARE_ATTACKERS: + game.getPhase().setStep(new DeclareBlockersStep()); + break; + case DECLARE_BLOCKERS: + game.getPhase().setStep(new CombatDamageStep(true)); + break; + case COMBAT_DAMAGE: + if (((CombatDamageStep)currentPhase.getStep()).getFirst()) + game.getPhase().setStep(new CombatDamageStep(false)); + else + game.getPhase().setStep(new EndOfCombatStep()); + break; + case END_COMBAT: + game.getTurn().setPhase(new PostCombatMainPhase()); + game.getPhase().setStep(new PostCombatMainStep()); + break; + case POSTCOMBAT_MAIN: + game.getTurn().setPhase(new EndPhase()); + game.getPhase().setStep(new EndStep()); + break; + case END_TURN: + game.getPhase().setStep(new CleanupStep()); + break; + case CLEANUP: + game.getPhase().getStep().beginStep(game, activePlayerId); + if (!game.checkStateAndTriggered() && !game.isGameOver()) { + game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); + game.getTurn().setPhase(new BeginningPhase()); + game.getPhase().setStep(new UntapStep()); + } + } + if (!game.getStep().skipStep(game, game.getActivePlayerId())) { + if (game.getTurn().getStepType() == PhaseStep.DECLARE_ATTACKERS) { + game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, activePlayerId)); + if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) { + for (Combat engagement: ((SimulatedPlayer2)game.getPlayer(activePlayerId)).addAttackers(game)) { + Game sim = game.copy(); + UUID defenderId = game.getOpponents(playerId).iterator().next(); + for (CombatGroup group: engagement.getGroups()) { + for (UUID attackerId: group.getAttackers()) { + sim.getPlayer(activePlayerId).declareAttacker(attackerId, defenderId, sim); + } + } + sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, playerId, playerId)); + SimulationNode2 newNode = new SimulationNode2(sim, node.getDepth()-1, activePlayerId); + logger.info("simulating -- node #:" + SimulationNode2.getCount() + " declare attakers"); + newNode.setCombat(sim.getCombat()); + node.children.add(newNode); + } + } + } + else if (game.getTurn().getStepType() == PhaseStep.DECLARE_BLOCKERS) { + game.fireEvent(new GameEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_PRE, null, null, activePlayerId)); + if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, activePlayerId, activePlayerId))) { + for (UUID defenderId: game.getCombat().getDefenders()) { + //check if defender is being attacked + if (game.getCombat().isAttacked(defenderId, game)) { + for (Combat engagement: ((SimulatedPlayer2)game.getPlayer(defenderId)).addBlockers(game)) { + Game sim = game.copy(); + for (CombatGroup group: engagement.getGroups()) { + for (UUID blockerId: group.getBlockers()) { + group.addBlocker(blockerId, defenderId, sim); + } + } + sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); + SimulationNode2 newNode = new SimulationNode2(sim, node.getDepth()-1, defenderId); + logger.info("simulating -- node #:" + SimulationNode2.getCount() + " declare blockers"); + newNode.setCombat(sim.getCombat()); + node.children.add(newNode); + } + } + } + } + } + else { + game.getStep().beginStep(game, activePlayerId); + } + if (game.getStep().getHasPriority()) + break; + } + else { + skip = true; + } + } + game.checkStateAndTriggered(); + } + + @Override + public void selectAttackers(Game game) { + logger.info("selectAttackers"); + if (combat != null) { + UUID opponentId = game.getCombat().getDefenders().iterator().next(); + for (UUID attackerId: combat.getAttackers()) { + logger.info("declare attacker: " + game.getCard(attackerId).getName()); + this.declareAttacker(attackerId, opponentId, game); + } + } + } + + @Override + public void selectBlockers(Game game) { + logger.info("selectBlockers"); + if (combat != null && combat.getGroups().size() > 0) { + List groups = game.getCombat().getGroups(); + for (int i = 0; i < groups.size(); i++) { + if (i < combat.getGroups().size()) { + for (UUID blockerId: combat.getGroups().get(i).getBlockers()) { + this.declareBlocker(blockerId, groups.get(i).getAttackers().get(0), game); + } + } + } + } + } + + /** + * Copies game and replaces all players in copy with simulated players + * + * @param game + * @return a new game object with simulated players + */ + protected Game createSimulation(Game game) { + Game sim = game.copy(); + + for (Player copyPlayer: sim.getState().getPlayers().values()) { + Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()); + SimulatedPlayer2 newPlayer = new SimulatedPlayer2(copyPlayer.getId(), copyPlayer.getId().equals(playerId)); + newPlayer.restore(origPlayer); + sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer); + } + return sim; + } + +} diff --git a/Mage.Tests/config/config.xml b/Mage.Tests/config/config.xml index 763e09187a..7af45388f8 100644 --- a/Mage.Tests/config/config.xml +++ b/Mage.Tests/config/config.xml @@ -4,7 +4,7 @@ - + diff --git a/Mage.Tests/plugins/mage-player-ai-ma.jar b/Mage.Tests/plugins/mage-player-ai-ma.jar index 8792f2746e..6e895cae6c 100644 Binary files a/Mage.Tests/plugins/mage-player-ai-ma.jar and b/Mage.Tests/plugins/mage-player-ai-ma.jar differ diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java index e8fed69414..157e8001ca 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java @@ -1,6 +1,8 @@ package org.mage.test.serverside; import mage.Constants; +import mage.cards.Card; +import mage.cards.ExpansionSet; import mage.cards.decks.Deck; import mage.game.Game; import mage.game.GameException; @@ -11,42 +13,137 @@ import mage.sets.Sets; import org.junit.Test; import org.mage.test.serverside.base.MageTestBase; +import java.io.File; import java.io.FileNotFoundException; +import java.util.*; +import java.util.regex.Matcher; /** * @author ayratn */ public class PlayGameTest extends MageTestBase { + private List handCardsA = new ArrayList(); + private List handCardsB = new ArrayList(); + private List battlefieldCardsA = new ArrayList(); + private List battlefieldCardsB = new ArrayList(); + private List graveyardCardsA = new ArrayList(); + private List graveyardCardsB = new ArrayList(); + private List libraryCardsA = new ArrayList(); + private List libraryCardsB = new ArrayList(); + + private Map commandsA = new HashMap(); + private Map commandsB = new HashMap(); + @Test public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { Game game = new TwoPlayerDuel(Constants.MultiplayerAttackOption.LEFT, Constants.RangeOfInfluence.ALL); - Player player = createPlayer("computer1", "Computer - mad"); + Player computerA = createPlayer("ComputerA", "Computer - mad"); Deck deck = Deck.load(Sets.loadDeck("RB Aggro.dck")); if (deck.getCards().size() < 40) { throw new IllegalArgumentException("Couldn't load deck, deck side=" + deck.getCards().size()); } - game.addPlayer(player, deck); - game.loadCards(deck.getCards(), player.getId()); + game.addPlayer(computerA, deck); + game.loadCards(deck.getCards(), computerA.getId()); - Player player2 = createPlayer("computer2", "Computer - mad"); + Player computerB = createPlayer("ComputerB", "Computer - mad"); Deck deck2 = Deck.load(Sets.loadDeck("RB Aggro.dck")); if (deck2.getCards().size() < 40) { throw new IllegalArgumentException("Couldn't load deck, deck side=" + deck2.getCards().size()); } - game.addPlayer(player2, deck2); - game.loadCards(deck2.getCards(), player2.getId()); + game.addPlayer(computerB, deck2); + game.loadCards(deck2.getCards(), computerB.getId()); + + parseScenario("scenario1.txt"); + game.cheat(computerA.getId(), commandsA); + game.cheat(computerA.getId(), libraryCardsA, handCardsA, battlefieldCardsA, graveyardCardsA); + game.cheat(computerB.getId(), commandsB); + game.cheat(computerB.getId(), libraryCardsB, handCardsB, battlefieldCardsB, graveyardCardsB); long t1 = System.nanoTime(); - game.start(player.getId()); + game.start(computerA.getId(), true); long t2 = System.nanoTime(); logger.info("Winner: " + game.getWinner()); logger.info("Time: " + (t2 - t1) / 1000000 + " ms"); } + private void addCard(List cards, String name, int count) { + for (int i = 0; i < count; i++) { + Card card = Sets.findCard(name, true); + if (card == null) { + throw new IllegalArgumentException("Couldn't find a card for test: " + name); + } + cards.add(card); + } + } + + private void parseScenario(String filename) throws FileNotFoundException { + File f = new File(filename); + Scanner scanner = new Scanner(f); + try { + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if (line.startsWith("#")) continue; + Matcher m = pattern.matcher(line); + if (m.matches()) { + + String zone = m.group(1); + String nickname = m.group(2); + + if (nickname.equals("ComputerA") || nickname.equals("ComputerB")) { + List cards; + Constants.Zone gameZone; + if ("hand".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.HAND; + cards = nickname.equals("ComputerA") ? handCardsA : handCardsB; + } else if ("battlefield".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.BATTLEFIELD; + cards = nickname.equals("ComputerA") ? battlefieldCardsA : battlefieldCardsB; + } else if ("graveyard".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.GRAVEYARD; + cards = nickname.equals("ComputerA") ? graveyardCardsA : graveyardCardsB; + } else if ("library".equalsIgnoreCase(zone)) { + gameZone = Constants.Zone.LIBRARY; + cards = nickname.equals("ComputerA") ? libraryCardsA : libraryCardsB; + } else { + continue; // go parse next line + } + + String cardName = m.group(3); + Integer amount = Integer.parseInt(m.group(4)); + + if (cardName.equals("clear")) { + if (nickname.equals("ComputerA")) { + commandsA.put(gameZone, "clear"); + } else { + commandsB.put(gameZone, "clear"); + } + } else { + for (int i = 0; i < amount; i++) { + Card card = Sets.findCard(cardName, true); + if (card != null) { + cards.add(card); + } else { + logger.severe("Couldn't find a card: " + cardName); + logger.severe("line: " + line); + } + } + } + } else { + logger.warning("Unknown player: " + nickname); + } + } else { + logger.warning("Init string wasn't parsed: " + line); + } + } + } finally { + scanner.close(); + } + } + private Player createPlayer(String name, String playerType) { return PlayerFactory.getInstance().createPlayer(playerType, name, Constants.RangeOfInfluence.ALL); } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java index 56e319cb6c..c9a0700086 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestBase.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.FilenameFilter; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; /** * @author ayratn @@ -29,6 +30,8 @@ public class MageTestBase { private final static String pluginFolder = "plugins"; + protected Pattern pattern = Pattern.compile("([a-zA-Z]*):([\\w]*):([a-zA-Z ,\\-.!'\\d]*):([\\d]*)"); + @BeforeClass public static void init() { logger.info("Starting MAGE tests"); diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 50550e934d..b0b18e30d3 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -28,15 +28,14 @@ package mage.game; +import mage.Constants; import mage.game.match.MatchType; import mage.cards.Card; import mage.game.stack.SpellStack; import mage.MageObject; import java.io.Serializable; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; + import mage.Constants.MultiplayerAttackOption; import mage.Constants.RangeOfInfluence; import mage.Constants.Zone; @@ -61,6 +60,7 @@ import mage.game.permanent.Permanent; import mage.game.turn.Phase; import mage.game.turn.Step; import mage.game.turn.Turn; +import mage.players.Library; import mage.players.Player; import mage.players.PlayerList; import mage.players.Players; @@ -136,8 +136,9 @@ public interface Game extends MageItem, Serializable { public boolean replaceEvent(GameEvent event); //game play methods -// public void init(UUID choosingPlayerId); + //public void init(UUID choosingPlayerId); public void start(UUID choosingPlayerId); + public void start(UUID choosingPlayerId, boolean testMode); public void end(); public void mulligan(UUID playerId); public void quit(UUID playerId); @@ -156,4 +157,7 @@ public interface Game extends MageItem, Serializable { public void restoreState(); public void removeLastBookmark(); + // game cheats (for tests only) + public void cheat(UUID ownerId, Map commands); + public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard); } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index c841c089ce..09a801fe5c 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -47,11 +47,13 @@ import mage.game.events.*; import mage.game.events.TableEvent.EventType; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; import mage.game.stack.SpellStack; import mage.game.stack.StackObject; import mage.game.turn.Phase; import mage.game.turn.Step; import mage.game.turn.Turn; +import mage.players.Library; import mage.players.Player; import mage.players.PlayerList; import mage.players.Players; @@ -289,7 +291,12 @@ public abstract class GameImpl> implements Game, Serializa @Override public void start(UUID choosingPlayerId) { - init(choosingPlayerId); + start(choosingPlayerId, false); + } + + @Override + public void start(UUID choosingPlayerId, boolean testMode) { + init(choosingPlayerId, testMode); PlayerList players = state.getPlayerList(startingPlayerId); Player player = getPlayer(players.get()); while (!isGameOver()) { @@ -311,8 +318,12 @@ public abstract class GameImpl> implements Game, Serializa } protected void init(UUID choosingPlayerId) { + init(choosingPlayerId, false); + } + + protected void init(UUID choosingPlayerId, boolean testMode) { for (Player player: state.getPlayers().values()) { - player.init(this); + player.init(this, testMode); } fireInformEvent("game has started"); saveState(); @@ -347,7 +358,9 @@ public abstract class GameImpl> implements Game, Serializa for (UUID playerId: state.getPlayerList(startingPlayerId)) { Player player = getPlayer(playerId); player.setLife(this.getLife(), this); - player.drawCards(7, this); + if (!testMode) { + player.drawCards(7, this); + } } //20091005 - 103.4 @@ -918,4 +931,100 @@ public abstract class GameImpl> implements Game, Serializa public void resetLKI() { lki.clear(); } + + public void cheat(UUID ownerId, Map commands) { + if (commands != null) { + Player player = getPlayer(ownerId); + if (player != null) { + for (Map.Entry command : commands.entrySet()) { + switch (command.getKey()) { + case HAND: + if (command.getValue().equals("clear")) { + removeCards(player.getHand()); + } + break; + case LIBRARY: + if (command.getValue().equals("clear")) { + for (UUID card : player.getLibrary().getCardList()) { + gameCards.remove(card); + } + player.getLibrary().clear(); + } + break; + } + } + } + } + } + + private void removeCards(Cards cards) { + for (UUID card : cards) { + gameCards.remove(card); + } + cards.clear(); + } + + public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard) { + Player player = getPlayer(ownerId); + if (player != null) { + loadCards(ownerId, library); + loadCards(ownerId, hand); + loadCards(ownerId, battlefield); + loadCards(ownerId, graveyard); + + for (Card card : library) { + setZone(card.getId(), Zone.LIBRARY); + player.getLibrary().putOnTop(card, this); + } + for (Card card : hand) { + setZone(card.getId(), Zone.HAND); + player.getHand().add(card); + } + for (Card card : graveyard) { + setZone(card.getId(), Zone.GRAVEYARD); + player.getGraveyard().add(card); + } + List permanents = new ArrayList(); + for (Card card : battlefield) { + card.setOwnerId(ownerId); + PermanentCard permanent = new PermanentCard(card, ownerId); + getBattlefield().addPermanent(permanent); + } + applyEffects(); + } + } + + private void loadCards(UUID ownerId, List cards) { + if (cards == null) { + return; + } + Set set = new HashSet(); + for (Card card : cards) { + set.add(card); + } + loadCards(set, ownerId); + } + + public void replaceLibrary(List cardsDownToTop, UUID ownerId) { + Player player = getPlayer(ownerId); + if (player != null) { + for (UUID card : player.getLibrary().getCardList()) { + gameCards.remove(card); + } + player.getLibrary().clear(); + Set cards = new HashSet(); + for (Card card : cardsDownToTop) { + cards.add(card); + } + loadCards(cards, ownerId); + + for (Card card : cards) { + player.getLibrary().putOnTop(card, this); + } + } + } + + public void clearGraveyard(UUID playerId) { + + } } diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 9749716a47..a3fe979ca7 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -97,6 +97,7 @@ public interface Player extends MageItem, Copyable { public Set getInRange(); public void init(Game game); + public void init(Game game, boolean testMode); public void useDeck(Deck deck, Game game); public void reset(); public void shuffleLibrary(Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 48d1152861..f1b12a0adb 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -152,9 +152,16 @@ public abstract class PlayerImpl> implements Player, Ser @Override public void init(Game game) { + init(game, false); + } + + @Override + public void init(Game game, boolean testMode) { this.abort = false; - this.hand.clear(); - this.graveyard.clear(); + if (!testMode) { + this.hand.clear(); + this.graveyard.clear(); + } this.abilities.clear(); this.wins = false; this.loses = false;