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 44acf69fdb8..2908f7f5e7d 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 @@ -65,7 +65,7 @@ import java.util.Map.Entry; /** * suitable for two player games and some multiplayer games * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class ComputerPlayer extends PlayerImpl implements Player { @@ -440,6 +440,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { abilityControllerId = target.getAbilityController(); } + // TODO: improve to process multiple opponents instead random UUID randomOpponentId; if (target.getTargetController() != null) { randomOpponentId = getRandomOpponent(target.getTargetController(), game); @@ -936,81 +937,141 @@ public class ComputerPlayer extends PlayerImpl implements Player { @Override public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { + // TODO: make same code for chooseTarget (without filter and target type dependence) if (log.isDebugEnabled()) { log.debug("chooseTarget: " + outcome.toString() + ':' + target.toString()); } - UUID opponentId = game.getOpponents(playerId).iterator().next(); - if (target.getOriginalTarget() instanceof TargetCreatureOrPlayerAmount - || target.getOriginalTarget() instanceof TargetAnyTargetAmount) { - if (outcome == Outcome.Damage && game.getPlayer(opponentId).getLife() <= target.getAmountRemaining()) { - return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); - } - List targets; - if (outcome.isGood()) { - targets = threats(playerId, source.getSourceId(), StaticFilters.FILTER_PERMANENT_CREATURE, game, target.getTargets()); - } else { - targets = threats(opponentId, source.getSourceId(), StaticFilters.FILTER_PERMANENT_CREATURE, game, target.getTargets()); - } - for (Permanent permanent : targets) { - if (target.canTarget(getId(), permanent.getId(), source, game)) { - if (permanent.getToughness().getValue() <= target.getAmountRemaining()) { - return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game); - } - } - } - if (outcome.isGood() && target.canTarget(getId(), getId(), source, game)) { - return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); - } else if (target.canTarget(getId(), opponentId, source, game)) { - // no permanent target so take opponent - return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); - } else if (target.canTarget(getId(), playerId, source, game)) { - return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); - } - return false; + + UUID sourceId = source != null ? source.getSourceId() : null; + + // process multiple opponents by random + List opponents; + if (target.getTargetController() != null) { + opponents = new ArrayList<>(game.getOpponents(target.getTargetController())); + } else if (source != null && source.getControllerId() != null) { + opponents = new ArrayList<>(game.getOpponents(source.getControllerId())); + } else { + opponents = new ArrayList<>(game.getOpponents(getId())); } + Collections.shuffle(opponents); - if (target.getOriginalTarget() instanceof TargetCreatureOrPlaneswalkerAmount) { - List targets; - if (outcome.isGood()) { - targets = threats(playerId, source.getSourceId(), StaticFilters.FILTER_PERMANENT_CREATURE, game, target.getTargets()); - } else { - targets = threats(opponentId, source.getSourceId(), StaticFilters.FILTER_PERMANENT_CREATURE, game, target.getTargets()); - } - for (Permanent permanent : targets) { - if (target.canTarget(getId(), permanent.getId(), source, game)) { - if (permanent.getToughness().getValue() <= target.getAmountRemaining()) { - return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game); - } + List targets; + + // ONE KILL PRIORITY: player -> planeswalker -> creature + if (outcome == Outcome.Damage) { + // player kill + for (UUID opponentId : opponents) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null + && target.canTarget(getId(), opponentId, source, game) + && opponent.getLife() <= target.getAmountRemaining()) { + return tryAddTarget(target, opponentId, opponent.getLife(), source, game); } } - if (target.getFilter() instanceof FilterPermanent) { - targets = threats(null, source.getSourceId(), (FilterPermanent) target.getFilter(), game, target.getTargets()); - Permanent possibleTarget = null; - for (Permanent permanent : targets) { - if (target.canTarget(getId(), permanent.getId(), source, game)) { - if (permanent.isCreature()) { - if (permanent.getToughness().getValue() <= target.getAmountRemaining()) { - tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game); - } else { - possibleTarget = permanent; - } - } else if (permanent.isPlaneswalker()) { - int loy = permanent.getCounters(game).getCount(CounterType.LOYALTY); - if (loy <= target.getAmountRemaining()) { - return tryAddTarget(target, permanent.getId(), loy, source, game); - } else { - possibleTarget = permanent; - } + // permanents kill + for (UUID opponentId : opponents) { + targets = threats(opponentId, sourceId, StaticFilters.FILTER_PERMANENT_CREATURE_OR_PLANESWALKER_A, game, target.getTargets()); + + // planeswalker kill + for (Permanent permanent : targets) { + if (permanent.isPlaneswalker() && target.canTarget(getId(), permanent.getId(), source, game)) { + int loy = permanent.getCounters(game).getCount(CounterType.LOYALTY); + if (loy <= target.getAmountRemaining()) { + return tryAddTarget(target, permanent.getId(), loy, source, game); } } } - if (possibleTarget != null) { - return tryAddTarget(target, possibleTarget.getId(), target.getAmountRemaining(), source, game); + + // creature kill + for (Permanent permanent : targets) { + if (permanent.isCreature() && target.canTarget(getId(), permanent.getId(), source, game)) { + if (permanent.getToughness().getValue() <= target.getAmountRemaining()) { + return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game); + } + } } } } + // NORMAL PRIORITY: planeswalker -> player -> creature + // own permanents will be checked multiple times... that's ok + for (UUID opponentId : opponents) { + if (outcome.isGood()) { + targets = threats(getId(), sourceId, StaticFilters.FILTER_PERMANENT, game, target.getTargets()); + } else { + targets = threats(opponentId, sourceId, StaticFilters.FILTER_PERMANENT, game, target.getTargets()); + } + + // planeswalkers + for (Permanent permanent : targets) { + if (permanent.isPlaneswalker() && target.canTarget(getId(), permanent.getId(), source, game)) { + return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); + } + } + + // players + if (outcome.isGood() && target.canTarget(getId(), getId(), source, game)) { + return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game); + } + if (!outcome.isGood() && target.canTarget(getId(), opponentId, source, game)) { + return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); + } + + // creature + for (Permanent permanent : targets) { + if (permanent.isCreature() && target.canTarget(getId(), permanent.getId(), source, game)) { + return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); + } + } + } + + // BAD PRIORITY, e.g. need bad target on yourself or good target on opponent + // priority: creature (non killable, killable) -> planeswalker -> player + if (!target.isRequired(sourceId, game)) { + return false; + } + for (UUID opponentId : opponents) { + if (!outcome.isGood()) { + // bad on yourself, uses weakest targets + targets = threats(getId(), sourceId, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false); + } else { + targets = threats(opponentId, sourceId, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false); + } + + // creatures - non killable (TODO: add extra skill checks like undestructeable) + for (Permanent permanent : targets) { + if (permanent.isCreature() && target.canTarget(getId(), permanent.getId(), source, game)) { + int safeDamage = Math.min(permanent.getToughness().getValue() - 1, target.getAmountRemaining()); + if (safeDamage > 0) { + return tryAddTarget(target, permanent.getId(), safeDamage, source, game); + } + } + } + + // creatures - all + for (Permanent permanent : targets) { + if (permanent.isCreature() && target.canTarget(getId(), permanent.getId(), source, game)) { + return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); + } + } + + // planeswalkers + for (Permanent permanent : targets) { + if (permanent.isPlaneswalker() && target.canTarget(getId(), permanent.getId(), source, game)) { + return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); + } + } + } + // players + for (UUID opponentId : opponents) { + if (target.canTarget(getId(), getId(), source, game)) { + return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game); + } else if (target.canTarget(getId(), opponentId, source, game)) { + return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); + } + } + log.warn("No proper AI target handling: " + target.getClass().getName()); return false; } @@ -2361,6 +2422,11 @@ public class ComputerPlayer extends PlayerImpl implements Player { } protected List threats(UUID playerId, UUID sourceId, FilterPermanent filter, Game game, List targets) { + return threats(playerId, sourceId, filter, game, targets, true); + } + + protected List threats(UUID playerId, UUID sourceId, FilterPermanent filter, Game game, List targets, boolean mostValueableGoFirst) { + // most valuable/powerfull permanents goes at first List threats; if (playerId == null) { threats = game.getBattlefield().getActivePermanents(filter, this.getId(), sourceId, game); // all permanents within the range of the player @@ -2377,7 +2443,9 @@ public class ComputerPlayer extends PlayerImpl implements Player { } } Collections.sort(threats, new PermanentComparator(game)); - Collections.reverse(threats); + if (mostValueableGoFirst) { + Collections.reverse(threats); + } return threats; } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java new file mode 100644 index 00000000000..a9b798cb40c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java @@ -0,0 +1,231 @@ +package org.mage.test.AI.basic; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageMultiEffect; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanentAmount; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseAI; + +/** + * @author JayDi85 + */ +public class TargetPriorityTest extends CardTestPlayerBaseAI { + + // TODO: enable _target_ tests after computerPlayer.chooseTarget will be reworks like chooseTargetAmount + + @Test + @Ignore + public void test_target_PriorityKillByBigPT() { + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3); + assertPermanentCount(playerB, "Balduvian Bears", 3); + assertPermanentCount(playerB, "Ashcoat Bear", 3); + assertPermanentCount(playerB, "Golden Bear", 3 - 1); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + @Ignore + public void test_target_PriorityByKillByLowPT() { + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + //addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + //addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + //addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3 - 1); + //assertPermanentCount(playerB, "Balduvian Bears", 3); + //assertPermanentCount(playerB, "Ashcoat Bear", 3); + //assertPermanentCount(playerB, "Golden Bear", 3); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + @Ignore + public void test_target_PriorityKillByExtraPoints() { + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + //addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3); + assertPermanentCount(playerB, "Balduvian Bears", 3); + assertPermanentCount(playerB, "Ashcoat Bear", 3 - 1); + //assertPermanentCount(playerB, "Golden Bear", 3); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + // + + @Test + public void test_targetAmount_PriorityKillByBigPT() { + addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flames of the Firebrand"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3); + assertPermanentCount(playerB, "Balduvian Bears", 3); + assertPermanentCount(playerB, "Ashcoat Bear", 3); + assertPermanentCount(playerB, "Golden Bear", 3 - 1); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + public void test_targetAmount_PriorityByKillByLowPT() { + addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + //addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + //addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flames of the Firebrand"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3 - 1); + assertPermanentCount(playerB, "Balduvian Bears", 3 - 1); + //assertPermanentCount(playerB, "Ashcoat Bear", 3); + //assertPermanentCount(playerB, "Golden Bear", 3); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + public void test_targetAmount_PriorityKillByExtraPoints() { + addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + //addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flames of the Firebrand"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3 - 1); + assertPermanentCount(playerB, "Balduvian Bears", 3); + assertPermanentCount(playerB, "Ashcoat Bear", 3 - 1); + //assertPermanentCount(playerB, "Golden Bear", 3); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + public void test_targetAmount_NormalCase() { + Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(3), new ManaCostsImpl("R")); + ability.addTarget(new TargetCreaturePermanentAmount(3)); + addCustomCardWithAbility("damage 3", playerA, ability); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability + addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: "); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerB, "Memnite", 3); + assertPermanentCount(playerB, "Balduvian Bears", 3); + assertPermanentCount(playerB, "Ashcoat Bear", 3); + assertPermanentCount(playerB, "Golden Bear", 3 - 1); + assertPermanentCount(playerB, "Battering Sliver", 3); + } + + @Test + public void test_targetAmount_BadCase() { + // choose targets as enters battlefield (e.g. can't be canceled) + SpellAbility spell = new SpellAbility(new ManaCostsImpl("R"), "damage 3", Zone.HAND); + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageMultiEffect(3)); + ability.addTarget(new TargetCreaturePermanentAmount(3)); + addCustomCardWithSpell(playerA, spell, ability, CardType.ENCHANTMENT); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 3); // 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 3); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Ashcoat Bear", 3); // 2/2 with ability + addCard(Zone.BATTLEFIELD, playerA, "Golden Bear", 3); // 4/3 + addCard(Zone.BATTLEFIELD, playerA, "Battering Sliver", 3); // 4/4 with ability + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "damage 3"); + + // must damage x3 Balduvian Bears by -1 to keep alive + checkDamage("pt after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 1); + showBattlefield("after", 1, PhaseStep.BEGIN_COMBAT, playerA); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "damage 3", 1); + + assertPermanentCount(playerA, "Memnite", 3); + assertPermanentCount(playerA, "Balduvian Bears", 3); + assertPermanentCount(playerA, "Ashcoat Bear", 3); + assertPermanentCount(playerA, "Golden Bear", 3); + assertPermanentCount(playerA, "Battering Sliver", 3); + } +}