forked from External/mage
Test framework: many improvements and fixes:
* added support to use custom cards with any abilities/effects (addCustomCardWithAbility); * added support of multiplayer games with all range (CardTestMultiPlayerBaseWithRangeAll); * added realtime checks for permanent counters (checkPermanentCounters); * added wrong attack commands check in strict mode; * fixed that added by addCard command cards don't init continues effects; * fixed that block commands don't removed from actions queue;
This commit is contained in:
parent
0bb735b482
commit
dc04092fce
8 changed files with 163 additions and 22 deletions
|
|
@ -19,6 +19,7 @@ import mage.cards.decks.Deck;
|
|||
import mage.choices.Choice;
|
||||
import mage.constants.*;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.counters.Counters;
|
||||
import mage.designations.Designation;
|
||||
import mage.designations.DesignationType;
|
||||
|
|
@ -639,6 +640,13 @@ public class TestPlayer implements Player {
|
|||
wasProccessed = true;
|
||||
}
|
||||
|
||||
// check permanent counters: card name, counter type, count
|
||||
if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNTERS) && params.length == 4) {
|
||||
assertPermanentCounters(action, game, computerPlayer, params[1], CounterType.findByName(params[2]), Integer.parseInt(params[3]));
|
||||
actions.remove(action);
|
||||
wasProccessed = true;
|
||||
}
|
||||
|
||||
// check exile count: card name, count
|
||||
if (params[0].equals(CHECK_COMMAND_EXILE_COUNT) && params.length == 3) {
|
||||
assertExileCount(action, game, computerPlayer, params[1], Integer.parseInt(params[2]));
|
||||
|
|
@ -836,8 +844,8 @@ public class TestPlayer implements Player {
|
|||
|
||||
List<String> data = cards.stream()
|
||||
.map(c -> (c.getIdName()
|
||||
+ " - " + c.getPower().getValue()
|
||||
+ "/" + c.getToughness().getValue()
|
||||
+ " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
|
||||
+ (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
|
||||
+ ", " + (c.isTapped() ? "Tapped" : "Untapped")
|
||||
+ (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName())
|
||||
))
|
||||
|
|
@ -949,6 +957,17 @@ public class TestPlayer implements Player {
|
|||
Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must exists in " + count + " instances", count, foundedCount);
|
||||
}
|
||||
|
||||
private void assertPermanentCounters(PlayerAction action, Game game, Player player, String permanentName, CounterType counterType, int count) {
|
||||
int foundedCount = 0;
|
||||
for (Permanent perm : game.getBattlefield().getAllPermanents()) {
|
||||
if (perm.getName().equals(permanentName) && perm.getControllerId().equals(player.getId())) {
|
||||
foundedCount = perm.getCounters(game).getCount(counterType);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(action.getActionName() + " - permanent " + permanentName + " must have " + count + " " + counterType.toString(), count, foundedCount);
|
||||
}
|
||||
|
||||
private void assertExileCount(PlayerAction action, Game game, Player player, String permanentName, int count) {
|
||||
int foundedCount = 0;
|
||||
for (Card card : game.getExile().getAllCards(game)) {
|
||||
|
|
@ -1151,10 +1170,12 @@ public class TestPlayer implements Player {
|
|||
public void selectAttackers(Game game, UUID attackingPlayerId) {
|
||||
// Loop through players and validate can attack/block this turn
|
||||
UUID defenderId = null;
|
||||
//List<PlayerAction>
|
||||
boolean mustAttackByAction = false;
|
||||
boolean madeAttackByAction = false;
|
||||
for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext(); ) {
|
||||
PlayerAction action = it.next();
|
||||
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("attack:")) {
|
||||
mustAttackByAction = true;
|
||||
String command = action.getAction();
|
||||
command = command.substring(command.indexOf("attack:") + 7);
|
||||
String[] groups = command.split("\\$");
|
||||
|
|
@ -1198,9 +1219,13 @@ public class TestPlayer implements Player {
|
|||
if (attacker != null && attacker.canAttack(defenderId, game)) {
|
||||
computerPlayer.declareAttacker(attacker.getId(), defenderId, game, false);
|
||||
it.remove();
|
||||
madeAttackByAction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mustAttackByAction && !madeAttackByAction) {
|
||||
this.chooseStrictModeFailed(game, "select attackers must use attack command but don't");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1212,10 +1237,12 @@ public class TestPlayer implements Player {
|
|||
@Override
|
||||
public void selectBlockers(Game game, UUID defendingPlayerId) {
|
||||
|
||||
List<PlayerAction> tempActions = new ArrayList<>(actions);
|
||||
|
||||
UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next();
|
||||
// Map of Blocker reference -> list of creatures blocked
|
||||
Map<MageObjectReference, List<MageObjectReference>> blockedCreaturesByCreature = new HashMap<>();
|
||||
for (PlayerAction action : actions) {
|
||||
for (PlayerAction action : tempActions) {
|
||||
if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) {
|
||||
String command = action.getAction();
|
||||
command = command.substring(command.indexOf("block:") + 6);
|
||||
|
|
@ -1226,6 +1253,7 @@ public class TestPlayer implements Player {
|
|||
Permanent blocker = findPermanent(new FilterControlledPermanent(), blockerName, computerPlayer.getId(), game);
|
||||
if (canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) {
|
||||
computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game);
|
||||
actions.remove(action);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(blockerName + " cannot block " + attackerName + " it is already blocking the maximum amount of creatures.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.mage.test.serverside.base;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.FreeForAll;
|
||||
|
|
@ -9,13 +8,14 @@ import mage.game.GameException;
|
|||
import mage.game.mulligan.VancouverMulligan;
|
||||
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Base class for testing single cards and effects in multiplayer game. For PvP
|
||||
* games {
|
||||
*
|
||||
* @see CardTestPlayerBase}
|
||||
*
|
||||
* @author magenoxx_at_gmail.com
|
||||
* @see CardTestPlayerBase}
|
||||
*/
|
||||
public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl {
|
||||
|
||||
|
|
@ -29,5 +29,4 @@ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl {
|
|||
playerD = createPlayer(game, playerD, "PlayerD");
|
||||
return game;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package org.mage.test.serverside.base;
|
||||
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.FreeForAll;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import mage.game.mulligan.VancouverMulligan;
|
||||
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* @author JayDi82
|
||||
*/
|
||||
public abstract class CardTestMultiPlayerBaseWithRangeAll extends CardTestPlayerAPIImpl {
|
||||
|
||||
@Override
|
||||
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||
Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20);
|
||||
// Player order: A -> D -> C -> B
|
||||
playerA = createPlayer(game, playerA, "PlayerA");
|
||||
playerB = createPlayer(game, playerB, "PlayerB");
|
||||
playerC = createPlayer(game, playerC, "PlayerC");
|
||||
playerD = createPlayer(game, playerD, "PlayerD");
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package org.mage.test.serverside.base;
|
||||
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.decks.DeckCardLists;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
|
|
@ -33,7 +36,7 @@ import java.util.regex.Pattern;
|
|||
/**
|
||||
* Base class for all tests.
|
||||
*
|
||||
* @author ayratn
|
||||
* @author ayratn, JayDi85
|
||||
*/
|
||||
public abstract class MageTestPlayerBase {
|
||||
|
||||
|
|
@ -333,10 +336,63 @@ public abstract class MageTestPlayerBase {
|
|||
return new TestPlayer(new TestComputerPlayer(name, rangeOfInfluence));
|
||||
}
|
||||
|
||||
public void setStrictChooseMode(boolean enable) {
|
||||
protected void setStrictChooseMode(boolean enable) {
|
||||
if (playerA != null) playerA.setChooseStrictMode(enable);
|
||||
if (playerB != null) playerB.setChooseStrictMode(enable);
|
||||
if (playerC != null) playerC.setChooseStrictMode(enable);
|
||||
if (playerD != null) playerD.setChooseStrictMode(enable);
|
||||
}
|
||||
|
||||
protected void addCustomCardWithAbility(String customName, TestPlayer controllerPlayer, Ability ability) {
|
||||
// add custom card with selected ability to battlefield
|
||||
CustomTestCard.clearCustomAbilities(customName);
|
||||
CustomTestCard.addCustomAbility(customName, ability);
|
||||
CardSetInfo testSet = new CardSetInfo(customName, "custom", "123", Rarity.COMMON);
|
||||
PermanentCard card = new PermanentCard(new CustomTestCard(controllerPlayer.getId(), testSet), controllerPlayer.getId(), currentGame);
|
||||
getBattlefieldCards(controllerPlayer).add(card);
|
||||
}
|
||||
}
|
||||
|
||||
// custom card with global abilities list to init (can contains abilities per card name)
|
||||
class CustomTestCard extends CardImpl {
|
||||
|
||||
static private Map<String, Abilities<Ability>> abilitiesList = new HashMap<>(); // card name -> abilities
|
||||
|
||||
static protected void addCustomAbility(String cardName, Ability ability) {
|
||||
if (!abilitiesList.containsKey(cardName)) {
|
||||
abilitiesList.put(cardName, new AbilitiesImpl<>());
|
||||
}
|
||||
Abilities<Ability> oldAbilities = abilitiesList.get(cardName);
|
||||
oldAbilities.add(ability);
|
||||
}
|
||||
|
||||
static protected void clearCustomAbilities(String cardName) {
|
||||
abilitiesList.remove(cardName);
|
||||
}
|
||||
|
||||
static public void clearCustomAbilities() {
|
||||
abilitiesList.clear();
|
||||
}
|
||||
|
||||
|
||||
public CustomTestCard(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "");
|
||||
|
||||
// load dynamic abilities by card name
|
||||
Abilities<Ability> extraAbitilies = abilitiesList.get(setInfo.getName());
|
||||
if (extraAbitilies != null) {
|
||||
for (Ability ability : extraAbitilies) {
|
||||
this.addAbility(ability.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CustomTestCard(final CustomTestCard card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomTestCard copy() {
|
||||
return new CustomTestCard(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
public static final String CHECK_COMMAND_LIFE = "LIFE";
|
||||
public static final String CHECK_COMMAND_ABILITY = "ABILITY";
|
||||
public static final String CHECK_COMMAND_PERMANENT_COUNT = "PERMANENT_COUNT";
|
||||
public static final String CHECK_COMMAND_PERMANENT_COUNTERS = "PERMANENT_COUNTERS";
|
||||
public static final String CHECK_COMMAND_EXILE_COUNT = "EXILE_COUNT";
|
||||
public static final String CHECK_COMMAND_HAND_COUNT = "HAND_COUNT";
|
||||
public static final String CHECK_COMMAND_HAND_CARD_COUNT = "HAND_CARD_COUNT";
|
||||
|
|
@ -232,10 +233,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
+ " (found actions after stop on " + maxTurn + " / " + maxPhase + ")",
|
||||
(maxTurn > this.stopOnTurn) || (maxTurn == this.stopOnTurn && maxPhase > this.stopAtStep.getIndex()));
|
||||
|
||||
|
||||
for (Player player : currentGame.getPlayers().values()) {
|
||||
TestPlayer testPlayer = (TestPlayer) player;
|
||||
currentGame.cheat(player.getId(), getCommands(testPlayer));
|
||||
currentGame.cheat(player.getId(), getLibraryCards(testPlayer), getHandCards(testPlayer),
|
||||
currentGame.cheat(player.getId(), activePlayer.getId(), getLibraryCards(testPlayer), getHandCards(testPlayer),
|
||||
getBattlefieldCards(testPlayer), getGraveCards(testPlayer));
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +310,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, permanentName, count.toString());
|
||||
}
|
||||
|
||||
public void checkPermanentCounters(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, CounterType counterType, Integer count) {
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNTERS, permanentName, counterType.toString(), count.toString());
|
||||
}
|
||||
|
||||
public void checkExileCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) {
|
||||
//Assert.assertNotEquals("", permanentName);
|
||||
check(checkName, turnNum, step, player, CHECK_COMMAND_EXILE_COUNT, permanentName, count.toString());
|
||||
|
|
@ -1231,6 +1237,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
+ "])", count, player.getActions().size());
|
||||
}
|
||||
|
||||
public void assertActionsMustBeEmpty(TestPlayer player) throws AssertionError {
|
||||
if (!player.getActions().isEmpty()) {
|
||||
System.out.println("Remaining actions for " + player.getName() + " (" + player.getActions().size() + "):");
|
||||
player.getActions().stream().forEach(a -> {
|
||||
System.out.println("* turn " + a.getTurnNum() + " - " + a.getStep() + ": " + a.getActionName());
|
||||
});
|
||||
Assert.fail("Player " + player.getName() + " must have 0 actions but found " + player.getActions().size());
|
||||
}
|
||||
}
|
||||
|
||||
public void assertChoicesCount(TestPlayer player, int count) throws AssertionError {
|
||||
Assert.assertEquals("(Choices of " + player.getName() + ") Count are not equal (found " + player.getChoices() + ")", count, player.getChoices().size());
|
||||
}
|
||||
|
|
@ -1242,7 +1258,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
public void assertAllCommandsUsed() throws AssertionError {
|
||||
for (Player player : currentGame.getPlayers().values()) {
|
||||
TestPlayer testPlayer = (TestPlayer) player;
|
||||
assertActionsCount(testPlayer, 0);
|
||||
assertActionsMustBeEmpty(testPlayer);
|
||||
assertChoicesCount(testPlayer, 0);
|
||||
assertTargetsCount(testPlayer, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,11 @@ public enum CounterType {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static CounterType findByName(String name) {
|
||||
for (CounterType counterType : values()) {
|
||||
if (counterType.getName().equals(name)) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.events.Listener;
|
||||
import mage.game.events.PlayerQueryEvent;
|
||||
import mage.game.events.TableEvent;
|
||||
import mage.game.match.Match;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.game.mulligan.Mulligan;
|
||||
import mage.game.permanent.Battlefield;
|
||||
|
|
@ -433,7 +432,7 @@ public interface Game extends MageItem, Serializable {
|
|||
// game cheats (for tests only)
|
||||
void cheat(UUID ownerId, Map<Zone, String> commands);
|
||||
|
||||
void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard);
|
||||
void cheat(UUID ownerId, UUID activePlayerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard);
|
||||
|
||||
// controlling the behaviour of replacement effects while permanents entering the battlefield
|
||||
void setScopeRelevant(boolean scopeRelevant);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import mage.game.command.Emblem;
|
|||
import mage.game.command.Plane;
|
||||
import mage.game.events.*;
|
||||
import mage.game.events.TableEvent.EventType;
|
||||
import mage.game.mulligan.LondonMulligan;
|
||||
import mage.game.mulligan.Mulligan;
|
||||
import mage.game.permanent.Battlefield;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -128,7 +127,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
private int priorityTime;
|
||||
|
||||
private final int startLife;
|
||||
protected PlayerList playerList;
|
||||
protected PlayerList playerList; // auto-generated from state, don't copy
|
||||
|
||||
// infinite loop check (no copy of this attributes neccessary)
|
||||
private int infiniteLoopCounter; // used to check if the game is in an infinite loop
|
||||
|
|
@ -138,8 +137,9 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
|
||||
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
|
||||
// used to proceed player conceding requests
|
||||
private final LinkedList<UUID> concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game
|
||||
|
||||
// temporary store for income concede commands, don't copy
|
||||
private final LinkedList<UUID> concedingPlayers = new LinkedList<>();
|
||||
|
||||
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) {
|
||||
this.id = UUID.randomUUID();
|
||||
|
|
@ -2845,7 +2845,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard) {
|
||||
public void cheat(UUID ownerId, UUID activePlayerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard) {
|
||||
Player player = getPlayer(ownerId);
|
||||
if (player != null) {
|
||||
loadCards(ownerId, library);
|
||||
|
|
@ -2864,6 +2864,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
card.setZone(Zone.GRAVEYARD, this);
|
||||
player.getGraveyard().add(card);
|
||||
}
|
||||
|
||||
// warning, permanents go to battlefield without resolve, continuus effects must be init
|
||||
for (PermanentCard permanentCard : battlefield) {
|
||||
permanentCard.setZone(Zone.BATTLEFIELD, this);
|
||||
permanentCard.setOwnerId(ownerId);
|
||||
|
|
@ -2876,6 +2878,14 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
if (permanentCard.isTapped()) {
|
||||
newPermanent.setTapped(true);
|
||||
}
|
||||
|
||||
// init effects on static abilities (init continuous effects, warning, game state contains copy)
|
||||
for (ContinuousEffect effect : this.getState().getContinuousEffects().getLayeredEffects(this)) {
|
||||
Optional<Ability> ability = this.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst();
|
||||
if (ability.isPresent() && newPermanent.getId().equals(ability.get().getSourceId())) {
|
||||
effect.init(ability.get(), this, activePlayerId); // game is not setup yet, game.activePlayer is null -- need direct id
|
||||
}
|
||||
}
|
||||
}
|
||||
applyEffects();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue