This commit is contained in:
tool4EvEr
2021-12-24 09:52:22 +01:00
parent 4cf730ffa0
commit 0e0fdaa1ff
4 changed files with 209 additions and 200 deletions

View File

@@ -41,7 +41,6 @@ import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*; import java.util.*;
@@ -67,16 +66,6 @@ public class ComputerUtilMana {
return payManaCost(cost, sa, ai, test, checkPlayable, effect); return payManaCost(cost, sa, ai, test, checkPlayable, effect);
} }
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
if (sa.getHostCard() != null) {
sa.getHostCard().setCanCounter(true);
}
for (final Mana m : manaSpent) {
ai.getManaPool().addMana(m);
}
manaSpent.clear();
}
/** /**
* Return the number of colors used for payment for Converge * Return the number of colors used for payment for Converge
*/ */
@@ -598,7 +587,7 @@ public class ComputerUtilMana {
} }
// get a mana of this type from floating, bail if none available // get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor()); final Mana mana = CostPayment.getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
if (mana != null) { if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) { if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
manaSpentToPay.add(0, mana); manaSpentToPay.add(0, mana);
@@ -609,18 +598,16 @@ public class ComputerUtilMana {
if (cost.isPaid()) { if (cost.isPaid()) {
// refund any mana taken from mana pool when test // refund any mana taken from mana pool when test
refundMana(manaSpentToPay, ai, sa); ManaPool.refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, true, cost.isPaid());
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources; return manaSources;
} }
// arrange all mana abilities by color produced. // arrange all mana abilities by color produced.
final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, true); final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, true);
if (manaAbilityMap.isEmpty()) { if (manaAbilityMap.isEmpty()) {
refundMana(manaSpentToPay, ai, sa); ManaPool.refundMana(manaSpentToPay, ai, sa);
CostPayment.handleOfferings(sa, true, cost.isPaid());
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources; return manaSources;
} }
@@ -666,9 +653,8 @@ public class ComputerUtilMana {
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
} }
handleOfferingsAI(sa, true, cost.isPaid()); CostPayment.handleOfferings(sa, true, cost.isPaid());
ManaPool.refundMana(manaSpentToPay, ai, sa);
refundMana(manaSpentToPay, ai, sa);
return manaSources; return manaSources;
} // getManaSourcesToPayCost() } // getManaSourcesToPayCost()
@@ -681,7 +667,7 @@ public class ComputerUtilMana {
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana(); List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
List<SpellAbility> paymentList = Lists.newArrayList(); List<SpellAbility> paymentList = Lists.newArrayList();
if (payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) { if (ManaPool.payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) {
return true; // paid all from floating mana return true; // paid all from floating mana
} }
@@ -870,7 +856,7 @@ public class ComputerUtilMana {
} }
} }
handleOfferingsAI(sa, test, cost.isPaid()); CostPayment.handleOfferings(sa, test, cost.isPaid());
// if (DEBUG_MANA_PAYMENT) { // if (DEBUG_MANA_PAYMENT) {
// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n", // System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
@@ -880,7 +866,7 @@ public class ComputerUtilMana {
// The cost is still unpaid, so refund the mana and report // The cost is still unpaid, so refund the mana and report
if (!cost.isPaid()) { if (!cost.isPaid()) {
refundMana(manaSpentToPay, ai, sa); ManaPool.refundMana(manaSpentToPay, ai, sa);
if (test) { if (test) {
resetPayment(paymentList); resetPayment(paymentList);
return false; return false;
@@ -891,7 +877,7 @@ public class ComputerUtilMana {
} }
if (test) { if (test) {
refundMana(manaSpentToPay, ai, sa); ManaPool.refundMana(manaSpentToPay, ai, sa);
resetPayment(paymentList); resetPayment(paymentList);
} }
@@ -929,8 +915,8 @@ public class ComputerUtilMana {
final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, checkPlayable); final ListMultimap<Integer, SpellAbility> manaAbilityMap = groupSourcesByManaColor(ai, checkPlayable);
if (manaAbilityMap.isEmpty()) { if (manaAbilityMap.isEmpty()) {
// no mana abilities, bailing out // no mana abilities, bailing out
refundMana(manaSpentToPay, ai, sa); ManaPool.refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, test, cost.isPaid()); CostPayment.handleOfferings(sa, test, cost.isPaid());
return null; return null;
} }
if (DEBUG_MANA_PAYMENT) { if (DEBUG_MANA_PAYMENT) {
@@ -968,157 +954,6 @@ public class ComputerUtilMana {
return sourcesForShards; return sourcesForShards;
} }
/**
* Checks if the given mana cost can be paid from floating mana.
* @param cost mana cost to pay for
* @param sa ability to pay for
* @param ai activating player
* @param test actual payment is made if this is false
* @param manaSpentToPay list of mana spent
* @return whether the floating mana is sufficient to pay the cost fully
*/
public static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
final boolean test, List<Mana> manaSpentToPay) {
final boolean hasConverge = sa.getHostCard().hasConverge();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana);
}
}
}
}
// refund any mana taken from mana pool when test
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
if (cost.isPaid()) {
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
return false;
}
/**
* <p>
* getManaFrom.
* </p>
*
* @param saBeingPaidFor
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case
if (weightedOptions.isEmpty()) {
return null; // There is no matching mana in the pool
}
// select equal weight possibilities
List<Mana> manaChoices = new ArrayList<>();
int bestWeight = Integer.MIN_VALUE;
for (Pair<Mana, Integer> option : weightedOptions) {
int thisWeight = option.getRight();
Mana thisMana = option.getLeft();
if (thisWeight > bestWeight) {
manaChoices.clear();
bestWeight = thisWeight;
}
if (thisWeight == bestWeight) {
// add only distinct Mana-s
boolean haveDuplicate = false;
for (Mana m : manaChoices) {
if (m.equals(thisMana)) {
haveDuplicate = true;
break;
}
}
if (!haveDuplicate) {
manaChoices.add(thisMana);
}
}
}
// got an only one best option?
if (manaChoices.size() == 1) {
return manaChoices.get(0);
}
// if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player)
if (!(ai.getController() instanceof PlayerControllerAi)) {
return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
continue;
}
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
continue;
}
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().getType().hasStringType(restriction)) {
continue;
}
int weight = 0;
if (colorsPaid == -1) {
// prefer colorless mana to spend
weight += thisMana.isColorless() ? 5 : 0;
} else {
// get more colors for converge
weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
}
// prefer restricted mana to spend
if (thisMana.isRestricted()) {
weight += 2;
}
// Spend non-snow mana first
if (!thisMana.isSnow()) {
weight += 1;
}
weightedOptions.add(Pair.of(thisMana, weight));
}
return weightedOptions;
}
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost, private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) { ManaCostShard toPay, SpellAbility saPayment) {
@@ -1936,25 +1771,6 @@ public class ComputerUtilMana {
return res; return res;
} }
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, false, null, null);
}
sa.resetSacrificedAsOffering();
}
if (sa.isEmerge() && sa.getSacrificedAsEmerge() != null) {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, false, null, null);
}
sa.resetSacrificedAsEmerge();
}
}
/** /**
* Matches list of creatures to shards in mana cost for convoking. * Matches list of creatures to shards in mana cost for convoking.
* @param cost cost of convoked ability * @param cost cost of convoked ability

View File

@@ -17,15 +17,25 @@
*/ */
package forge.game.cost; package forge.game.cost;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -221,4 +231,132 @@ public class CostPayment extends ManaConversionMatrix {
} }
return true; return true;
} }
/**
* <p>
* getManaFrom.
* </p>
*
* @param saBeingPaidFor
* a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object.
*/
public static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case
if (weightedOptions.isEmpty()) {
return null; // There is no matching mana in the pool
}
// select equal weight possibilities
List<Mana> manaChoices = new ArrayList<>();
int bestWeight = Integer.MIN_VALUE;
for (Pair<Mana, Integer> option : weightedOptions) {
int thisWeight = option.getRight();
Mana thisMana = option.getLeft();
if (thisWeight > bestWeight) {
manaChoices.clear();
bestWeight = thisWeight;
}
if (thisWeight == bestWeight) {
// add only distinct Mana-s
boolean haveDuplicate = false;
for (Mana m : manaChoices) {
if (m.equals(thisMana)) {
haveDuplicate = true;
break;
}
}
if (!haveDuplicate) {
manaChoices.add(thisMana);
}
}
}
// got an only one best option?
if (manaChoices.size() == 1) {
return manaChoices.get(0);
}
// if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player)
if (!ai.getController().isAI()) {
return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue;
}
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
continue;
}
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
continue;
}
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().getType().hasStringType(restriction)) {
continue;
}
int weight = 0;
if (colorsPaid == -1) {
// prefer colorless mana to spend
weight += thisMana.isColorless() ? 5 : 0;
} else {
// get more colors for converge
weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
}
// prefer restricted mana to spend
if (thisMana.isRestricted()) {
weight += 2;
}
// Spend non-snow mana first
if (!thisMana.isSnow()) {
weight += 1;
}
weightedOptions.add(Pair.of(thisMana, weight));
}
return weightedOptions;
}
public static void handleOfferings(final SpellAbility sa, boolean test, boolean costIsPaid) {
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, false, null, null);
}
sa.resetSacrificedAsOffering();
}
if (sa.isEmerge() && sa.getSacrificedAsEmerge() != null) {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, false, null, null);
}
sa.resetSacrificedAsEmerge();
}
}
} }

View File

@@ -18,6 +18,7 @@
package forge.game.mana; package forge.game.mana;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -33,6 +34,7 @@ import forge.card.mana.ManaCostShard;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.cost.CostPayment;
import forge.game.event.EventValueChangeType; import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventManaPool; import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone; import forge.game.event.GameEventZone;
@@ -289,6 +291,16 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return true; return true;
} }
public static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
if (sa.getHostCard() != null) {
sa.getHostCard().setCanCounter(true);
}
for (final Mana m : manaSpent) {
ai.getManaPool().addMana(m);
}
manaSpent.clear();
}
public final void refundManaPaid(final SpellAbility sa) { public final void refundManaPaid(final SpellAbility sa) {
// Send all mana back to your mana pool, before accounting for it. // Send all mana back to your mana pool, before accounting for it.
final List<Mana> manaPaid = sa.getPayingMana(); final List<Mana> manaPaid = sa.getPayingMana();
@@ -342,6 +354,47 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return shard.canBePaidWithManaOfColor((byte)0); return shard.canBePaidWithManaOfColor((byte)0);
} }
/**
* Checks if the given mana cost can be paid from floating mana.
* @param cost mana cost to pay for
* @param sa ability to pay for
* @param ai activating player
* @param test actual payment is made if this is false
* @param manaSpentToPay list of mana spent
* @return whether the floating mana is sufficient to pay the cost fully
*/
public static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
final boolean test, List<Mana> manaSpentToPay) {
final boolean hasConverge = sa.getHostCard().hasConverge();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = CostPayment.getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana);
}
}
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
if (test) {
refundMana(manaSpentToPay, ai, sa);
}
CostPayment.handleOfferings(sa, test, cost.isPaid());
return true;
}
return false;
}
@Override @Override
public Iterator<Mana> iterator() { public Iterator<Mana> iterator() {
return floatingMana.values().iterator(); return floatingMana.values().iterator();

View File

@@ -1,14 +1,14 @@
package forge.gamemodes.match.input; package forge.gamemodes.match.input;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import forge.ai.ComputerUtilMana;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.game.cost.CostPayment;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences;
@@ -28,7 +28,9 @@ public class InputPayManaOfCostPayment extends InputPayMana {
// CR 118.3c forced cast must use pool mana // CR 118.3c forced cast must use pool mana
// TODO this introduces a small risk to lock up the GUI if the human "wastes" enough mana for abilities like Doubling Cube // TODO this introduces a small risk to lock up the GUI if the human "wastes" enough mana for abilities like Doubling Cube
if (spellAbility.getPayCosts().isMandatory()) { if (spellAbility.getPayCosts().isMandatory()) {
mandatory = ComputerUtilMana.payManaCostFromPool(new ManaCostBeingPaid(cost), spellAbility, payer, true, new ArrayList<Mana>()); List<Mana> refund = new ArrayList<>();
mandatory = ManaPool.payManaCostFromPool(new ManaCostBeingPaid(cost), spellAbility, payer, true, refund);
ManaPool.refundMana(refund, payer, spellAbility);
} }
// Set Mana cost being paid for SA to be able to reference it later // Set Mana cost being paid for SA to be able to reference it later