forked from External/mage
test framework improves:
- now game logs will show stack ability on push and on resolve (before any choices); - now game logs will show used choices made by cast/activate, setChoice, setMode and addTarget commands (not work for AI tests, part of #13832); - improved choice logic for modes and yes/not dialogs (now it's use a more strictly checks, use TestPlayer.MODE_SKIP to stop mode selection); - improved error logs and testable dialogs menu in cheat mode;
This commit is contained in:
parent
a7a6ffd6f3
commit
e866707912
17 changed files with 553 additions and 410 deletions
|
|
@ -7,6 +7,7 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetPermanentOrPlayer;
|
||||
|
||||
/**
|
||||
|
|
@ -76,6 +77,10 @@ abstract class BaseTestableDialog implements TestableDialog {
|
|||
return createAnyTarget(min, max, false);
|
||||
}
|
||||
|
||||
static Target createPlayerTarget(int min, int max, boolean notTarget) {
|
||||
return new TargetPlayer(min, max, notTarget);
|
||||
}
|
||||
|
||||
private static Target createAnyTarget(int min, int max, boolean notTarget) {
|
||||
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,19 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
|
|||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)).aiMustChoose(false, 0));
|
||||
//
|
||||
// additional tests for 2 possible options limitation
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0", createPlayerTarget(0, 0, notTarget)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-1", createPlayerTarget(0, 1, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-2", createPlayerTarget(0, 2, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-5", createPlayerTarget(0, 5, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1", createPlayerTarget(1, 1, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-2", createPlayerTarget(1, 2, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-5", createPlayerTarget(1, 5, notTarget)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2", createPlayerTarget(2, 2, notTarget)).aiMustChoose(true, 2));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2-5", createPlayerTarget(2, 5, notTarget)).aiMustChoose(true, 2));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3", createPlayerTarget(3, 3, notTarget)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3-5", createPlayerTarget(3, 5, notTarget)).aiMustChoose(false, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import mage.constants.Outcome;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -120,10 +117,8 @@ public class TestableDialogsRunner {
|
|||
choice = prepareSelectDialogChoice(needGroup);
|
||||
player.choose(Outcome.Benefit, choice, game);
|
||||
if (choice.getChoiceKey() != null) {
|
||||
int needIndex = Integer.parseInt(choice.getChoiceKey());
|
||||
if (needIndex < this.dialogs.size()) {
|
||||
needDialog = this.dialogs.get(needIndex);
|
||||
}
|
||||
int needRegNumber = Integer.parseInt(choice.getChoiceKey());
|
||||
needDialog = this.dialogs.getOrDefault(needRegNumber, null);
|
||||
}
|
||||
}
|
||||
if (needDialog == null) {
|
||||
|
|
@ -144,15 +139,20 @@ public class TestableDialogsRunner {
|
|||
Choice choice = new ChoiceImpl(false);
|
||||
choice.setMessage("Choose dialogs group to run");
|
||||
|
||||
// use min reg number for groups
|
||||
Map<String, Integer> groupNumber = new HashMap<>();
|
||||
this.dialogs.values().forEach(dialog -> {
|
||||
groupNumber.put(dialog.getGroup(), Math.min(groupNumber.getOrDefault(dialog.getGroup(), Integer.MAX_VALUE), dialog.getRegNumber()));
|
||||
});
|
||||
|
||||
// main groups
|
||||
int recNumber = 0;
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
recNumber++;
|
||||
String group = groups.get(i);
|
||||
Integer groupMinNumber = groupNumber.getOrDefault(group, 0);
|
||||
choice.withItem(
|
||||
String.valueOf(i),
|
||||
String.format("%02d. %s", recNumber, group),
|
||||
recNumber,
|
||||
String.format("%02d. %s", groupMinNumber, group),
|
||||
groupMinNumber,
|
||||
ChoiceHintType.TEXT,
|
||||
String.join("<br>", group)
|
||||
);
|
||||
|
|
@ -186,18 +186,15 @@ public class TestableDialogsRunner {
|
|||
private Choice prepareSelectDialogChoice(String needGroup) {
|
||||
Choice choice = new ChoiceImpl(false);
|
||||
choice.setMessage("Choose game dialog to run from " + needGroup);
|
||||
int recNumber = 0;
|
||||
for (int i = 0; i < this.dialogs.size(); i++) {
|
||||
TestableDialog dialog = this.dialogs.get(i);
|
||||
for (TestableDialog dialog : this.dialogs.values()) {
|
||||
if (!dialog.getGroup().equals(needGroup)) {
|
||||
continue;
|
||||
}
|
||||
recNumber++;
|
||||
String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription());
|
||||
choice.withItem(
|
||||
String.valueOf(i),
|
||||
String.format("%02d. %s", recNumber, info),
|
||||
recNumber,
|
||||
String.valueOf(dialog.getRegNumber()),
|
||||
String.format("%02d. %s", dialog.getRegNumber(), info),
|
||||
dialog.getRegNumber(),
|
||||
ChoiceHintType.TEXT,
|
||||
String.join("<br>", info)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -405,13 +405,13 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
if (effect != null
|
||||
&& stackObject.getControllerId().equals(playerId)) {
|
||||
Target target = effect.getTarget();
|
||||
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
|
||||
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game, null)) {
|
||||
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
|
||||
Game sim = game.createSimulationForAI();
|
||||
StackAbility newAbility = (StackAbility) stackObject.copy();
|
||||
SearchEffect newEffect = getSearchEffect(newAbility);
|
||||
newEffect.getTarget().addTarget(targetId, newAbility, sim);
|
||||
sim.getStack().push(newAbility);
|
||||
sim.getStack().push(sim, newAbility);
|
||||
SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId());
|
||||
node.children.add(newNode);
|
||||
newNode.getTargets().add(targetId);
|
||||
|
|
@ -886,10 +886,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.addTarget(targetId, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -906,10 +906,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.add(targetId, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
|
|||
return list;
|
||||
}
|
||||
|
||||
protected void simulateOptions(Game game) {
|
||||
private void simulateOptions(Game game) {
|
||||
List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
|
||||
for (ActivatedAbility ability : playables) {
|
||||
if (ability.isManaAbility()) {
|
||||
|
|
@ -176,6 +176,10 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
|
|||
return options;
|
||||
}
|
||||
|
||||
// remove invalid targets
|
||||
// TODO: is it useless cause it already filtered before?
|
||||
options.removeIf(option -> !option.getTargets().isChosen(game));
|
||||
|
||||
if (AI_SIMULATE_ALL_BAD_AND_GOOD_TARGETS) {
|
||||
return options;
|
||||
}
|
||||
|
|
@ -315,7 +319,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
|
|||
List<Ability> options = getPlayableOptions(ability, game);
|
||||
if (options.isEmpty()) {
|
||||
logger.debug("simulating -- triggered ability:" + ability);
|
||||
game.getStack().push(new StackAbility(ability, playerId));
|
||||
game.getStack().push(game, new StackAbility(ability, playerId));
|
||||
if (ability.activate(game, false) && ability.isUsesStack()) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
}
|
||||
|
|
@ -337,9 +341,9 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
|
|||
|
||||
protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) {
|
||||
Game sim = game.createSimulationForAI();
|
||||
sim.getStack().push(new StackAbility(ability, playerId));
|
||||
sim.getStack().push(sim, new StackAbility(ability, playerId));
|
||||
if (ability.activate(sim, false) && ability.isUsesStack()) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
sim.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
}
|
||||
sim.applyEffects();
|
||||
SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId);
|
||||
|
|
|
|||
|
|
@ -142,17 +142,27 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
|
||||
// nothing to choose, e.g. X=0
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// default logic for any targets
|
||||
PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game);
|
||||
possibleTargetsSelector.findNewTargets(fromCards);
|
||||
|
||||
// nothing to choose, e.g. no valid targets
|
||||
if (!possibleTargetsSelector.hasAnyTargets()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't choose
|
||||
if (!possibleTargetsSelector.hasMinNumberOfTargets()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// good targets -- choose as much as possible
|
||||
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||
target.add(item.getId(), game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -226,7 +236,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
|
||||
// nothing to choose, e.g. X=0
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -238,6 +248,11 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
// can't choose
|
||||
if (!possibleTargetsSelector.hasMinNumberOfTargets()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// KILL PRIORITY
|
||||
if (outcome == Outcome.Damage) {
|
||||
// opponent first
|
||||
|
|
@ -251,7 +266,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
|
||||
target.addTarget(item.getId(), leftLife, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -268,7 +283,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
|
||||
target.addTarget(item.getId(), leftLife, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -283,7 +298,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
continue;
|
||||
}
|
||||
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -303,7 +318,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||
if (leftLife > 1) {
|
||||
target.addTarget(item.getId(), Math.min(leftLife - 1, target.getAmountRemaining()), source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -322,7 +337,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
return !target.getTargets().isEmpty();
|
||||
}
|
||||
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -339,7 +354,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
continue;
|
||||
}
|
||||
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +370,7 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
return !target.getTargets().isEmpty();
|
||||
}
|
||||
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ public class PossibleTargetsSelector {
|
|||
// collect new valid targets
|
||||
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream()
|
||||
.filter(id -> !target.contains(id))
|
||||
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
|
||||
.map(id -> {
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
|
|
@ -137,6 +136,10 @@ public class PossibleTargetsSelector {
|
|||
}
|
||||
}
|
||||
|
||||
public List<MageItem> getAny() {
|
||||
return this.any;
|
||||
}
|
||||
|
||||
public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
|
||||
if (item instanceof Player) {
|
||||
return item.getId().equals(abilityControllerId);
|
||||
|
|
@ -181,7 +184,12 @@ public class PossibleTargetsSelector {
|
|||
return false;
|
||||
}
|
||||
|
||||
boolean hasAnyTargets() {
|
||||
public boolean hasAnyTargets() {
|
||||
return !this.any.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasMinNumberOfTargets() {
|
||||
return this.target.getMinNumberOfTargets() == 0
|
||||
|| this.any.size() >= this.target.getMinNumberOfTargets();
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
}
|
||||
}
|
||||
if (ability.isUsesStack()) {
|
||||
game.getStack().push(new StackAbility(ability, playerId));
|
||||
game.getStack().push(game, new StackAbility(ability, playerId));
|
||||
if (ability.activate(game, false)) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
actionCount++;
|
||||
|
|
@ -187,8 +187,8 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
abort = true;
|
||||
}
|
||||
|
||||
protected boolean chooseRandom(Target target, Game game) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, game);
|
||||
private boolean chooseRandom(Target target, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
|
||||
if (possibleTargets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
@Override
|
||||
public boolean choose(Outcome outcome, Target target, Ability source, Game game) {
|
||||
if (this.isHuman()) {
|
||||
return chooseRandom(target, game);
|
||||
return chooseRandom(target, source, game);
|
||||
}
|
||||
return super.choose(outcome, target, source, game);
|
||||
}
|
||||
|
|
@ -241,7 +241,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
@Override
|
||||
public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) {
|
||||
if (this.isHuman()) {
|
||||
return chooseRandom(target, game);
|
||||
return chooseRandom(target, source, game);
|
||||
}
|
||||
return super.choose(outcome, target, source, game, options);
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, cards, source, game);
|
||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game, cards);
|
||||
if (possibleTargets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import mage.abilities.effects.common.InfoEffect;
|
|||
import mage.abilities.effects.common.continuous.PlayAdditionalLandsAllEffect;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.util.ConsoleUtil;
|
||||
import mage.utils.testers.TestableDialog;
|
||||
import mage.utils.testers.TestableDialogsRunner;
|
||||
import org.junit.Assert;
|
||||
|
|
@ -107,7 +108,7 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
@Test
|
||||
@Ignore // debug only - run single dialog by reg number
|
||||
public void test_RunSingle_Debugging() {
|
||||
int needRegNumber = 557;
|
||||
int needRegNumber = 5;
|
||||
|
||||
prepareCards();
|
||||
|
||||
|
|
@ -233,15 +234,15 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
if (resAssert == null) {
|
||||
totalUnknown++;
|
||||
status = "?";
|
||||
coloredTexts.put("?", asYellow("?"));
|
||||
coloredTexts.put("?", ConsoleUtil.asYellow("?"));
|
||||
} else if (resAssert.isEmpty()) {
|
||||
totalGood++;
|
||||
status = "OK";
|
||||
coloredTexts.put("OK", asGreen("OK"));
|
||||
coloredTexts.put("OK", ConsoleUtil.asGreen("OK"));
|
||||
} else {
|
||||
totalBad++;
|
||||
status = "FAIL";
|
||||
coloredTexts.put("FAIL", asRed("FAIL"));
|
||||
coloredTexts.put("FAIL", ConsoleUtil.asRed("FAIL"));
|
||||
assertError = resAssert;
|
||||
}
|
||||
if (!assertError.isEmpty()) {
|
||||
|
|
@ -256,8 +257,8 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
// print dialog error
|
||||
if (!assertError.isEmpty()) {
|
||||
coloredTexts.clear();
|
||||
coloredTexts.put(resAssert, asRed(resAssert));
|
||||
coloredTexts.put(resDebugSource, asRed(resDebugSource));
|
||||
coloredTexts.put(resAssert, ConsoleUtil.asRed(resAssert));
|
||||
coloredTexts.put(resDebugSource, ConsoleUtil.asRed(resDebugSource));
|
||||
String badAssert = getColoredRow(totalsRightFormat, coloredTexts, resAssert);
|
||||
String badDebugSource = getColoredRow(totalsRightFormat, coloredTexts, resDebugSource);
|
||||
if (firstBadDialog == null) {
|
||||
|
|
@ -283,15 +284,15 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
String badStats = String.format("%d bad", totalBad);
|
||||
String unknownStats = String.format("%d unknown", totalUnknown);
|
||||
coloredTexts.clear();
|
||||
coloredTexts.put(goodStats, String.format("%s good", asGreen(String.valueOf(totalGood))));
|
||||
coloredTexts.put(badStats, String.format("%s bad", asRed(String.valueOf(totalBad))));
|
||||
coloredTexts.put(unknownStats, String.format("%s unknown", asYellow(String.valueOf(totalUnknown))));
|
||||
coloredTexts.put(goodStats, String.format("%s good", ConsoleUtil.asGreen(String.valueOf(totalGood))));
|
||||
coloredTexts.put(badStats, String.format("%s bad", ConsoleUtil.asRed(String.valueOf(totalBad))));
|
||||
coloredTexts.put(unknownStats, String.format("%s unknown", ConsoleUtil.asYellow(String.valueOf(totalUnknown))));
|
||||
System.out.print(getColoredRow(totalsLeftFormat, coloredTexts, String.format("Total results: %s, %s, %s",
|
||||
goodStats, badStats, unknownStats)));
|
||||
// first error for fast access in big list
|
||||
if (totalDialogs > 1 && firstBadDialog != null) {
|
||||
System.out.println(horizontalBorder);
|
||||
System.out.print(getColoredRow(totalsRightFormat, coloredTexts, "First bad dialog: " + firstBadDialog.getRegNumber()));
|
||||
System.out.print(getColoredRow(totalsRightFormat, coloredTexts, "First bad dialog: " + firstBadDialog.getRegNumber() + " (debug it by test_RunSingle_Debugging)"));
|
||||
System.out.print(getColoredRow(totalsRightFormat, coloredTexts, firstBadDialog.getName() + " - " + firstBadDialog.getDescription()));
|
||||
System.out.print(firstBadAssert);
|
||||
System.out.print(firstBadDebugSource);
|
||||
|
|
@ -313,16 +314,4 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private String asRed(String text) {
|
||||
return "\u001B[31m" + text + "\u001B[0m";
|
||||
}
|
||||
|
||||
private String asGreen(String text) {
|
||||
return "\u001B[32m" + text + "\u001B[0m";
|
||||
}
|
||||
|
||||
private String asYellow(String text) {
|
||||
return "\u001B[33m" + text + "\u001B[0m";
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1706,6 +1706,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
private void assertAllCommandsUsed() throws AssertionError {
|
||||
for (Player player : currentGame.getPlayers().values()) {
|
||||
TestPlayer testPlayer = (TestPlayer) player;
|
||||
|
||||
if (testPlayer.isSkipAllNextChooseCommands()) {
|
||||
Assert.fail(testPlayer.getName() + " used skip next choose commands, but game do not call any choose dialog after it. Skip must be removed after debug.");
|
||||
}
|
||||
|
||||
assertActionsMustBeEmpty(testPlayer);
|
||||
assertChoicesCount(testPlayer, 0);
|
||||
assertTargetsCount(testPlayer, 0);
|
||||
|
|
@ -2008,7 +2013,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* @param step
|
||||
* @param player
|
||||
* @param cardName
|
||||
* @param targetName for modes you can add "mode=3" before target name;
|
||||
* @param targetName for non default mode you can add target by "mode=3target_name" style;
|
||||
* multiple targets can be separated by ^;
|
||||
* no target marks as TestPlayer.NO_TARGET;
|
||||
* warning, do not support cards with target adjusters - use addTarget instead
|
||||
|
|
@ -2315,6 +2320,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
* spell mode can be used only once like Demonic Pact, the
|
||||
* value has to be set to the number of the remaining modes
|
||||
* (e.g. if only 2 are left the number need to be 1 or 2).
|
||||
* If you need to partly select then use TestPlayer.MODE_SKIP
|
||||
*/
|
||||
public void setModeChoice(TestPlayer player, String choice) {
|
||||
player.addModeChoice(choice);
|
||||
|
|
@ -2439,6 +2445,26 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
gameOptions.skipInitShuffling = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug only: skip all choose commands after that command.
|
||||
* <p>
|
||||
* Alternative to comment/uncomment all test commands:
|
||||
* - insert skip before first choice command;
|
||||
* - run test and look at error message about miss choice;
|
||||
* - make sure test use correct choice;
|
||||
* - move skip command to next test's choice and repeat;
|
||||
*/
|
||||
protected void skipAllNextChooseCommands() {
|
||||
playerA.skipAllNextChooseCommands();
|
||||
playerB.skipAllNextChooseCommands();
|
||||
if (playerC != null) {
|
||||
playerC.skipAllNextChooseCommands();
|
||||
}
|
||||
if (playerD != null) {
|
||||
playerD.skipAllNextChooseCommands();
|
||||
}
|
||||
}
|
||||
|
||||
public void assertDamageReceived(Player player, String cardName, int expected) {
|
||||
Permanent p = getPermanent(cardName, player);
|
||||
if (p != null) {
|
||||
|
|
|
|||
|
|
@ -130,4 +130,9 @@ public class Mode implements Serializable {
|
|||
public int getPawPrintValue() {
|
||||
return pawPrintValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s", this.getEffects().getText(this));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package mage.collectors;
|
|||
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -11,10 +12,12 @@ import java.util.UUID;
|
|||
* Supported features:
|
||||
* - [x] collect and print game logs in server output, including unit tests
|
||||
* - [x] collect and save full games history and decks
|
||||
* - [ ] collect and print performance metrics like ApplyEffects calc time or inform players time (pings)
|
||||
* - [ ] collect and send metrics to third party tools like prometheus + grafana
|
||||
* - [ ] prepare "attachable" game data for bug reports
|
||||
* - [ ] record game replays data (GameView history)
|
||||
* - [ ] TODO: collect and print performance metrics like ApplyEffects calc time or inform players time (pings)
|
||||
* - [ ] TODO: collect and send metrics to third party tools like prometheus + grafana
|
||||
* - [x] tests: print used selections (choices, targets, modes, skips) TODO: add yes/no, replacement effect, coins, other choices
|
||||
* - [ ] TODO: tests: print additional info like current resolve ability?
|
||||
* - [ ] TODO: prepare "attachable" game data for bug reports
|
||||
* - [ ] TODO: record game replays data (GameView history)
|
||||
* <p>
|
||||
* How-to enable or disable:
|
||||
* - use java params like -Dxmage.dataCollectors.saveGameHistory=true
|
||||
|
|
@ -62,7 +65,27 @@ public interface DataCollector {
|
|||
void onChatTable(UUID tableId, String userName, String message);
|
||||
|
||||
/**
|
||||
* @param gameId chat sessings don't have full game access, so use onGameStart event to find game's ID before chat
|
||||
* @param gameId chat session don't have full game access, so use onGameStart event to find game's ID before chat
|
||||
*/
|
||||
void onChatGame(UUID gameId, String userName, String message);
|
||||
|
||||
/**
|
||||
* Tests only: on any non-target choice like yes/no, mode, etc
|
||||
*/
|
||||
void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason);
|
||||
|
||||
/**
|
||||
* Tests only: on any target choice
|
||||
*/
|
||||
void onTestsTargetUse(Game game, Player player, String usingTarget, String reason);
|
||||
|
||||
/**
|
||||
* Tests only: on push object to stack (calls before activate and make any choice/announce)
|
||||
*/
|
||||
void onTestsStackPush(Game game);
|
||||
|
||||
/**
|
||||
* Tests only: on stack object resolve (calls before starting resolve)
|
||||
*/
|
||||
void onTestsStackResolve(Game game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.collectors.services.PrintGameLogsDataCollector;
|
|||
import mage.collectors.services.SaveGameHistoryDataCollector;
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
|
|
@ -140,4 +141,28 @@ final public class DataCollectorServices implements DataCollector {
|
|||
public void onChatGame(UUID gameId, String userName, String message) {
|
||||
activeServices.forEach(c -> c.onChatGame(gameId, userName, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason) {
|
||||
if (game.isSimulation()) return;
|
||||
activeServices.forEach(c -> c.onTestsChoiceUse(game, player, usingChoice, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsTargetUse(Game game, Player player, String usingTarget, String reason) {
|
||||
if (game.isSimulation()) return;
|
||||
activeServices.forEach(c -> c.onTestsTargetUse(game, player, usingTarget, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackPush(Game game) {
|
||||
if (game.isSimulation()) return;
|
||||
activeServices.forEach(c -> c.onTestsStackPush(game));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackResolve(Game game) {
|
||||
if (game.isSimulation()) return;
|
||||
activeServices.forEach(c -> c.onTestsStackResolve(game));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.collectors.services;
|
|||
import mage.collectors.DataCollector;
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -67,4 +68,24 @@ public abstract class EmptyDataCollector implements DataCollector {
|
|||
public void onChatGame(UUID gameId, String userName, String message) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsChoiceUse(Game game, Player player, String usingChoice, String reason) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsTargetUse(Game game, Player player, String usingTarget, String reason) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackPush(Game game) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackResolve(Game game) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package mage.collectors.services;
|
||||
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.ConsoleUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
|
|
@ -40,9 +42,47 @@ public class PrintGameLogsDataCollector extends EmptyDataCollector {
|
|||
@Override
|
||||
public void onGameLog(Game game, String message) {
|
||||
String needMessage = Jsoup.parse(message).text();
|
||||
writeLog("GAME", "LOG", String.format("%s: %s",
|
||||
writeLog("LOG", "GAME", String.format("%s: %s",
|
||||
CardUtil.getTurnInfo(game),
|
||||
needMessage
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsChoiceUse(Game game, Player player, String choice, String reason) {
|
||||
String needReason = Jsoup.parse(reason).text();
|
||||
writeLog("LOG", "GAME", ConsoleUtil.asYellow(String.format("%s: %s using choice: %s%s",
|
||||
CardUtil.getTurnInfo(game),
|
||||
player.getName(),
|
||||
choice,
|
||||
reason.isEmpty() ? "" : " (" + needReason + ")"
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsTargetUse(Game game, Player player, String target, String reason) {
|
||||
String needReason = Jsoup.parse(reason).text();
|
||||
writeLog("LOG", "GAME", ConsoleUtil.asYellow(String.format("%s: %s using target: %s%s",
|
||||
CardUtil.getTurnInfo(game),
|
||||
player.getName(),
|
||||
target,
|
||||
reason.isEmpty() ? "" : " (" + needReason + ")"
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackPush(Game game) {
|
||||
writeLog("LOG", "GAME", String.format("%s: Stack push: %s",
|
||||
CardUtil.getTurnInfo(game),
|
||||
game.getStack().toString()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestsStackResolve(Game game) {
|
||||
writeLog("LOG", "GAME", String.format("%s: Stack resolve: %s",
|
||||
CardUtil.getTurnInfo(game),
|
||||
game.getStack().toString()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
Mage/src/main/java/mage/util/ConsoleUtil.java
Normal file
19
Mage/src/main/java/mage/util/ConsoleUtil.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package mage.util;
|
||||
|
||||
/**
|
||||
* Helper class to work with console logs
|
||||
*/
|
||||
public class ConsoleUtil {
|
||||
|
||||
public static String asRed(String text) {
|
||||
return "\u001B[31m" + text + "\u001B[0m";
|
||||
}
|
||||
|
||||
public static String asGreen(String text) {
|
||||
return "\u001B[32m" + text + "\u001B[0m";
|
||||
}
|
||||
|
||||
public static String asYellow(String text) {
|
||||
return "\u001B[33m" + text + "\u001B[0m";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue