- Complete the basic implementation of AI optional cost choice, remove getOptionalCosts.

This commit is contained in:
Agetian
2018-12-16 09:11:54 +03:00
parent 7a53a0fb36
commit 14799ccd62
4 changed files with 32 additions and 156 deletions

View File

@@ -849,20 +849,22 @@ public class AiController {
int neededMana = 0; int neededMana = 0;
boolean dangerousRecurringCost = false; boolean dangerousRecurringCost = false;
for (SpellAbility sa2 : GameActionUtil.getOptionalCosts(sa)) {
if (sa2.isOptionalCostPaid(OptionalCost.Buyback)) { Cost costWithBuyback = sa.getPayCosts().copy();
Cost sac = sa2.getPayCosts(); for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
CostAdjustment.adjust(sac, sa2); if (opt.getType() == OptionalCost.Buyback) {
if (sac.getCostMana() != null) { costWithBuyback.add(opt.getCost());
neededMana = sac.getCostMana().getMana().getCMC();
} }
if (sac.hasSpecificCostType(CostPayLife.class) }
|| sac.hasSpecificCostType(CostDiscard.class) CostAdjustment.adjust(costWithBuyback, sa);
|| sac.hasSpecificCostType(CostSacrifice.class)) { if (costWithBuyback.getCostMana() != null) {
neededMana = costWithBuyback.getCostMana().getMana().getCMC();
}
if (costWithBuyback.hasSpecificCostType(CostPayLife.class)
|| costWithBuyback.hasSpecificCostType(CostDiscard.class)
|| costWithBuyback.hasSpecificCostType(CostSacrifice.class)) {
dangerousRecurringCost = true; dangerousRecurringCost = true;
} }
}
}
// won't be able to afford buyback any time soon // won't be able to afford buyback any time soon
// if Buyback cost includes sacrifice, life, discard // if Buyback cost includes sacrifice, life, discard

View File

@@ -105,17 +105,22 @@ public class ComputerUtilAbility {
final List<SpellAbility> result = Lists.newArrayList(); final List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : newAbilities) { for (SpellAbility sa : newAbilities) {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
// TODO: remove this once all optional costs are ported over to chooseOptionalCosts
result.addAll(GameActionUtil.getOptionalCosts(sa));
// Optional cost selection through the AI controller // Optional cost selection through the AI controller
boolean choseOptCost = false;
List<OptionalCostValue> list = GameActionUtil.getOptionalCostValues(sa); List<OptionalCostValue> list = GameActionUtil.getOptionalCostValues(sa);
if (!list.isEmpty()) { if (!list.isEmpty()) {
list = player.getController().chooseOptionalCosts(sa, list); list = player.getController().chooseOptionalCosts(sa, list);
if (!list.isEmpty()) { if (!list.isEmpty()) {
choseOptCost = true;
result.add(GameActionUtil.addOptionalCosts(sa, list)); result.add(GameActionUtil.addOptionalCosts(sa, list));
} }
} }
// Add only one ability: either the one with preferred optional costs, or the original one if there are none
if (!choseOptCost) {
result.add(sa);
}
} }
return result; return result;

View File

@@ -1147,19 +1147,20 @@ public class PlayerControllerAi extends PlayerController {
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen, public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
List<OptionalCostValue> optionalCostValues) { List<OptionalCostValue> optionalCostValues) {
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList(); List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
Cost cost = chosen.getPayCosts(); Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero;
for (OptionalCostValue opt : optionalCostValues) { for (OptionalCostValue opt : optionalCostValues) {
if (opt.getType() == OptionalCost.Entwine) { if (opt.getType() == OptionalCost.Entwine) {
// Test implementation: just always choose Entwine // Specific code for optional costs should be made conditional here
Cost fullCost = opt.getCost().copy().add(cost); }
// Generic code: for now, chooses the optional cost if it can be paid (and later - played)
Cost fullCost = opt.getCost().copy().add(costSoFar);
SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost); SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost);
if (ComputerUtilCost.canPayCost(fullCostSa, player)) { if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
chosenOptCosts.add(opt); chosenOptCosts.add(opt);
} costSoFar.add(opt.getCost());
} System.out.println("Chosen: " + opt + " for total cost of " + costSoFar.toSimpleString());
else {
System.out.println("Skipping unported optional cost: " + opt.getType());
} }
} }

View File

@@ -345,138 +345,6 @@ public final class GameActionUtil {
return abilities; return abilities;
} }
/**
* get optional additional costs.
*
* @param original
* the original sa
* @return an ArrayList<SpellAbility>.
*
* @deprecated only used by AI, replace it with new functions in AI
*/
@Deprecated public static List<SpellAbility> getOptionalCosts(final SpellAbility original) {
final List<SpellAbility> abilities = getAdditionalCostSpell(original);
final Card source = original.getHostCard();
if (!original.isSpell()) {
return abilities;
}
// Buyback, Kicker
for (KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Buyback")) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
newSA.setBasicSpell(false);
newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts()));
newSA.setDescription(newSA.getDescription() + " (with Buyback)");
newSA.addOptionalCost(OptionalCost.Buyback);
if (newSA.canPlay()) {
abilities.add(i, newSA);
i++;
}
}
} else if (keyword.startsWith("MayFlashCost")) {
// this is there for the AI
if (source.getGame().getPhaseHandler().isPlayerTurn(source.getController())) {
continue; // don't cast it with additional flash cost during AI's own turn, commonly a waste of mana
}
final String[] k = keyword.split(":");
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
newSA.setBasicSpell(false);
newSA.setPayCosts(new Cost(k[1], false).add(newSA.getPayCosts()));
newSA.setDescription(newSA.getDescription() + " (as though it had flash)");
newSA.getRestrictions().setInstantSpeed(true);
if (newSA.canPlay()) {
abilities.add(i, newSA);
i++;
}
}
} else if (keyword.startsWith("Kicker")) {
String[] sCosts = TextUtil.split(keyword.substring(6), ':');
boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
// If this is a "generic kicker" (Undergrowth), ignore value for kicker creations
int numKickers = sCosts.length - (generic ? 1 : 0);
for (int i = 0; i < abilities.size(); i++) {
int iUnKicked = i;
for (int j = 0; j < numKickers; j++) {
final SpellAbility newSA = abilities.get(iUnKicked).copy();
newSA.setBasicSpell(false);
final Cost cost = new Cost(sCosts[j], false);
newSA.setPayCosts(cost.add(newSA.getPayCosts()));
if (!generic) {
newSA.setDescription(newSA.getDescription() + " (Kicker " + cost.toSimpleString() + ")");
newSA.addOptionalCost(j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2);
} else {
newSA.setDescription(newSA.getDescription() + " (Optional " + cost.toSimpleString() + ")");
newSA.addOptionalCost(OptionalCost.Generic);
}
if (newSA.canPlay()) {
abilities.add(i, newSA);
i++;
iUnKicked++;
}
}
if (numKickers == 2) { // case for both kickers - it's hardcoded since they never have more than 2 kickers
final SpellAbility newSA = abilities.get(iUnKicked).copy();
newSA.setBasicSpell(false);
final Cost cost1 = new Cost(sCosts[0], false);
final Cost cost2 = new Cost(sCosts[1], false);
newSA.setDescription(TextUtil.addSuffix(newSA.getDescription(), TextUtil.concatWithSpace(" (Both kickers:", cost1.toSimpleString(), "and", TextUtil.addSuffix(cost2.toSimpleString(), ")"))));
newSA.setPayCosts(cost2.add(cost1.add(newSA.getPayCosts())));
newSA.addOptionalCost(OptionalCost.Kicker1);
newSA.addOptionalCost(OptionalCost.Kicker2);
if (newSA.canPlay()) {
abilities.add(i, newSA);
i++;
}
}
}
}
}
if (source.hasKeyword(Keyword.CONSPIRE)) {
int amount = source.getAmountOfKeyword(Keyword.CONSPIRE);
for (int kwInstance = 1; kwInstance <= amount; kwInstance++) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
newSA.setBasicSpell(false);
final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control that shares a color with " + source.getName() + ">";
newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts()));
final String tag = kwInstance > 1 ? " (Conspire " + kwInstance + ")" : " (Conspire)";
newSA.setDescription(newSA.getDescription() + tag);
newSA.addOptionalCost(OptionalCost.Conspire);
newSA.addConspireInstance();
if (newSA.canPlay()) {
abilities.add(++i, newSA);
}
}
}
}
if (source.hasKeyword(Keyword.JUMP_START)) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
newSA.setBasicSpell(false);
final String jumpstartCost = "Discard<1/Card>";
newSA.setPayCosts(new Cost(jumpstartCost, false).add(newSA.getPayCosts()));
newSA.getRestrictions().setZone(ZoneType.Graveyard);
newSA.setDescription(newSA.getDescription() + " (Jump-start)");
newSA.addOptionalCost(OptionalCost.Jumpstart);
if (newSA.canPlay()) {
abilities.add(i, newSA);
i++;
}
}
}
return abilities;
}
private static boolean hasUrzaLands(final Player p) { private static boolean hasUrzaLands(final Player p) {
final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))