Test framework: added AI support in basic card tests:

* added new command to play one best action on priority by AI (aiPlayPriority);
* added new command to play all best actions on step by AI (aiPlayStep);
* now you can setup battlefield by standard commands and run AI simulations for single priority/step;
* CardTestPlayerBase - old default class for card tests, it uses simple AI for choosing, but do not play/simulate it;
* CardTestPlayerBaseAI - old default class for AI game tests, auto-playable playerA, you can control only playerB;
* CardTestPlayerBaseWithAIHelps - new improved class for card tests, it uses controllable and real AI with game simulations;
This commit is contained in:
Oleg Agafonov 2020-01-29 07:42:20 +04:00
parent 6cbf94bad6
commit c767d19534
4 changed files with 166 additions and 2 deletions

View file

@ -0,0 +1,77 @@
package org.mage.test.AI.basic;
import mage.constants.CardType;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author JayDi85
*/
public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_AI_PlayOnePriorityAction() {
addCard(Zone.HAND, playerA, "Lightning Bolt", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 5);
// AI must play one time
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertPermanentCount(playerB, "Balduvian Bears", 5 - 1);
}
@Test
public void test_AI_PlayManyActionsInOneStep() {
addCard(Zone.HAND, playerA, "Lightning Bolt", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 5);
// AI must play all step actions
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Lightning Bolt", 3);
assertPermanentCount(playerB, "Balduvian Bears", 5 - 3);
}
@Test
@Ignore // AI can't play blade cause score system give priority for boost instead restriction effects like goad
public void test_AI_GoadedByBloodthirstyBlade_Normal() {
// Equipped creature gets +2/+0 and is goaded
// {1}: Attach Bloodthirsty Blade to target creature an opponent controls. Activate this ability only any time you could cast a sorcery.
addCard(Zone.BATTLEFIELD, playerA, "Bloodthirsty Blade", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
// AI must play
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
Assert.assertEquals(1, currentGame.getBattlefield().getAllActivePermanents(CardType.CREATURE).size());
Permanent permanent = currentGame.getBattlefield().getAllActivePermanents(CardType.CREATURE).get(0);
Assert.assertEquals(1, permanent.getAttachments().size());
}
}

View file

@ -84,6 +84,7 @@ public class TestPlayer implements Player {
private int foundNoAction = 0; private int foundNoAction = 0;
private boolean AIPlayer; private boolean AIPlayer;
private final List<PlayerAction> actions = new ArrayList<>(); private final List<PlayerAction> actions = new ArrayList<>();
private final Map<PlayerAction, PhaseStep> actionsToRemovesLater = new HashMap<>(); // remove actions later, on next step (e.g. for AI commands)
private final List<String> choices = new ArrayList<>(); // choices stack for choice private final List<String> choices = new ArrayList<>(); // choices stack for choice
private final List<String> targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command) private final List<String> targets = new ArrayList<>(); // targets stack for choose (it's uses on empty direct target by cast command)
private final Map<String, UUID> aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use) private final Map<String, UUID> aliases = new HashMap<>(); // aliases for game objects/players (use it for cards with same name to save and use)
@ -499,6 +500,18 @@ public class TestPlayer implements Player {
@Override @Override
public boolean priority(Game game) { public boolean priority(Game game) {
// later remove actions (ai commands related)
if (actionsToRemovesLater.size() > 0) {
List<PlayerAction> removed = new ArrayList<>();
actionsToRemovesLater.forEach((action, step) -> {
if (game.getStep().getType() != step) {
actions.remove(action);
removed.add(action);
}
});
removed.forEach(actionsToRemovesLater::remove);
}
int numberOfActions = actions.size(); int numberOfActions = actions.size();
List<PlayerAction> tempActions = new ArrayList<>(); List<PlayerAction> tempActions = new ArrayList<>();
tempActions.addAll(actions); tempActions.addAll(actions);
@ -622,6 +635,25 @@ public class TestPlayer implements Player {
actions.remove(action); actions.remove(action);
} }
} }
} else if (action.getAction().startsWith(AI_PREFIX)) {
String command = action.getAction();
command = command.substring(command.indexOf(AI_PREFIX) + AI_PREFIX.length());
// play priority
if (command.equals(AI_COMMAND_PLAY_PRIORITY)) {
computerPlayer.priority(game);
actions.remove(action);
return true;
}
// play step
if (command.equals(AI_COMMAND_PLAY_STEP)) {
actionsToRemovesLater.put(action, game.getStep().getType());
computerPlayer.priority(game);
return true;
}
Assert.fail("Unknow ai command: " + command);
} else if (action.getAction().startsWith(CHECK_PREFIX)) { } else if (action.getAction().startsWith(CHECK_PREFIX)) {
String command = action.getAction(); String command = action.getAction();
command = command.substring(command.indexOf(CHECK_PREFIX) + CHECK_PREFIX.length()); command = command.substring(command.indexOf(CHECK_PREFIX) + CHECK_PREFIX.length());
@ -752,9 +784,9 @@ public class TestPlayer implements Player {
if (!wasProccessed) { if (!wasProccessed) {
Assert.fail("Unknow check command or params: " + command); Assert.fail("Unknow check command or params: " + command);
} }
} else if (action.getAction().startsWith("show:")) { } else if (action.getAction().startsWith(SHOW_PREFIX)) {
String command = action.getAction(); String command = action.getAction();
command = command.substring(command.indexOf("show:") + "show:".length()); command = command.substring(command.indexOf(SHOW_PREFIX) + SHOW_PREFIX.length());
String[] params = command.split(CHECK_PARAM_DELIMETER); String[] params = command.split(CHECK_PARAM_DELIMETER);
boolean wasProccessed = false; boolean wasProccessed = false;
@ -3640,4 +3672,8 @@ public class TestPlayer implements Player {
this.chooseStrictModeFailed("choice", game, getInfo(card) + " - can't select ability to cast.\n" + "Card's abilities:\n" + allInfo); this.chooseStrictModeFailed("choice", game, getInfo(card) + " - can't select ability to cast.\n" + "Card's abilities:\n" + allInfo);
return computerPlayer.chooseAbilityForCast(card, game, noMana); return computerPlayer.chooseAbilityForCast(card, game, noMana);
} }
public ComputerPlayer getComputerPlayer() {
return computerPlayer;
}
} }

View file

@ -0,0 +1,22 @@
package org.mage.test.serverside.base;
import mage.constants.RangeOfInfluence;
import org.mage.test.player.TestComputerPlayer7;
import org.mage.test.player.TestPlayer;
/**
* Base class but with latest computer player to test single AI commands (it's different from full AI simulation from CardTestPlayerBaseAI):
* 1. AI don't play normal priorities (you must use ai*** commands to play it);
* 2. AI will choose in non strict mode (it's simulated ComputerPlayer7, not simple ComputerPlayer from basic tests)
*
* @author JayDi85
*/
public abstract class CardTestPlayerBaseWithAIHelps extends CardTestPlayerBase {
@Override
protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) {
TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7(name, RangeOfInfluence.ONE, 6));
testPlayer.setAIPlayer(false); // AI can't play it by itself, use AI commands
return testPlayer;
}
}

View file

@ -22,6 +22,7 @@ import mage.game.GameOptions;
import mage.game.command.CommandObject; import mage.game.command.CommandObject;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentCard;
import mage.player.ai.ComputerPlayer7;
import mage.players.ManaPool; import mage.players.ManaPool;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -53,6 +54,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String ALIAS_PREFIX = "@"; // don't change -- it uses in user's tests public static final String ALIAS_PREFIX = "@"; // don't change -- it uses in user's tests
public static final String CHECK_PARAM_DELIMETER = "#"; public static final String CHECK_PARAM_DELIMETER = "#";
public static final String CHECK_PREFIX = "check:"; // prefix for all check commands public static final String CHECK_PREFIX = "check:"; // prefix for all check commands
public static final String SHOW_PREFIX = "show:"; // prefix for all show commands
public static final String AI_PREFIX = "ai:"; // prefix for all ai commands
static { static {
// aliases can be used in check commands, so all prefixes and delimeters must be unique // aliases can be used in check commands, so all prefixes and delimeters must be unique
@ -67,6 +70,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String ACTIVATE_PLAY = "activate:Play "; public static final String ACTIVATE_PLAY = "activate:Play ";
public static final String ACTIVATE_CAST = "activate:Cast "; public static final String ACTIVATE_CAST = "activate:Cast ";
// commands for AI
public static final String AI_COMMAND_PLAY_PRIORITY = "play priority";
public static final String AI_COMMAND_PLAY_STEP = "play step";
static { static {
// cards can be played/casted by activate ability command too // cards can be played/casted by activate ability command too
Assert.assertTrue("musts contains activate ability part", ACTIVATE_PLAY.startsWith(ACTIVATE_ABILITY)); Assert.assertTrue("musts contains activate ability part", ACTIVATE_PLAY.startsWith(ACTIVATE_ABILITY));
@ -1391,6 +1398,28 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool); player.addAction(turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool);
} }
/**
* AI play one PRIORITY with multi game simulations (calcs and play ONE best action, can be called with stack)
*/
public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player) {
assertAiPlayAndGameCompatible(player);
player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_PRIORITY);
}
/**
* AI play STEP to the end with multi game simulations (calcs and play best actions until step ends, can be called in the middle of the step)
*/
public void aiPlayStep(int turnNum, PhaseStep step, TestPlayer player) {
assertAiPlayAndGameCompatible(player);
player.addAction(turnNum, step, AI_PREFIX + AI_COMMAND_PLAY_STEP);
}
private void assertAiPlayAndGameCompatible(TestPlayer player) {
if (player.isAIPlayer() || !(player.getComputerPlayer() instanceof ComputerPlayer7)) {
Assert.fail("AI commands supported by CardTestPlayerBaseWithAIHelps only");
}
}
public void waitStackResolved(int turnNum, PhaseStep step, TestPlayer player) { public void waitStackResolved(int turnNum, PhaseStep step, TestPlayer player) {
player.addAction(turnNum, step, "waitStackResolved"); player.addAction(turnNum, step, "waitStackResolved");
} }