AI and test framework improved:

* Now AI can see and use special mana payments like convoke, delve, improvise pays;
* Now devs can test special mana payments (disable auto-payment and use choices for mana pool and special pays);
* Fixed broken TargetDiscard in tests;
* Fixed broken same named targets in tests;
This commit is contained in:
Oleg Agafonov 2020-06-19 13:16:26 +04:00
parent c2e7b02e13
commit 10cf884923
2 changed files with 88 additions and 19 deletions

View file

@ -1533,10 +1533,34 @@ public class ComputerPlayer extends PlayerImpl implements Player {
} }
} }
} }
// pay phyrexian life costs // pay phyrexian life costs
if (cost instanceof PhyrexianManaCost) { if (cost instanceof PhyrexianManaCost) {
return cost.pay(null, game, null, playerId, false, null) || permittingObject != null; return cost.pay(null, game, null, playerId, false, null) || permittingObject != null;
} }
// pay special mana like convoke cost (tap for pay)
// GUI: user see "special" button while pay spell's cost
// TODO: AI can't prioritize special mana types to pay, e.g. it will use first available
SpecialAction specialAction = game.getState().getSpecialActions().getControlledBy(this.getId(), true)
.values().stream().findFirst().orElse(null);
ManaOptions specialMana = specialAction == null ? null : specialAction.getManaOptions(ability, game, unpaid);
if (specialMana != null) {
for (Mana netMana : specialMana) {
if (cost.testPay(netMana) || permittingObject != null) {
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
continue;
}
specialAction.setUnpaidMana(unpaid);
if (activateAbility(specialAction, game)) {
return true;
}
// only one time try to pay
break;
}
}
}
return false; return false;
} }

View file

@ -1795,7 +1795,7 @@ public class TestPlayer implements Player {
assertAliasSupportInChoices(true); assertAliasSupportInChoices(true);
if (!choices.isEmpty()) { if (!choices.isEmpty()) {
List<String> usedChoices = new ArrayList<>(); List<Integer> usedChoices = new ArrayList<>();
List<UUID> usedTargets = new ArrayList<>(); List<UUID> usedTargets = new ArrayList<>();
Ability source = null; Ability source = null;
@ -1883,7 +1883,8 @@ public class TestPlayer implements Player {
boolean targetCompleted = false; boolean targetCompleted = false;
CheckAllChoices: CheckAllChoices:
for (String choiceRecord : choices) { for (int choiceIndex = 0; choiceIndex < choices.size(); choiceIndex++) {
String choiceRecord = choices.get(choiceIndex);
if (targetCompleted) { if (targetCompleted) {
break CheckAllChoices; break CheckAllChoices;
} }
@ -1923,14 +1924,19 @@ public class TestPlayer implements Player {
} }
if (targetFound) { if (targetFound) {
usedChoices.add(choiceRecord); usedChoices.add(choiceIndex);
} }
} }
// apply only on ALL targets or revert // apply only on ALL targets or revert
if (usedChoices.size() > 0) { if (usedChoices.size() > 0) {
if (target.isChosen()) { if (target.isChosen()) {
choices.removeAll(usedChoices); // remove all used choices
for (int i = choices.size(); i >= 0; i--) {
if (usedChoices.contains(i)) {
choices.remove(i);
}
}
return true; return true;
} else { } else {
Assert.fail("Not full targets list."); Assert.fail("Not full targets list.");
@ -2091,7 +2097,7 @@ public class TestPlayer implements Player {
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) {
target.addTarget(permanent.getId(), source, game); target.addTarget(permanent.getId(), source, game);
targetFound = true; targetFound = true;
break; // return to for (String targetName break; // return to next targetName
} }
} }
} }
@ -2105,18 +2111,19 @@ public class TestPlayer implements Player {
} }
// card in hand // card in hand
if (target.getOriginalTarget() instanceof TargetCardInHand) { if (target.getOriginalTarget() instanceof TargetCardInHand
|| target.getOriginalTarget() instanceof TargetDiscard) {
for (String targetDefinition : targets) { for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^"); String[] targetList = targetDefinition.split("\\^");
boolean targetFound = false; boolean targetFound = false;
for (String targetName : targetList) { for (String targetName : targetList) {
for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target.getOriginalTarget()).getFilter(), game)) { for (Card card : computerPlayer.getHand().getCards(((TargetCard) target.getOriginalTarget()).getFilter(), game)) {
if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search?
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
targetFound = true; targetFound = true;
break; // return to for (String targetName break; // return to next targetName
} }
} }
} }
@ -2141,7 +2148,7 @@ public class TestPlayer implements Player {
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
targetFound = true; targetFound = true;
break; // return to for (String targetName break; // return to next targetName
} }
} }
} }
@ -2166,7 +2173,7 @@ public class TestPlayer implements Player {
if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) {
targetFull.add(card.getId(), game); targetFull.add(card.getId(), game);
targetFound = true; targetFound = true;
break; // return to for (String targetName break; // return to next targetName
} }
} }
} }
@ -2216,7 +2223,7 @@ public class TestPlayer implements Player {
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
targetFound = true; targetFound = true;
break IterateGraveyards; // return to for (String targetName break IterateGraveyards; // return to next targetName
} }
} }
} }
@ -2243,7 +2250,7 @@ public class TestPlayer implements Player {
if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) { if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) {
target.addTarget(stackObject.getId(), source, game); target.addTarget(stackObject.getId(), source, game);
targetFound = true; targetFound = true;
break; // return to for (String targetName break; // return to next targetName
} }
} }
} }
@ -3620,38 +3627,76 @@ public class TestPlayer implements Player {
groupsForTargetHandling = null; groupsForTargetHandling = null;
if (!computerPlayer.getManaPool().isAutoPayment()) { if (!computerPlayer.getManaPool().isAutoPayment()) {
// manual pay by mana clicks/commands
if (!choices.isEmpty()) { if (!choices.isEmpty()) {
String needColor = choices.get(0); // manual pay by mana clicks/commands
switch (needColor) { String choice = choices.get(0);
boolean choiceUsed = false;
boolean choiceRemoved = false;
switch (choice) {
case "White": case "White":
Assert.assertTrue("pool must have white mana", computerPlayer.getManaPool().getWhite() > 0); Assert.assertTrue("pool must have white mana", computerPlayer.getManaPool().getWhite() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.WHITE); computerPlayer.getManaPool().unlockManaType(ManaType.WHITE);
choiceUsed = true;
break; break;
case "Blue": case "Blue":
Assert.assertTrue("pool must have blue mana", computerPlayer.getManaPool().getBlue() > 0); Assert.assertTrue("pool must have blue mana", computerPlayer.getManaPool().getBlue() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.BLUE); computerPlayer.getManaPool().unlockManaType(ManaType.BLUE);
choiceUsed = true;
break; break;
case "Black": case "Black":
Assert.assertTrue("pool must have black mana", computerPlayer.getManaPool().getBlack() > 0); Assert.assertTrue("pool must have black mana", computerPlayer.getManaPool().getBlack() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.BLACK); computerPlayer.getManaPool().unlockManaType(ManaType.BLACK);
choiceUsed = true;
break; break;
case "Red": case "Red":
Assert.assertTrue("pool must have red mana", computerPlayer.getManaPool().getRed() > 0); Assert.assertTrue("pool must have red mana", computerPlayer.getManaPool().getRed() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.RED); computerPlayer.getManaPool().unlockManaType(ManaType.RED);
choiceUsed = true;
break; break;
case "Green": case "Green":
Assert.assertTrue("pool must have green mana", computerPlayer.getManaPool().getGreen() > 0); Assert.assertTrue("pool must have green mana", computerPlayer.getManaPool().getGreen() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.GREEN); computerPlayer.getManaPool().unlockManaType(ManaType.GREEN);
choiceUsed = true;
break;
case "Colorless":
Assert.assertTrue("pool must have colorless mana", computerPlayer.getManaPool().getColorless() > 0);
computerPlayer.getManaPool().unlockManaType(ManaType.COLORLESS);
choiceUsed = true;
break; break;
default: default:
Assert.fail("Unknown choice command for mana unlock: " + needColor); // go to special block
//Assert.fail("Unknown choice command for mana unlock: " + needColor);
break; break;
} }
// manual pay by special actions like convoke
if (!choiceUsed) {
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(this.getId(), true);
for (SpecialAction specialAction : specialActions.values()) {
if (specialAction.getRule(true).startsWith(choice)) {
if (specialAction.canActivate(this.getId(), game).canActivate()) {
choices.remove(0); choices.remove(0);
return true; choiceRemoved = true;
specialAction.setUnpaidMana(unpaid);
if (activateAbility(specialAction, game)) {
choiceUsed = true;
} }
Assert.fail(this.getName() + " disabled mana auto-payment, but no choices found for color unlock in pool for unpaid cost: " + unpaid.getText()); }
}
}
}
if (choiceUsed) {
if (!choiceRemoved) {
choices.remove(0);
}
return true;
} else {
Assert.fail("Can't use choice in play mana: " + choice);
}
}
Assert.fail(this.getName() + " disabled mana auto-payment, but no choices found for color unlock in pool or special action for unpaid cost: " + unpaid.getText());
} }
return computerPlayer.playMana(ability, unpaid, promptText, game); return computerPlayer.playMana(ability, unpaid, promptText, game);