Some fixes/improvements to AI target handling and tests.

This commit is contained in:
LevelX2 2015-07-16 13:10:11 +02:00
parent 7ce030101c
commit 0c0fe9984e
7 changed files with 217 additions and 92 deletions

View file

@ -302,10 +302,10 @@ public class SimulatedPlayer2 extends ComputerPlayer {
}
if (bad) {
// remove its own creatures, player itself for bad effects
// remove its own creatures, player itself for bad effects with one target
while (iterator.hasNext()) {
Ability ability1 = iterator.next();
if (ability1.getTargets().size() == 1) {
if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) {
Permanent permanent = game.getPermanent(ability1.getFirstTarget());
if (permanent != null && !game.getOpponents(playerId).contains(permanent.getControllerId())) {
iterator.remove();
@ -318,10 +318,10 @@ public class SimulatedPlayer2 extends ComputerPlayer {
}
}
if (good) {
// remove opponent creatures and opponent for only good effects
// remove opponent creatures and opponent for only good effects with one target
while (iterator.hasNext()) {
Ability ability1 = iterator.next();
if (ability1.getTargets().size() == 1) {
if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) {
Permanent permanent = game.getPermanent(ability1.getFirstTarget());
if (permanent != null && game.getOpponents(playerId).contains(permanent.getControllerId())) {
iterator.remove();

View file

@ -161,7 +161,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
private transient final static Logger log = Logger.getLogger(ComputerPlayer.class);
protected int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are availble
protected boolean ALLOW_INTERRUPT = true; // change this for test purposes to switch off interrupts while debugging
protected boolean ALLOW_INTERRUPT = false; // change this for test / debugging purposes to false to switch off interrupts while debugging
private transient Map<Mana, Card> unplayable = new TreeMap<>();
private transient List<Card> playableNonInstant = new ArrayList<>();
@ -301,12 +301,25 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
for (Permanent permanent : targets) {
if (((TargetPermanent) target).canTarget(abilityControllerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) {
// stop to add targets if not needed and outcome is no advantage for AI player
if (target.getNumberOfTargets() == target.getTargets().size()) {
if (outcome.isGood() && hasOpponent(permanent.getControllerId(), game)) {
return true;
}
if (!outcome.isGood() && !hasOpponent(permanent.getControllerId(), game)) {
return true;
}
}
// add the target
target.add(permanent.getId(), game);
return true;
if (target.doneChosing()) {
return true;
}
}
}
return false;
return target.isChosen();
}
if (target instanceof TargetCardInHand) {
List<Card> cards = new ArrayList<>();
for (UUID cardId : ((TargetCardInHand) target).possibleTargets(sourceId, this.getId(), game)) {

View file

@ -49,12 +49,12 @@ import mage.target.TargetPermanent;
* @author JotaPeRL
*/
public class NovijenSages extends CardImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creatures you control with a +1/+1 counter on it");
static {
filter.add(new CounterPredicate(CounterType.P1P1));
}
}
public NovijenSages(UUID ownerId) {
super(ownerId, 27, "Novijen Sages", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{4}{U}{U}");
@ -67,10 +67,10 @@ public class NovijenSages extends CardImpl {
// Graft 4
this.addAbility(new GraftAbility(this, 4));
// {1}, Remove two +1/+1 counters from among creatures you control: Draw a card.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new GenericManaCost(1));
ability.addCost(new RemoveCounterCost(new TargetPermanent(1, Integer.MAX_VALUE, filter, true), CounterType.P1P1, 2));
ability.addCost(new RemoveCounterCost(new TargetPermanent(1, 2, filter, true), CounterType.P1P1, 2));
this.addAbility(ability);
}

View file

@ -0,0 +1,102 @@
/*
* 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 org.mage.test.AI.basic;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/**
*
* @author LevelX2
*/
public class TargetsAreChosenTest extends CardTestPlayerBaseAI {
/**
* Check that the AI selects a target from the own artifacts and also an
* artifact from the opponent artficats
*/
@Test
public void testRackAndRuin() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Destroy two target artifacts.
addCard(Zone.HAND, playerA, "Rack and Ruin"); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mox Emerald", 2);
addCard(Zone.BATTLEFIELD, playerB, "Juggernaut");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Rack and Ruin", 1);
assertGraveyardCount(playerA, "Mox Emerald", 1);
assertGraveyardCount(playerB, "Juggernaut", 1);
}
/**
* Check that the AI does not cast Rack and Ruin if it would destroy the
* owly creature on the battlefield owned by the AI
*/
@Test
public void testRackAndRuin2() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Destroy two target artifacts.
addCard(Zone.HAND, playerA, "Rack and Ruin"); // {2}{R}
addCard(Zone.BATTLEFIELD, playerB, "Mox Emerald");
addCard(Zone.BATTLEFIELD, playerA, "Juggernaut");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Rack and Ruin", 0);
}
/**
* Check that the AI does cast Rack and Ruin if it would destroy two targets
* of the opponent
*/
@Test
public void testRackAndRuin3() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
// Destroy two target artifacts.
addCard(Zone.HAND, playerA, "Rack and Ruin"); // {2}{R}
addCard(Zone.BATTLEFIELD, playerB, "Mox Emerald", 2);
addCard(Zone.BATTLEFIELD, playerA, "Juggernaut");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Rack and Ruin", 1);
assertGraveyardCount(playerB, "Mox Emerald", 2);
}
}

View file

@ -14,30 +14,33 @@ public class BoseijuTest extends CardTestPlayerBase {
* Boseiju, Who Shelters All
* Legendary Land
* Boseiju, Who Shelters All enters the battlefield tapped.
* {T}, Pay 2 life: Add {1} to your mana pool. If that mana is spent on an
* {T}, Pay 2 life: Add {1} to your mana pool. If that mana is spent on an
* instant or sorcery spell, that spell can't be countered by spells or abilities.
*
*/
*/
// test that instants and soceries can't be countered when Boseiju mana is used
@Test
public void testCantCounter() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
addCard(Zone.BATTLEFIELD, playerA, "Boseiju, Who Shelters All");
addCard(Zone.HAND, playerA, "Brilliant Plan");
addCard(Zone.HAND, playerA, "Counterspell");
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
// Draw three cards.
addCard(Zone.HAND, playerA, "Brilliant Plan"); // {4}{U}
addCard(Zone.HAND, playerB, "Counterspell"); // {U}{U}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brilliant Plan");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Counterspell", "Brilliant Plan");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Brilliant Plan");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
this.assertHandCount(playerA, 3);
this.assertGraveyardCount(playerA, "Counterspell", 1);
this.assertGraveyardCount(playerB, "Counterspell", 1);
}
// test that instants and soceries can be countered when Boseiju mana is not used
@Test
public void testCanCounter() {
@ -45,16 +48,16 @@ public class BoseijuTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Boseiju, Who Shelters All");
addCard(Zone.HAND, playerA, "Mental Note");
addCard(Zone.HAND, playerA, "Counterspell");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mental Note");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Counterspell", "Mental Note");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
this.assertHandCount(playerA, 0);
this.assertGraveyardCount(playerA, 2);
}
}

View file

@ -1,16 +1,16 @@
/*
* 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
@ -20,16 +20,14 @@
* 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.abilities.costs.common;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
@ -55,7 +53,7 @@ public class RemoveCounterCost extends CostImpl {
private String name;
private CounterType counterTypeToRemove;
private int countersToRemove;
public RemoveCounterCost(TargetPermanent target) {
this(target, null);
}
@ -88,7 +86,7 @@ public class RemoveCounterCost extends CostImpl {
if (controller != null) {
target.clearChosen();
if (target.choose(Outcome.UnboostCreature, controllerId, sourceId, game)) {
for (UUID targetId: (List<UUID>)target.getTargets()) {
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
if (permanent.getCounters().size() > 0 && (counterTypeToRemove == null || permanent.getCounters().containsKey(counterTypeToRemove))) {
@ -123,18 +121,19 @@ public class RemoveCounterCost extends CostImpl {
int numberOfCountersSelected = 1;
if (countersLeft > 1 && countersOnPermanent > 1) {
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
}
permanent.removeCounters(counterName, numberOfCountersSelected, game);
if (permanent.getCounters().getCount(counterName) == 0 ){
if (permanent.getCounters().getCount(counterName) == 0) {
permanent.getCounters().removeCounter(counterName);
}
countersRemoved += numberOfCountersSelected;
if (!game.isSimulation())
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName())
.append(" removes ").append(numberOfCountersSelected == 1 ? "a":numberOfCountersSelected).append(" ")
.append(counterName).append(numberOfCountersSelected == 1 ? " counter from ":" counters from ")
.append(permanent.getName()).toString());
.append(" removes ").append(numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected).append(" ")
.append(counterName).append(numberOfCountersSelected == 1 ? " counter from " : " counters from ")
.append(permanent.getName()).toString());
}
if (countersRemoved == countersToRemove) {
this.paid = true;
break;
@ -160,7 +159,7 @@ public class RemoveCounterCost extends CostImpl {
if (counterTypeToRemove != null) {
sb.append(counterTypeToRemove.getName());
}
sb.append(countersToRemove == 1 ? " counter from ":" counters from ").append(target.getMaxNumberOfTargets() == 1 ? "a ":"").append(target.getTargetName());
sb.append(countersToRemove == 1 ? " counter from " : " counters from ").append(target.getMaxNumberOfTargets() == 1 ? "a " : "").append(target.getTargetName());
return sb.toString();
}

View file

@ -1,46 +1,53 @@
/*
* 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.
*/
* 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.target;
import mage.constants.Outcome;
import mage.constants.Zone;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.AbilityType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.players.Player;
import java.util.*;
import mage.MageObject;
import mage.constants.AbilityType;
/**
*
* @author BetaSteward_at_googlemail.com
@ -104,7 +111,7 @@ public abstract class TargetImpl implements Target {
public void setMinNumberOfTargets(int minNumberOftargets) {
this.minNumberOfTargets = minNumberOftargets;
}
@Override
public void setMaxNumberOfTargets(int maxNumberOftargets) {
this.maxNumberOfTargets = maxNumberOftargets;
@ -116,7 +123,7 @@ public abstract class TargetImpl implements Target {
if (targetController != null) {
// Hint for the selecting player that the targets must be valid from the point of the ability controller
// e.g. select opponent text may be misleading otherwise
suffix = " (target controlling!)";
suffix = " (target controlling!)";
}
if (getMaxNumberOfTargets() != 1) {
StringBuilder sb = new StringBuilder();
@ -158,7 +165,7 @@ public abstract class TargetImpl implements Target {
public Zone getZone() {
return zone;
}
@Override
public boolean isRequired(UUID sourceId, Game game) {
MageObject object = game.getObject(sourceId);
@ -168,12 +175,12 @@ public abstract class TargetImpl implements Target {
return isRequired();
}
}
@Override
public boolean isRequired() {
return required;
}
@Override
public boolean isRequired(Ability ability) {
return ability == null || ability.isActivated() || !(ability.getAbilityType().equals(AbilityType.SPELL) || ability.getAbilityType().equals(AbilityType.ACTIVATED));
@ -217,6 +224,7 @@ public abstract class TargetImpl implements Target {
if (!targets.containsKey(id)) {
targets.put(id, 0);
rememberZoneChangeCounter(id, game);
chosen = targets.size() >= getNumberOfTargets();
}
}
}
@ -254,7 +262,7 @@ public abstract class TargetImpl implements Target {
}
}
}
@Override
public void updateTarget(UUID id, Game game) {
rememberZoneChangeCounter(id, game);
@ -315,7 +323,7 @@ public abstract class TargetImpl implements Target {
int i = 0;
int rnd = new Random().nextInt(possibleTargets.size());
Iterator it = possibleTargets.iterator();
while( i < rnd) {
while (i < rnd) {
it.next();
i++;
}
@ -336,9 +344,9 @@ public abstract class TargetImpl implements Target {
@Override
public boolean isLegal(Ability source, Game game) {
//20101001 - 608.2b
Set <UUID> illegalTargets = new HashSet<>();
Set<UUID> illegalTargets = new HashSet<>();
// int replacedTargets = 0;
for (UUID targetId: targets.keySet()) {
for (UUID targetId : targets.keySet()) {
Card card = game.getCard(targetId);
if (card != null) {
if (zoneChangeCounters.containsKey(targetId) && zoneChangeCounters.get(targetId) != card.getZoneChangeCounter(game)) {
@ -356,7 +364,7 @@ public abstract class TargetImpl implements Target {
}
}
// remove illegal targets, needed to handle if only a subset of targets was illegal
for (UUID targetId: illegalTargets) {
for (UUID targetId : illegalTargets) {
targets.remove(targetId);
}
// if (replacedTargets > 0 && replacedTargets == targets.size()) {
@ -381,7 +389,7 @@ public abstract class TargetImpl implements Target {
target.addTarget(targetId, source, game, true);
if (!target.isChosen()) {
Iterator<UUID> it2 = possibleTargets.iterator();
while (it2.hasNext()&& !target.isChosen()) {
while (it2.hasNext() && !target.isChosen()) {
UUID nextTargetId = it2.next();
target.addTarget(nextTargetId, source, game, true);
}
@ -389,7 +397,7 @@ public abstract class TargetImpl implements Target {
if (target.isChosen()) {
options.add(target);
}
}
}
return options;
}
@ -458,6 +466,6 @@ public abstract class TargetImpl implements Target {
} else {
return game.getPlayer(playerId);
}
}
}
}