* AI: fixed rollback errors with copy spell abilities;

Tests: added copy spell support for test player;
This commit is contained in:
Oleg Agafonov 2019-12-23 21:47:01 +04:00
parent c0df1d6e8a
commit fac7ea1388
3 changed files with 137 additions and 73 deletions

View file

@ -146,6 +146,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
abilityControllerId = target.getAbilityController();
}
boolean required = target.isRequired(sourceId, game);
Set<UUID> possibleTargets = target.possibleTargets(sourceId, abilityControllerId, game);
if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) {
required = false;
}
UUID randomOpponentId;
if (target.getTargetController() != null) {
randomOpponentId = getRandomOpponent(target.getTargetController(), game);
@ -156,7 +162,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
if (target.getOriginalTarget() instanceof TargetPlayer) {
return setTargetPlayer(outcome, target, null, sourceId, abilityControllerId, randomOpponentId, game);
return setTargetPlayer(outcome, target, null, sourceId, abilityControllerId, randomOpponentId, game, required);
}
if (target.getOriginalTarget() instanceof TargetDiscard) {
@ -287,7 +293,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
target.add(randomOpponentId, game);
return true;
}
if (!target.isRequired(sourceId, game)) {
if (!required) {
return false;
}
}
@ -318,7 +324,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
target.add(randomOpponentId, game);
return true;
}
if (!target.isRequired(sourceId, game)) {
if (!required) {
return false;
}
}
@ -405,7 +411,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard
|| target.getOriginalTarget() instanceof TargetCardInASingleGraveyard) {
List<UUID> alreadyTargeted = target.getTargets();
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game));
TargetCard originalTarget = (TargetCard) target.getOriginalTarget();
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(originalTarget.getFilter(), game));
while (!cards.isEmpty()) {
Card card = pickTarget(abilityControllerId, cards, outcome, target, null, game);
if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) {
@ -433,7 +440,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
}
if (!target.isRequired(sourceId, game)) {
if (!required) {
return false;
}
throw new IllegalStateException("TargetSource wasn't handled. class: " + target.getClass().toString());
@ -448,6 +455,10 @@ public class ComputerPlayer extends PlayerImpl implements Player {
log.debug("chooseTarget: " + outcome.toString() + ':' + target.toString());
}
// target - real target, make all changes and add targets to it
// target.getOriginalTarget() - copy spell effect replaces original target with TargetWithAdditionalFilter
// use originalTarget to get filters and target class info
// source can be null (as example: legendary rule permanent selection)
UUID sourceId = source != null ? source.getSourceId() : null;
@ -482,7 +493,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
if (target.getOriginalTarget() instanceof TargetPlayer) {
return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game);
return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game, required);
}
if (target.getOriginalTarget() instanceof TargetDiscard
@ -555,15 +566,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
// TODO: add findBest*Targets for all target types
if (target.getOriginalTarget() instanceof TargetPermanent) {
TargetPermanent origTarget = (TargetPermanent) target.getOriginalTarget();
findBestPermanentTargets(outcome, abilityControllerId, sourceId, ((TargetPermanent) target).getFilter(),
game, target, goodList, badList, allList);
findBestPermanentTargets(outcome, abilityControllerId, sourceId, origTarget.getFilter(),
game, target, goodList2, badList2, allList2);
if (goodList.size() != goodList2.size() || badList.size() != badList2.size() || allList.size() != allList2.size()
|| !origTarget.getFilter().equals(target.getFilter())) {
// TODO: remove double check after servers testing
log.error("Different filters in target and origTarget: " + target.getClass().getName() + " - " + origTarget.getClass().getName());
}
game, target, goodList, badList, allList);
// use good list all the time and add maximum targets
for (Permanent permanent : goodList) {
@ -589,7 +593,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
if (target.getOriginalTarget() instanceof TargetCreatureOrPlayer) {
List<Permanent> targets;
TargetCreatureOrPlayer origTarget = ((TargetCreatureOrPlayer) target);
TargetCreatureOrPlayer origTarget = ((TargetCreatureOrPlayer) target.getOriginalTarget());
if (outcome.isGood()) {
targets = threats(abilityControllerId, sourceId, ((FilterCreatureOrPlayer) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets());
} else {
@ -626,13 +630,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
return tryAddTarget(target, randomOpponentId, source, game);
}
//if (!target.isRequired())
return false;
}
if (target.getOriginalTarget() instanceof TargetAnyTarget) {
List<Permanent> targets;
TargetAnyTarget origTarget = ((TargetAnyTarget) target);
TargetAnyTarget origTarget = ((TargetAnyTarget) target.getOriginalTarget());
if (outcome.isGood()) {
targets = threats(abilityControllerId, sourceId, ((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), game, target.getTargets());
} else {
@ -649,7 +652,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
if (targets.isEmpty() && target.isRequired(source)) {
if (targets.isEmpty() && required) {
targets = game.getBattlefield().getActivePermanents(((FilterCreaturePlayerOrPlaneswalker) origTarget.getFilter()).getCreatureFilter(), playerId, game);
}
for (Permanent permanent : targets) {
@ -675,7 +678,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) {
List<Permanent> targets;
TargetPermanentOrPlayer origTarget = ((TargetPermanentOrPlayer) target);
TargetPermanentOrPlayer origTarget = ((TargetPermanentOrPlayer) target.getOriginalTarget());
if (outcome.isGood()) {
targets = threats(abilityControllerId, source.getSourceId(), ((FilterPermanentOrPlayer) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets());
} else {
@ -707,7 +710,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
if (target.getOriginalTarget() instanceof TargetPlayerOrPlaneswalker) {
List<Permanent> targets;
TargetPlayerOrPlaneswalker origTarget = ((TargetPlayerOrPlaneswalker) target);
TargetPlayerOrPlaneswalker origTarget = ((TargetPlayerOrPlaneswalker) target.getOriginalTarget());
// TODO: if effect is bad and no opponent's targets available then AI can't target yourself but must by rules
/*
@ -739,7 +742,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
// can't find targets (e.g. effect is bad, but you need take targets from yourself)
if (targets.isEmpty() && target.isRequired(source)) {
if (targets.isEmpty() && required) {
targets = game.getBattlefield().getActivePermanents(origTarget.getFilterPermanent(), playerId, game);
}
@ -762,7 +765,6 @@ public class ComputerPlayer extends PlayerImpl implements Player {
return tryAddTarget(target, randomOpponentId, source, game);
}
//if (!target.isRequired())
return false;
}
@ -821,7 +823,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
} else {
targets = threats(randomOpponentId, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets());
}
if (targets.isEmpty() && target.isRequired(source)) {
if (targets.isEmpty() && required) {
targets = threats(null, source == null ? null : source.getSourceId(), origTarget.getPermanentFilter(), game, target.getTargets());
Collections.reverse(targets);
outcomeTargets = false;
@ -1315,6 +1317,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
}
// TODO: wtf?! change to player.getPlayable
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
for (ActivatedAbility ability : permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
if (!(ability instanceof ActivatedManaAbilityImpl) && ability.canActivate(playerId, game).canActivate()) {
@ -2654,7 +2657,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
/**
* Sets a possible target player
*/
private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game) {
private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) {
if (target.getOriginalTarget() instanceof TargetOpponent) {
if (source == null) {
if (target.canTarget(randomOpponentId, game)) {
@ -2721,7 +2724,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
target.add(randomOpponentId, game);
return true;
}
if (target.isRequired(sourceId, game)) {
if (required) {
if (target.canTarget(abilityControllerId, game)) {
target.add(abilityControllerId, game);
return true;

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.triggers;
import mage.abilities.keyword.TrampleAbility;
@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class ZadaHedronGrinderTest extends CardTestPlayerBase {
@ -43,7 +41,70 @@ public class ZadaHedronGrinderTest extends CardTestPlayerBase {
assertAbility(playerA, "Zada, Hedron Grinder", TrampleAbility.getInstance(), true);
assertPowerToughness(playerA, "Silvercoat Lion", 4, 2);
assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true);
}
@Test
public void testTargetsByTestPlayer() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
// Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for
// each other creature you control that the spell could target. Each copy targets a different one of those creatures.
addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
//
// Put a +1/+1 counter on target creature. That creature gains reach until end of turn.
addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// cast
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder");
addTarget(playerA, "Balduvian Bears^Silvercoat Lion");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertGraveyardCount(playerA, "Arbor Armament", 1);
assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1);
assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1);
assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1);
}
@Test
public void testTargetsByAI() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
// Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for
// each other creature you control that the spell could target. Each copy targets a different one of those creatures.
addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
//
// Put a +1/+1 counter on target creature. That creature gains reach until end of turn.
addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// cast
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder");
//addTarget(playerA, "Balduvian Bears^Silvercoat Lion");
//setStrictChooseMode(true); // no strict mode for AI
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertGraveyardCount(playerA, "Arbor Armament", 1);
assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1);
assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1);
assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1);
}
}

View file

@ -453,9 +453,9 @@ public class TestPlayer implements Player {
if (currentTarget.getNumberOfTargets() == 1) {
currentTarget.clearChosen();
}
if (currentTarget instanceof TargetCreaturePermanentAmount) {
if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) {
// supports only to set the complete amount to one target
TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget;
TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget.getOriginalTarget();
targetAmount.setAmount(ability, game);
int amount = targetAmount.getAmountRemaining();
targetAmount.addTarget(id, amount, ability, game);
@ -1540,12 +1540,13 @@ public class TestPlayer implements Player {
source = stackObject.getStackAbility();
}
if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer)) { // player target not implemted yet
if ((target.getOriginalTarget() instanceof TargetPermanent)
|| (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)) { // player target not implemted yet
FilterPermanent filterPermanent;
if (target instanceof TargetPermanentOrPlayer) {
filterPermanent = ((TargetPermanentOrPlayer) target).getFilterPermanent();
if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) {
filterPermanent = ((TargetPermanentOrPlayer) target.getOriginalTarget()).getFilterPermanent();
} else {
filterPermanent = ((TargetPermanent) target).getFilter();
filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter();
}
for (String choose2 : choices) {
String[] targetList = choose2.split("\\^");
@ -1608,7 +1609,7 @@ public class TestPlayer implements Player {
}
// TODO: add same choices fixes for other target types (one choice must uses only one time for one target)
if (target instanceof TargetCard) {
if (target.getOriginalTarget() instanceof TargetCard) {
// one choice per target
// only unique targets
//TargetCard targetFull = ((TargetCard) target);
@ -1674,9 +1675,9 @@ public class TestPlayer implements Player {
}
}
if (target instanceof TargetSource) {
if (target.getOriginalTarget() instanceof TargetSource) {
Set<UUID> possibleTargets;
TargetSource t = ((TargetSource) target);
TargetSource t = ((TargetSource) target.getOriginalTarget());
possibleTargets = t.possibleTargets(sourceId, abilityControllerId, game);
for (String choose2 : choices) {
String[] targetList = choose2.split("\\^");
@ -1760,11 +1761,11 @@ public class TestPlayer implements Player {
}
// player
if (target instanceof TargetPlayer
|| target instanceof TargetAnyTarget
|| target instanceof TargetCreatureOrPlayer
|| target instanceof TargetPermanentOrPlayer
|| target instanceof TargetDefender) {
if (target.getOriginalTarget() instanceof TargetPlayer
|| target.getOriginalTarget() instanceof TargetAnyTarget
|| target.getOriginalTarget() instanceof TargetCreatureOrPlayer
|| target.getOriginalTarget() instanceof TargetPermanentOrPlayer
|| target.getOriginalTarget() instanceof TargetDefender) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "=");
if (targetDefinition.startsWith("targetPlayer=")) {
@ -1779,15 +1780,14 @@ public class TestPlayer implements Player {
}
}
}
}
// permanent in battlefield
if ((target instanceof TargetPermanent)
|| (target instanceof TargetPermanentOrPlayer)
|| (target instanceof TargetAnyTarget)
|| (target instanceof TargetCreatureOrPlayer)
|| (target instanceof TargetDefender)) {
if ((target.getOriginalTarget() instanceof TargetPermanent)
|| (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)
|| (target.getOriginalTarget() instanceof TargetAnyTarget)
|| (target.getOriginalTarget() instanceof TargetCreatureOrPlayer)
|| (target.getOriginalTarget() instanceof TargetDefender)) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^[]");
String[] targetList = targetDefinition.split("\\^");
@ -1805,7 +1805,7 @@ public class TestPlayer implements Player {
targetName = targetName.substring(0, targetName.length() - 11);
}
}
Filter filter = target.getFilter();
Filter filter = target.getOriginalTarget().getFilter();
if (filter instanceof FilterCreatureOrPlayer) {
filter = ((FilterCreatureOrPlayer) filter).getCreatureFilter();
}
@ -1824,12 +1824,11 @@ public class TestPlayer implements Player {
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) {
target.add(permanent.getId(), game);
targetFound = true;
break;
break; // return to for (String targetName
}
}
}
}
}
if (targetFound) {
targets.remove(targetDefinition);
@ -1839,18 +1838,18 @@ public class TestPlayer implements Player {
}
// card in hand
if (target instanceof TargetCardInHand) {
if (target.getOriginalTarget() instanceof TargetCardInHand) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
boolean targetFound = false;
for (String targetName : targetList) {
for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target).getFilter(), game)) {
for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target.getOriginalTarget()).getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.add(card.getId(), game);
targetFound = true;
break;
break; // return to for (String targetName
}
}
}
@ -1863,8 +1862,8 @@ public class TestPlayer implements Player {
}
// card in exile
if (target instanceof TargetCardInExile) {
TargetCardInExile targetFull = (TargetCardInExile) target;
if (target.getOriginalTarget() instanceof TargetCardInExile) {
TargetCardInExile targetFull = (TargetCardInExile) target.getOriginalTarget();
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
@ -1872,10 +1871,10 @@ public class TestPlayer implements Player {
for (String targetName : targetList) {
for (Card card : game.getExile().getCards(targetFull.getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) {
targetFull.add(card.getId(), game);
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.add(card.getId(), game);
targetFound = true;
break;
break; // return to for (String targetName
}
}
}
@ -1900,7 +1899,7 @@ public class TestPlayer implements Player {
if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) {
targetFull.add(card.getId(), game);
targetFound = true;
break;
break; // return to for (String targetName
}
}
}
@ -1914,22 +1913,22 @@ public class TestPlayer implements Player {
// card in graveyard
if (target instanceof TargetCardInOpponentsGraveyard
|| target instanceof TargetCardInYourGraveyard
|| target instanceof TargetCardInGraveyard
|| target instanceof TargetCardInGraveyardOrBattlefield) {
TargetCard targetFull = (TargetCard) target;
if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard
|| target.getOriginalTarget() instanceof TargetCardInYourGraveyard
|| target.getOriginalTarget() instanceof TargetCardInGraveyard
|| target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) {
TargetCard targetFull = (TargetCard) target.getOriginalTarget();
List<UUID> needPlayers = game.getState().getPlayersInRange(getId(), game).toList();
// fix for opponent graveyard
if (target instanceof TargetCardInOpponentsGraveyard) {
if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) {
// current player remove
Assert.assertTrue(needPlayers.contains(getId()));
needPlayers.remove(getId());
Assert.assertFalse(needPlayers.contains(getId()));
}
// fix for your graveyard
if (target instanceof TargetCardInYourGraveyard) {
if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) {
// only current player
Assert.assertTrue(needPlayers.contains(getId()));
needPlayers.clear();
@ -1948,17 +1947,16 @@ public class TestPlayer implements Player {
Player player = game.getPlayer(playerId);
for (Card card : player.getGraveyard().getCards(targetFull.getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.add(card.getId(), game);
targetFound = true;
break IterateGraveyards;
break IterateGraveyards; // return to for (String targetName
}
}
}
}
}
if (targetFound) {
targets.remove(targetDefinition);
return true;
@ -1968,7 +1966,7 @@ public class TestPlayer implements Player {
}
// stack
if (target instanceof TargetSpell) {
if (target.getOriginalTarget() instanceof TargetSpell) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
@ -1976,9 +1974,11 @@ public class TestPlayer implements Player {
for (String targetName : targetList) {
for (StackObject stackObject : game.getStack()) {
if (stackObject.getName().equals(targetName)) {
target.add(stackObject.getId(), game);
targetFound = true;
break;
if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) {
target.add(stackObject.getId(), game);
targetFound = true;
break; // return to for (String targetName
}
}
}
}