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 f7c3ed93023..a292a99e72e 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 @@ -130,7 +130,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { if (log.isDebugEnabled()) { - log.debug("chooseTarget: " + outcome.toString() + ':' + target.toString()); + log.debug("choose: " + outcome.toString() + ':' + target.toString()); } // controller hints: @@ -205,21 +205,30 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (target.getOriginalTarget() instanceof TargetPermanent) { - TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget(); + + FilterPermanent filter = null; + if (target.getOriginalTarget().getFilter() instanceof FilterPermanent) { + filter = (FilterPermanent) target.getOriginalTarget().getFilter(); + } + if (filter == null) { + throw new IllegalStateException("Unsupported permanent filter in computer's choose method: " + + target.getOriginalTarget().getClass().getCanonicalName()); + } + List targets; if (outcome.isCanTargetAll()) { - targets = threats(null, sourceId, origTarget.getFilter(), game, target.getTargets()); + targets = threats(null, sourceId, filter, game, target.getTargets()); } else { if (outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, origTarget.getFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, filter, game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, origTarget.getFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, filter, game, target.getTargets()); } if (targets.isEmpty() && target.isRequired()) { if (!outcome.isGood()) { - targets = threats(abilityControllerId, sourceId, origTarget.getFilter(), game, target.getTargets()); + targets = threats(abilityControllerId, sourceId, filter, game, target.getTargets()); } else { - targets = threats(randomOpponentId, sourceId, origTarget.getFilter(), game, target.getTargets()); + targets = threats(randomOpponentId, sourceId, filter, game, target.getTargets()); } } } @@ -429,8 +438,17 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (target.getOriginalTarget() instanceof TargetCardInExile) { List alreadyTargeted = target.getTargets(); - TargetCard originalTarget = (TargetCard) target.getOriginalTarget(); - List cards = game.getExile().getCards(originalTarget.getFilter(), game); + + FilterCard filter = null; + if (target.getOriginalTarget().getFilter() instanceof FilterCard) { + filter = (FilterCard) target.getOriginalTarget().getFilter(); + } + if (filter == null) { + throw new IllegalStateException("Unsupported exile target filter in computer's choose method: " + + target.getOriginalTarget().getClass().getCanonicalName()); + } + + List cards = game.getExile().getCards(filter, game); while (!cards.isEmpty()) { Card card = pickTarget(abilityControllerId, cards, outcome, target, null, game); if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { @@ -461,10 +479,23 @@ public class ComputerPlayer extends PlayerImpl implements Player { if (!required) { return false; } - throw new IllegalStateException("TargetSource wasn't handled. class: " + target.getClass().toString()); + throw new IllegalStateException("TargetSource wasn't handled in computer's choose method: " + target.getClass().getCanonicalName()); } - throw new IllegalStateException("Target wasn't handled. class: " + target.getClass().toString()); + if (target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard) { + Cards cards = new CardsImpl(possibleTargets); + List possibleCards = new ArrayList<>(cards.getCards(game)); + while (!target.isChosen() && !possibleCards.isEmpty()) { + Card pick = pickTarget(abilityControllerId, possibleCards, outcome, target, null, game); + if (pick != null) { + target.addTarget(pick.getId(), null, game); + possibleCards.remove(pick); + } + } + return target.isChosen(); + } + + throw new IllegalStateException("Target wasn't handled in computer's choose method: " + target.getClass().getCanonicalName()); } //end of choose method @Override @@ -578,8 +609,19 @@ public class ComputerPlayer extends PlayerImpl implements Player { // TODO: implemented findBestPlayerTargets // TODO: add findBest*Targets for all target types if (target.getOriginalTarget() instanceof TargetPermanent) { - TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget(); - findBestPermanentTargets(outcome, abilityControllerId, sourceId, origTarget.getFilter(), + + FilterPermanent filter = null; + if (target.getOriginalTarget().getFilter() instanceof FilterPermanent) { + filter = (FilterPermanent) target.getOriginalTarget().getFilter(); + } else if (target.getOriginalTarget().getFilter() instanceof FilterPermanentOrSuspendedCard) { + filter = ((FilterPermanentOrSuspendedCard) target.getOriginalTarget().getFilter()).getPermanentFilter(); + } + if (filter == null) { + throw new IllegalStateException("Unsupported permanent filter in computer's chooseTarget method: " + + target.getOriginalTarget().getClass().getCanonicalName()); + } + + findBestPermanentTargets(outcome, abilityControllerId, sourceId, filter, game, target, goodList, badList, allList); // use good list all the time and add maximum targets @@ -921,10 +963,20 @@ public class ComputerPlayer extends PlayerImpl implements Player { } if (target.getOriginalTarget() instanceof TargetCardInExile) { + + FilterCard filter = null; + if (target.getOriginalTarget().getFilter() instanceof FilterCard) { + filter = (FilterCard) target.getOriginalTarget().getFilter(); + } + if (filter == null) { + throw new IllegalStateException("Unsupported exile target filter in computer's chooseTarget method: " + + target.getOriginalTarget().getClass().getCanonicalName()); + } + List cards = new ArrayList<>(); for (UUID uuid : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) { Card card = game.getCard(uuid); - if (card != null) { + if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { cards.add(card); } } @@ -968,7 +1020,20 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } - throw new IllegalStateException("Target wasn't handled. class:" + target.getClass().toString()); + if (target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard) { + Cards cards = new CardsImpl(possibleTargets); + List possibleCards = new ArrayList<>(cards.getCards(game)); + while (!target.isChosen() && !possibleCards.isEmpty()) { + Card pick = pickTarget(abilityControllerId, possibleCards, outcome, target, source, game); + if (pick != null) { + target.addTarget(pick.getId(), source, game); + possibleCards.remove(pick); + } + } + return target.isChosen(); + } + + throw new IllegalStateException("Target wasn't handled in computer's chooseTarget method: " + target.getClass().getCanonicalName()); } //end of chooseTarget method protected Card pickTarget(UUID abilityControllerId, List cards, Outcome outcome, Target target, Ability source, Game game) { @@ -1996,7 +2061,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { // mode was already set by the AI return modes.getMode(); } - //TODO: improve this; + + // spell modes simulated by AI, see addModeOptions + // trigger modes chooses here + // TODO: add AI support to select best modes, current code uses first valid mode AvailableMode: for (Mode mode : modes.getAvailableModes(source, game)) { for (UUID selectedModeId : modes.getSelectedModes()) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tsr/ShivanSandMageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tsr/ShivanSandMageTest.java new file mode 100644 index 00000000000..3256fac646b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tsr/ShivanSandMageTest.java @@ -0,0 +1,121 @@ +package org.mage.test.cards.single.tsr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class ShivanSandMageTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_ModeOne_Manual() { + // When Shivan Sand-Mage enters the battlefield, choose one — + // • Remove two time counters from target permanent or suspended card. + // • Put two time counters on target permanent with a time counter on it or suspended card. + addCard(Zone.HAND, playerA, "Shivan Sand-Mage", 1); // {2}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shivan Sand-Mage"); + setModeChoice(playerA, "1"); + addTarget(playerA, "Grizzly Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Shivan Sand-Mage", 1); + } + + @Test + public void test_ModeOne_AI() { + // When Shivan Sand-Mage enters the battlefield, choose one — + // • Remove two time counters from target permanent or suspended card. + // • Put two time counters on target permanent with a time counter on it or suspended card. + addCard(Zone.HAND, playerA, "Shivan Sand-Mage", 1); // {2}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + // AI must play card + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Shivan Sand-Mage", 1); + } + + @Test + public void test_ModeTwo_Manual() { + // When Shivan Sand-Mage enters the battlefield, choose one — + // • Remove two time counters from target permanent or suspended card. + // • Put two time counters on target permanent with a time counter on it or suspended card. + addCard(Zone.HAND, playerA, "Shivan Sand-Mage", 1); // {2}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + // + // Suspend 3—{1}{W} + addCard(Zone.HAND, playerA, "Duskrider Peregrine", 1); // {5}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // prepare suspended card + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}", 2); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend 3"); + checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", 1); + checkCardCounters("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", CounterType.TIME, 3); + + // add +2 time counters + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shivan Sand-Mage"); + setModeChoice(playerA, "2"); + addTarget(playerA, "Duskrider Peregrine"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkCardCounters("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", CounterType.TIME, 3 + 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Shivan Sand-Mage", 1); + } + + @Test + public void test_ModeTwo_AI() { + // When Shivan Sand-Mage enters the battlefield, choose one — + // • Remove two time counters from target permanent or suspended card. + // • Put two time counters on target permanent with a time counter on it or suspended card. + addCard(Zone.HAND, playerA, "Shivan Sand-Mage", 1); // {2}{R}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + // + // Suspend 3—{1}{W} + addCard(Zone.HAND, playerA, "Duskrider Peregrine", 1); // {5}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // prepare suspended card + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}", 2); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suspend 3"); + checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", 1); + checkCardCounters("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", CounterType.TIME, 3); + + // must add +2 time counters + // due to AI limitation with ETB's modes - it checks only target support + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shivan Sand-Mage"); + setModeChoice(playerA, "2"); + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkCardCounters("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Duskrider Peregrine", CounterType.TIME, 3 + 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Shivan Sand-Mage", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 4411373ea3c..be12fae6908 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2419,13 +2419,13 @@ public class TestPlayer implements Player { if (target.getOriginalTarget() instanceof TargetCardInExile || target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard) { - FilterCard filterCard = null; + FilterCard filter = null; if (target.getOriginalTarget().getFilter() instanceof FilterCard) { - filterCard = (FilterCard) target.getOriginalTarget().getFilter(); + filter = (FilterCard) target.getOriginalTarget().getFilter(); } else if (target.getOriginalTarget().getFilter() instanceof FilterPermanentOrSuspendedCard) { - filterCard = ((FilterPermanentOrSuspendedCard) target.getOriginalTarget().getFilter()).getCardFilter(); + filter = ((FilterPermanentOrSuspendedCard) target.getOriginalTarget().getFilter()).getCardFilter(); } - if (filterCard == null) { + if (filter == null) { Assert.fail("Unsupported exile target filter in TestPlayer: " + target.getOriginalTarget().getClass().getCanonicalName()); } @@ -2435,7 +2435,7 @@ public class TestPlayer implements Player { String[] targetList = targetDefinition.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { - for (Card card : game.getExile().getCards(filterCard, game)) { + for (Card card : game.getExile().getCards(filter, game)) { if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { target.addTarget(card.getId(), source, game);