test framework: improved aiXXX commands support:

- added more options for priority control (play single priority, play multiple priorities until stack resolved);
- added more options for step control (play single step, play multiple steps);
- improved compatibility with AI and real time commands (now check commands can be called inside AI controlled steps);
- added tests for assign non-blocked damage;
This commit is contained in:
Oleg Agafonov 2024-10-26 11:33:32 +04:00
parent a06935da81
commit a9bdf2eb18
12 changed files with 342 additions and 88 deletions

View file

@ -48,8 +48,8 @@ public class CopyAITest extends CardTestPlayerBaseWithAIHelps {
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
// clone (AI must choose most valueable permanent - own)
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
// clone (AI must choose most valuable permanent - own)
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -70,8 +70,8 @@ public class CopyAITest extends CardTestPlayerBaseWithAIHelps {
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3
// clone (AI must choose most valueable permanent - opponent)
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
// clone (AI must choose most valuable permanent - opponent)
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);

View file

@ -15,14 +15,36 @@ import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_AI_PlayOnePriorityAction() {
public void test_AI_PlayOnePriority_FromSingle() {
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3);
// AI must play one time
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
// make sure runtime commands can be called with same priority after stack resolve
checkGraveyardCount("after resolve", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", 1);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertPermanentCount(playerB, "Balduvian Bears", 3 - 1);
}
@Test
public void test_AI_PlayOnePriority_FromMultiple() {
// must choose only 1 spell
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);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA, false); // stop after first spell
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
@ -33,7 +55,45 @@ public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps {
}
@Test
public void test_AI_PlayManyActionsInOneStep() {
public void test_AI_PlayOnePriority_WithChoicesOnCast() {
// 1/1
// {2}{W}, Discard a card: Aven Trooper gets +1/+2 until end of turn.
addCard(Zone.BATTLEFIELD, playerA, "Aven Trooper", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
addCard(Zone.HAND, playerA, "Mountain", 3);
// AI must play one time and make all choices on cast
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertPowerToughness(playerA, "Aven Trooper", 1 + 1, 1 + 2);
}
@Test
public void test_AI_PlayOnePriority_WithChoicesOnResolve() {
// {2}{B}, {T}: Target player discards a card. Activate only as a sorcery.
addCard(Zone.BATTLEFIELD, playerA, "Cat Burglar", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
//
addCard(Zone.HAND, playerB, "Mountain", 5);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}, {T}: Target player", playerB);
// AI must be able to use choices
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerB, "Mountain", 1);
}
@Test
public void test_AI_PlayManyPrioritiesInOneStep() {
addCard(Zone.HAND, playerA, "Lightning Bolt", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
//
@ -50,6 +110,86 @@ public class TestFrameworkCanPlayAITest extends CardTestPlayerBaseWithAIHelps {
assertPermanentCount(playerB, "Balduvian Bears", 5 - 3);
}
@Test
public void test_AI_CleanStepCommands() {
// some step commands don't have priorities, so it must be clean another way, e.g. on start of the turn
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerA);
setStopAt(3, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute(); // on failed clean code it will raise error about not used command
}
@Test
public void test_AI_PlayStep_CallRuntimeCommandsInsideAIControl() {
// make sure runtime check commands can be called under AI control
runCode("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Assert.assertFalse("must be non AI before", player.isComputer());
});
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, PhaseStep.END_TURN, playerA);
checkLife("on same start", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20);
runCode("on inner step", 1, PhaseStep.BEGIN_COMBAT, playerA, (info, player, game) -> {
Assert.assertTrue("must be AI", player.isComputer());
});
//
checkLife("on inner step", 1, PhaseStep.DECLARE_ATTACKERS, playerA, 20);
runCode("on inner step", 1, PhaseStep.BEGIN_COMBAT, playerA, (info, player, game) -> {
Assert.assertTrue("must be AI", player.isComputer());
});
//
checkLife("on same end", 1, PhaseStep.END_TURN, playerA, 20);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
}
@Test
public void test_AI_PlayPriority_CallRuntimeCommandsInsideAIControl() {
// make sure runtime check commands can be called under AI control
runCode("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Assert.assertFalse("must be non AI before", player.isComputer());
});
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
checkLife("on same start", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20);
runCode("on same start", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Assert.assertFalse("must be non AI after", player.isComputer());
});
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
}
@Test
public void test_AI_PlayManyPrioritiesInManySteps() {
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
//
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
//
addCard(Zone.HAND, playerA, "Mountain", 1);
// AI must play multiple actions in multiple steps:
// - cast bolt
// - play land on second main
aiPlayStep(1, PhaseStep.DECLARE_ATTACKERS, PhaseStep.POSTCOMBAT_MAIN, playerA);
setStopAt(3, PhaseStep.END_TURN); // make sure no land plays on turn 1
setStrictChooseMode(true);
execute();
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertPermanentCount(playerB, "Balduvian Bears", 0);
assertPermanentCount(playerA, "Mountain", 2 + 1); // must play land on second main
}
@Test
public void test_AI_Attack() {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);

View file

@ -0,0 +1,71 @@
package org.mage.test.cards.damage;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author JayDi85
*/
public class AssignDamageTest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_ThornElemental_Manual_DamageToBlock() {
// 7/7
// You may have Thorn Elemental assign its combat damage as though it weren't blocked.
addCard(Zone.BATTLEFIELD, playerA, "Thorn Elemental", 1);
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
// attack and kill bear due block
attack(1, playerA, "Thorn Elemental");
block(1, playerB, "Grizzly Bears", "Thorn Elemental");
setChoice(playerA, false); // use blocked damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20);
assertGraveyardCount(playerB, "Grizzly Bears", 1);
}
@Test
public void test_ThornElemental_Manual_DamageToPlayer() {
// 7/7
// You may have Thorn Elemental assign its combat damage as though it weren't blocked.
addCard(Zone.BATTLEFIELD, playerA, "Thorn Elemental", 1);
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
// attack and ignore bear
attack(1, playerA, "Thorn Elemental");
block(1, playerB, "Grizzly Bears", "Thorn Elemental");
setChoice(playerA, true); // use ignored damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 7);
assertDamageReceived(playerB, "Grizzly Bears", 0);
}
@Test
public void test_ThornElemental_AI() {
// 7/7
// You may have Thorn Elemental assign its combat damage as though it weren't blocked.
addCard(Zone.BATTLEFIELD, playerA, "Thorn Elemental", 1);
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
// AI must attack and decide to ignore block damage (e.g. damage a player)
aiPlayStep(1, PhaseStep.DECLARE_ATTACKERS, playerA);
block(1, playerB, "Grizzly Bears", "Thorn Elemental");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 7);
assertDamageReceived(playerB, "Grizzly Bears", 0);
}
}

View file

@ -489,6 +489,7 @@ public class RollDiceTest extends CardTestPlayerBaseWithAIHelps {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Box of Free-Range Goblins");
setDieRollResult(playerA, 3); // normal roll
setDieRollResult(playerA, 6); // additional roll
// AI must choose max value due good outcome
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);

View file

@ -59,7 +59,7 @@ public class RadiateTest extends CardTestPlayerBaseWithAIHelps {
addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 2);
// cast bolt and copy spell for each another target
// must call commands manually cause it's a bad scenario and AI don't cast it itself
// must call commands manually because it's a bad scenario and AI don't cast it itself
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Radiate", "Lightning Bolt", "Lightning Bolt");
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); // but AI can choose targets

View file

@ -49,12 +49,14 @@ public class PlayerAction {
/**
* Calls after action removed from commands queue later (for multi steps
* action, e.g.AI related)
*
* @param game
* @param player
* action, e.g. AI related)
*/
public void onActionRemovedLater(Game game, TestPlayer player) {
//
}
@Override
public String toString() {
return "T" + this.turnNum + "." + this.step.getStepShortText() + ": " + this.action;
}
}

View file

@ -54,7 +54,6 @@ import mage.util.MultiAmountMessage;
import mage.util.RandomUtil;
import org.apache.log4j.Logger;
import org.junit.Assert;
import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*;
import java.io.Serializable;
import java.util.*;
@ -62,6 +61,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*;
/**
* Basic implementation of testable player
*
@ -90,15 +91,16 @@ public class TestPlayer implements Player {
// warning, test player do not restore own data by game rollback
// full playable AI, TODO: can be deleted?
private boolean AIPlayer;
// full playable AI
private boolean AIPlayer; // TODO: better rename
// AI simulates a real game, e.g. ignores strict mode and play command/priority, see aiXXX commands
// true - unit tests uses real AI logic (e.g. AI hints and AI workarounds in cards)
// false - unit tests uses Human logic and dialogs
// false - unit tests uses Human logic and dialogs (in non-strict mode AI replace miss target/choice commands)
private boolean AIRealGameSimulation = false;
private PhaseStep AIRealGameControlUntil = null; // enable temporary AI control until some point in time
private final List<PlayerAction> actions = new ArrayList<>();
private final Map<PlayerAction, PhaseStep> actionsToRemoveLater = new HashMap<>(); // remove actions later, on next step (e.g. for AI commands)
//private final Map<PlayerAction, PhaseStep> actionsToRemoveLater = new HashMap<>(); // remove actions after some step (used for AI commands)
private final Map<Integer, HashMap<UUID, ArrayList<PlayerAction>>> rollbackActions = new HashMap<>(); // actions to add after a executed rollback
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)
@ -140,6 +142,7 @@ public class TestPlayer implements Player {
public TestPlayer(final TestPlayer testPlayer) {
this.AIPlayer = testPlayer.AIPlayer;
this.AIRealGameSimulation = testPlayer.AIRealGameSimulation;
this.AIRealGameControlUntil = testPlayer.AIRealGameControlUntil;
this.foundNoAction = testPlayer.foundNoAction;
this.actions.addAll(testPlayer.actions);
this.choices.addAll(testPlayer.choices);
@ -573,17 +576,23 @@ public class TestPlayer implements Player {
@Override
public boolean priority(Game game) {
// later remove actions (ai commands related)
if (actionsToRemoveLater.size() > 0) {
List<PlayerAction> removed = new ArrayList<>();
actionsToRemoveLater.forEach((action, step) -> {
if (game.getTurnStepType() != step) {
action.onActionRemovedLater(game, this);
actions.remove(action);
removed.add(action);
}
});
removed.forEach(actionsToRemoveLater::remove);
boolean oldControl = AIRealGameSimulation;
if (AIPlayer) {
// full AI control
changeAIControl(game, true);
} else {
// temporary AI control
// after enabled on priority it must work until end of the priority
// e.g. AI can be called multiple times in complex choices
if (game.getTurnStepType().equals(PhaseStep.UPKEEP)) {
// reset
AIRealGameControlUntil = null;
changeAIControl(game, false);
} else {
// setup
boolean enable = AIRealGameControlUntil != null && game.getTurnStepType().getIndex() <= AIRealGameControlUntil.getIndex();
changeAIControl(game, enable);
}
}
// fake test ability for triggers and events
@ -746,19 +755,31 @@ public class TestPlayer implements Player {
String command = action.getAction();
command = command.substring(command.indexOf(AI_PREFIX) + AI_PREFIX.length());
// play priority
if (command.equals(AI_COMMAND_PLAY_PRIORITY)) {
AIRealGameSimulation = true; // disable on action's remove
// play single priority, two modes support:
// - really single priority
// - multiple priorities until empty stack
if (command.startsWith(AI_COMMAND_PLAY_PRIORITY)) {
boolean needEmptyStack = Boolean.parseBoolean(command.split(AI_PARAM_DELIMETER)[1]);
changeAIControl(game, true);
computerPlayer.priority(game);
actions.remove(action);
if (!needEmptyStack || game.getStack().isEmpty()) {
changeAIControl(game, false);
actions.remove(action);
computerPlayer.resetPassed(); // remove AI's pass, so runtime/check commands can be executed in same priority
}
// control will be disabled on next priority, not here
// (require to process triggers and other non-direct actions and choices)
return true;
}
// play step
if (command.equals(AI_COMMAND_PLAY_STEP)) {
AIRealGameSimulation = true; // disable on action's remove
actionsToRemoveLater.put(action, game.getTurnStepType());
// play multiple priorities on one or multiple steps
if (command.startsWith(AI_COMMAND_PLAY_STEP)) {
PhaseStep endStep = PhaseStep.fromString(command.split(AI_PARAM_DELIMETER)[1]);
changeAIControl(game, true); // enable AI
AIRealGameControlUntil = endStep; // disable on end step
computerPlayer.priority(game);
actions.remove(action);
computerPlayer.resetPassed(); // remove AI's pass, so runtime/check commands can be executed in same priority
return true;
}
@ -1087,6 +1108,7 @@ public class TestPlayer implements Player {
} // turn/step
}
// normal priority (by AI or pass)
tryToPlayPriority(game);
// check to prevent endless loops
@ -1103,6 +1125,16 @@ public class TestPlayer implements Player {
return false;
}
private void changeAIControl(Game game, boolean enable) {
if (AIRealGameSimulation != enable) {
LOGGER.info("AI control for " + getName()
+ " " + (enable ? "ENABLED" : "DISABLED")
//+ " on T" + game.getTurnNum() + "." + game.getTurnStepType().getStepShortText());
+ " on " + game);
}
AIRealGameSimulation = enable;
}
/**
* Adds actions to the player actions after an executed rollback Actions
* have to be added after the rollback because otherwise the actions are
@ -1130,7 +1162,7 @@ public class TestPlayer implements Player {
}
private void tryToPlayPriority(Game game) {
if (AIPlayer) {
if (AIPlayer || AIRealGameSimulation) {
computerPlayer.priority(game);
} else {
computerPlayer.pass(game);
@ -1788,16 +1820,6 @@ public class TestPlayer implements Player {
boolean madeAttackByAction = false;
for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext(); ) {
PlayerAction action = it.next();
// aiXXX commands
if (action.getTurnNum() == game.getTurnNum() && action.getAction().equals(AI_PREFIX + AI_COMMAND_PLAY_STEP)) {
mustAttackByAction = true;
madeAttackByAction = true;
this.computerPlayer.selectAttackers(game, attackingPlayerId);
// play step action will be removed on step end
continue;
}
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) {
mustAttackByAction = true;
String command = action.getAction();
@ -1861,7 +1883,7 @@ public class TestPlayer implements Player {
}
// AI FULL play if no actions available
if (!mustAttackByAction && this.AIPlayer) {
if (!mustAttackByAction && (this.AIPlayer || this.AIRealGameSimulation)) {
this.computerPlayer.selectAttackers(game, attackingPlayerId);
}
}
@ -1879,15 +1901,6 @@ public class TestPlayer implements Player {
boolean mustBlockByAction = false;
for (PlayerAction action : tempActions) {
// aiXXX commands
if (action.getTurnNum() == game.getTurnNum() && action.getAction().equals(AI_PREFIX + AI_COMMAND_PLAY_STEP)) {
mustBlockByAction = true;
this.computerPlayer.selectBlockers(source, game, defendingPlayerId);
// play step action will be removed on step end
continue;
}
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) {
mustBlockByAction = true;
String command = action.getAction();
@ -1916,7 +1929,7 @@ public class TestPlayer implements Player {
checkMultipleBlockers(game, blockedCreaturesList);
// AI FULL play if no actions available
if (!mustBlockByAction && this.AIPlayer) {
if (!mustBlockByAction && (this.AIPlayer || this.AIRealGameSimulation)) {
this.computerPlayer.selectBlockers(source, game, defendingPlayerId);
}
}
@ -4606,10 +4619,6 @@ public class TestPlayer implements Player {
return computerPlayer;
}
public void setAIRealGameSimulation(boolean AIRealGameSimulation) {
this.AIRealGameSimulation = AIRealGameSimulation;
}
public Map<Integer, HashMap<UUID, ArrayList<org.mage.test.player.PlayerAction>>> getRollbackActions() {
return rollbackActions;
}

View file

@ -59,8 +59,7 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl {
protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) {
if (getFullSimulatedPlayers().contains(name)) {
TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7(name, RangeOfInfluence.ONE, getSkillLevel()));
testPlayer.setAIPlayer(true);
testPlayer.setAIRealGameSimulation(true); // enable full AI support (game simulations) for all turns by default
testPlayer.setAIPlayer(true); // enable full AI support (game simulations) for all turns by default
return testPlayer;
}
return super.createPlayer(name, rangeOfInfluence);

View file

@ -27,14 +27,11 @@ import mage.player.ai.ComputerPlayerMCTS;
import mage.players.ManaPool;
import mage.players.Player;
import mage.server.game.GameSessionPlayer;
import mage.util.CardUtil;
import mage.util.ThreadUtils;
import mage.utils.SystemUtil;
import mage.util.CardUtil;
import mage.view.GameView;
import org.junit.Assert;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.mage.test.player.PlayerAction;
import org.mage.test.player.TestPlayer;
@ -48,6 +45,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.junit.Assert.assertTrue;
/**
* API for test initialization and asserting the test results.
*
@ -61,10 +60,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
private static final boolean SHOW_EXECUTE_TIME_PER_TEST = false;
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_PREFIX = "check:"; // prefix for all check commands
public static final String CHECK_PARAM_DELIMETER = "#";
public static final String SHOW_PREFIX = "show:"; // prefix for all show commands
public static final String AI_PREFIX = "ai:"; // prefix for all ai commands
public static final String AI_PARAM_DELIMETER = "#";
public static final String RUN_PREFIX = "run:"; // prefix for all run commands
// prefix for activate commands
@ -1769,36 +1769,59 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* AI play one PRIORITY with multi game simulations like real game
* (calcs and play ONE best action, can be called with stack)
* All choices must be made by AI (e.g.strict mode possible)
*
* @param turnNum
* @param step
* @param player
* <p>
* Warning, by default it will take control until stack resolved
*/
public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player) {
aiPlayPriority(turnNum, step, player, true);
}
/**
* AI play one PRIORITY
*
* @param untilStackResolved use false to stop AI after first cast
*/
public void aiPlayPriority(int turnNum, PhaseStep step, TestPlayer player, boolean untilStackResolved) {
assertAiPlayAndGameCompatible(player);
addPlayerAction(player, createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_PRIORITY));
addPlayerAction(player, createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_PRIORITY + AI_PARAM_DELIMETER + untilStackResolved));
}
/**
* 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) All
* choices must be made by AI (e.g. strict mode possible)
* actions until step ends, can be called in the middle of the step)
* All choices must be made by AI (e.g. strict mode possible)
* <p>
* Can be used for AI's declare of attackers/blockers
*/
public void aiPlayStep(int turnNum, PhaseStep step, TestPlayer player) {
aiPlayStep(turnNum, step, step, player);
}
public void aiPlayStep(int turnNum, PhaseStep startStep, PhaseStep endStep, TestPlayer player) {
assertAiPlayAndGameCompatible(player);
addPlayerAction(player, createAIPlayerAction(turnNum, step, AI_COMMAND_PLAY_STEP));
// direct steps support removed to simplify AI enabling code
// (no needs in code duplicating in selectAttackers and selectBlockers methods anymore)
if (startStep == PhaseStep.DECLARE_ATTACKERS
|| startStep == PhaseStep.DECLARE_BLOCKERS) {
PhaseStep newStartStep = PhaseStep.BEGIN_COMBAT;
PhaseStep newEndStep = endStep == startStep ? PhaseStep.END_COMBAT : endStep;
aiPlayStep(turnNum, newStartStep, newEndStep, player);
return;
}
if (startStep == PhaseStep.UPKEEP
|| startStep == PhaseStep.CLEANUP
|| startStep == PhaseStep.FIRST_COMBAT_DAMAGE
|| startStep == PhaseStep.COMBAT_DAMAGE) {
Assert.fail("AI can't be started from step without priority");
}
addPlayerAction(player, createAIPlayerAction(turnNum, startStep, AI_COMMAND_PLAY_STEP + AI_PARAM_DELIMETER + endStep.toString()));
}
public PlayerAction createAIPlayerAction(int turnNum, PhaseStep step, String aiCommand) {
// AI commands must disable and enable real game simulation and strict mode
return new PlayerAction("", turnNum, step, AI_PREFIX + aiCommand) {
@Override
public void onActionRemovedLater(Game game, TestPlayer player) {
player.setAIRealGameSimulation(false);
}
};
return new PlayerAction("", turnNum, step, AI_PREFIX + aiCommand);
}
private void assertAiPlayAndGameCompatible(TestPlayer player) {