mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
Playable mana calculation improved:
* server: fixed server crashes on usage of multiple permanents with {Any} mana abilities (example: Energy Refractor, related to #11285);
* AI: fixed game freezes and errors on computer's {Any} mana usage (closes #9467, closes #6419);
This commit is contained in:
parent
19f7ba8937
commit
2298ebc5f5
11 changed files with 504 additions and 221 deletions
|
|
@ -90,17 +90,17 @@ public final class RateCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
String name = card.getName();
|
String name = card.getName();
|
||||||
if (useCache && allowedColors == null && ratedCard.containsKey(name)) {
|
if (useCache && allowedColors.isEmpty() && ratedCard.containsKey(name)) {
|
||||||
int rate = ratedCard.get(name);
|
return ratedCard.get(name);
|
||||||
return rate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int typeMultiplier = typeMultiplier(card);
|
int typeMultiplier = typeMultiplier(card);
|
||||||
int score = getBaseCardScore(card) + 2 * typeMultiplier + getManaCostScore(card, allowedColors)
|
int score = getBaseCardScore(card) + 2 * typeMultiplier + getManaCostScore(card, allowedColors)
|
||||||
+ 40 * isRemoval(card);
|
+ 40 * isRemoval(card);
|
||||||
|
|
||||||
if (useCache && allowedColors == null)
|
if (useCache && allowedColors.isEmpty()) {
|
||||||
ratedCard.put(name, score);
|
ratedCard.put(name, score);
|
||||||
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
@ -111,17 +111,17 @@ public final class RateCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
String name = cardview.getName();
|
String name = cardview.getName();
|
||||||
if (useCache && allowedColors == null && ratedCardView.containsKey(name)) {
|
if (useCache && allowedColors.isEmpty() && ratedCardView.containsKey(name)) {
|
||||||
int rate = ratedCardView.get(name);
|
return ratedCardView.get(name);
|
||||||
return rate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int typeMultiplier = typeMultiplier(cardview);
|
int typeMultiplier = typeMultiplier(cardview);
|
||||||
int score = getBaseCardScore(cardview) + 2 * typeMultiplier + getManaCostScore(cardview, allowedColors);
|
int score = getBaseCardScore(cardview) + 2 * typeMultiplier + getManaCostScore(cardview, allowedColors);
|
||||||
// Cardview does not have enough info to know the card is a removal.
|
// Cardview does not have enough info to know the card is a removal.
|
||||||
|
|
||||||
if (useCache && allowedColors == null)
|
if (useCache && allowedColors.isEmpty()) {
|
||||||
ratedCardView.put(name, score);
|
ratedCardView.put(name, score);
|
||||||
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +402,7 @@ public final class RateCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getManaCostScore(String name, int manaValue, List<String> manaCostSymbols, List<ColoredManaSymbol> allowedColors) {
|
private static int getManaCostScore(String name, int manaValue, List<String> manaCostSymbols, List<ColoredManaSymbol> allowedColors) {
|
||||||
if (allowedColors == null) {
|
if (allowedColors.isEmpty()) {
|
||||||
int colorPenalty = 0;
|
int colorPenalty = 0;
|
||||||
for (String symbol : manaCostSymbols) {
|
for (String symbol : manaCostSymbols) {
|
||||||
if (isColoredMana(symbol)) {
|
if (isColoredMana(symbol)) {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ import mage.target.common.*;
|
||||||
import mage.util.*;
|
import mage.util.*;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
@ -75,14 +74,17 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
|
|
||||||
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1; // TODO: rework simulations logic to use multiple calcs instead one by one
|
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1; // TODO: rework simulations logic to use multiple calcs instead one by one
|
||||||
|
|
||||||
private transient Map<Mana, Card> unplayable = new TreeMap<>();
|
private final transient Map<Mana, Card> unplayable = new TreeMap<>();
|
||||||
private transient List<Card> playableNonInstant = new ArrayList<>();
|
private final transient List<Card> playableNonInstant = new ArrayList<>();
|
||||||
private transient List<Card> playableInstant = new ArrayList<>();
|
private final transient List<Card> playableInstant = new ArrayList<>();
|
||||||
private transient List<ActivatedAbility> playableAbilities = new ArrayList<>();
|
private final transient List<ActivatedAbility> playableAbilities = new ArrayList<>();
|
||||||
private transient List<PickedCard> pickedCards;
|
private final transient List<PickedCard> pickedCards = new ArrayList<>();
|
||||||
private transient List<ColoredManaSymbol> chosenColors;
|
private final transient List<ColoredManaSymbol> chosenColors = new ArrayList<>();
|
||||||
|
|
||||||
private transient ManaCost currentUnpaidMana;
|
// keep current paying cost info for choose dialogs
|
||||||
|
// mana abilities must ask payment too, so keep full chain
|
||||||
|
// TODO: make sure it thread safe for AI simulations (all transient fields above and bottom)
|
||||||
|
private final transient Map<UUID, ManaCost> lastUnpaidMana = new LinkedHashMap<>();
|
||||||
|
|
||||||
// For stopping infinite loops when trying to pay Phyrexian mana when the player can't spend life and no other sources are available
|
// For stopping infinite loops when trying to pay Phyrexian mana when the player can't spend life and no other sources are available
|
||||||
private transient boolean alreadyTryingToPayPhyrexian;
|
private transient boolean alreadyTryingToPayPhyrexian;
|
||||||
|
|
@ -94,7 +96,6 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
userData.setAvatarId(64);
|
userData.setAvatarId(64);
|
||||||
userData.setGroupId(UserGroup.COMPUTER.getGroupId());
|
userData.setGroupId(UserGroup.COMPUTER.getGroupId());
|
||||||
userData.setFlagName("computer.png");
|
userData.setFlagName("computer.png");
|
||||||
pickedCards = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComputerPlayer(UUID id) {
|
protected ComputerPlayer(UUID id) {
|
||||||
|
|
@ -104,7 +105,6 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
userData.setAvatarId(64);
|
userData.setAvatarId(64);
|
||||||
userData.setGroupId(UserGroup.COMPUTER.getGroupId());
|
userData.setGroupId(UserGroup.COMPUTER.getGroupId());
|
||||||
userData.setFlagName("computer.png");
|
userData.setFlagName("computer.png");
|
||||||
pickedCards = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerPlayer(final ComputerPlayer player) {
|
public ComputerPlayer(final ComputerPlayer player) {
|
||||||
|
|
@ -653,7 +653,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
while (!target.isChosen(game)
|
while (!target.isChosen(game)
|
||||||
&& !cardsInHand.isEmpty()
|
&& !cardsInHand.isEmpty()
|
||||||
&& target.getMaxNumberOfTargets() > target.getTargets().size()) {
|
&& target.getMaxNumberOfTargets() > target.getTargets().size()) {
|
||||||
Card card = pickBestCard(cardsInHand, null, target, source, game);
|
Card card = pickBestCard(cardsInHand, Collections.emptyList(), target, source, game);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
|
if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
|
||||||
target.addTarget(card.getId(), source, game);
|
target.addTarget(card.getId(), source, game);
|
||||||
|
|
@ -1139,9 +1139,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
while (!cards.isEmpty()) {
|
while (!cards.isEmpty()) {
|
||||||
|
|
||||||
if (outcome.isGood()) {
|
if (outcome.isGood()) {
|
||||||
card = pickBestCard(cards, null, target, source, game);
|
card = pickBestCard(cards, Collections.emptyList(), target, source, game);
|
||||||
} else {
|
} else {
|
||||||
card = pickWorstCard(cards, null, target, source, game);
|
card = pickWorstCard(cards, Collections.emptyList(), target, source, game);
|
||||||
}
|
}
|
||||||
if (!target.getTargets().contains(card.getId())) {
|
if (!target.getTargets().contains(card.getId())) {
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
|
|
@ -1152,7 +1152,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cards.remove(card);
|
cards.remove(card); // TODO: research parent code - is it depends on original list? Can be bugged
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -1550,11 +1550,12 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) {
|
public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) {
|
||||||
payManaMode = true;
|
payManaMode = true;
|
||||||
currentUnpaidMana = unpaid;
|
lastUnpaidMana.put(ability.getId(), unpaid.copy());
|
||||||
try {
|
try {
|
||||||
return playManaHandling(ability, unpaid, game);
|
return playManaHandling(ability, unpaid, game);
|
||||||
} finally {
|
} finally {
|
||||||
currentUnpaidMana = null;
|
|
||||||
|
lastUnpaidMana.remove(ability.getId());
|
||||||
payManaMode = false;
|
payManaMode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1575,19 +1576,27 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
producers = this.getAvailableManaProducers(game);
|
producers = this.getAvailableManaProducers(game);
|
||||||
producers.addAll(this.getAvailableManaProducersWithCost(game));
|
producers.addAll(this.getAvailableManaProducersWithCost(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use fully compatible colored mana producers first
|
||||||
for (MageObject mageObject : producers) {
|
for (MageObject mageObject : producers) {
|
||||||
// use color producing mana abilities with costs first that produce all color manas that are needed to pay
|
|
||||||
// otherwise the computer may not be able to pay the cost for that source
|
|
||||||
ManaAbility:
|
ManaAbility:
|
||||||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||||
int colored = 0;
|
boolean canPayColoredMana = false;
|
||||||
for (Mana mana : manaAbility.getNetMana(game)) {
|
for (Mana mana : manaAbility.getNetMana(game)) {
|
||||||
|
// if mana ability can produce non-useful mana then ignore whole ability here (example: {R} or {G})
|
||||||
|
// (AI can't choose a good mana option, so make sure any selection option will be compatible with cost)
|
||||||
|
// AI support {Any} choice by lastUnpaidMana, so it can safly used in includesMana
|
||||||
if (!unpaid.getMana().includesMana(mana)) {
|
if (!unpaid.getMana().includesMana(mana)) {
|
||||||
continue ManaAbility;
|
continue ManaAbility;
|
||||||
|
} else if (mana.getAny() > 0) {
|
||||||
|
throw new IllegalArgumentException("Wrong mana calculation: AI do not support color choosing from {Any}");
|
||||||
|
}
|
||||||
|
if (mana.countColored() > 0) {
|
||||||
|
canPayColoredMana = true;
|
||||||
}
|
}
|
||||||
colored = CardUtil.overflowInc(colored, mana.countColored());
|
|
||||||
}
|
}
|
||||||
if (colored > 1 && (cost instanceof ColoredManaCost)) {
|
// found compatible source - try to pay
|
||||||
|
if (canPayColoredMana && (cost instanceof ColoredManaCost)) {
|
||||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||||
if (cost.testPay(netMana)) {
|
if (cost.testPay(netMana)) {
|
||||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||||
|
|
@ -1605,6 +1614,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use any other mana produces
|
||||||
for (MageObject mageObject : producers) {
|
for (MageObject mageObject : producers) {
|
||||||
// pay all colored costs first
|
// pay all colored costs first
|
||||||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||||
|
|
@ -1723,6 +1733,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
// pay phyrexian life costs
|
// pay phyrexian life costs
|
||||||
if (cost.isPhyrexian()) {
|
if (cost.isPhyrexian()) {
|
||||||
alreadyTryingToPayPhyrexian = true;
|
alreadyTryingToPayPhyrexian = true;
|
||||||
|
// TODO: make sure it's thread safe and protected from modifications (cost/unpaid can be shared between AI simulation threads?)
|
||||||
boolean paidPhyrexian = cost.pay(ability, game, ability, playerId, false, null) || hasApprovingObject;
|
boolean paidPhyrexian = cost.pay(ability, game, ability, playerId, false, null) || hasApprovingObject;
|
||||||
alreadyTryingToPayPhyrexian = false;
|
alreadyTryingToPayPhyrexian = false;
|
||||||
return paidPhyrexian;
|
return paidPhyrexian;
|
||||||
|
|
@ -1956,29 +1967,33 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
chooseCreatureType(outcome, choice, game);
|
chooseCreatureType(outcome, choice, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose the correct color to pay a spell
|
// choose the correct color to pay a spell (use last unpaid ability for color hint)
|
||||||
if (outcome == Outcome.PutManaInPool && choice.isManaColorChoice() && currentUnpaidMana != null) {
|
ManaCost unpaid = null;
|
||||||
if (currentUnpaidMana.containsColor(ColoredManaSymbol.W) && choice.getChoices().contains("White")) {
|
if (!lastUnpaidMana.isEmpty()) {
|
||||||
|
unpaid = new ArrayList<>(lastUnpaidMana.values()).get(lastUnpaidMana.size() - 1);
|
||||||
|
}
|
||||||
|
if (outcome == Outcome.PutManaInPool && unpaid != null && choice.isManaColorChoice()) {
|
||||||
|
if (unpaid.containsColor(ColoredManaSymbol.W) && choice.getChoices().contains("White")) {
|
||||||
choice.setChoice("White");
|
choice.setChoice("White");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (currentUnpaidMana.containsColor(ColoredManaSymbol.R) && choice.getChoices().contains("Red")) {
|
if (unpaid.containsColor(ColoredManaSymbol.R) && choice.getChoices().contains("Red")) {
|
||||||
choice.setChoice("Red");
|
choice.setChoice("Red");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (currentUnpaidMana.containsColor(ColoredManaSymbol.G) && choice.getChoices().contains("Green")) {
|
if (unpaid.containsColor(ColoredManaSymbol.G) && choice.getChoices().contains("Green")) {
|
||||||
choice.setChoice("Green");
|
choice.setChoice("Green");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (currentUnpaidMana.containsColor(ColoredManaSymbol.U) && choice.getChoices().contains("Blue")) {
|
if (unpaid.containsColor(ColoredManaSymbol.U) && choice.getChoices().contains("Blue")) {
|
||||||
choice.setChoice("Blue");
|
choice.setChoice("Blue");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (currentUnpaidMana.containsColor(ColoredManaSymbol.B) && choice.getChoices().contains("Black")) {
|
if (unpaid.containsColor(ColoredManaSymbol.B) && choice.getChoices().contains("Black")) {
|
||||||
choice.setChoice("Black");
|
choice.setChoice("Black");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (currentUnpaidMana.getMana().getColorless() > 0 && choice.getChoices().contains("Colorless")) {
|
if (unpaid.getMana().getColorless() > 0 && choice.getChoices().contains("Colorless")) {
|
||||||
choice.setChoice("Colorless");
|
choice.setChoice("Colorless");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -2411,11 +2426,14 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
int deckMinSize = deckValidator != null ? deckValidator.getDeckMinSize() : 0;
|
int deckMinSize = deckValidator != null ? deckValidator.getDeckMinSize() : 0;
|
||||||
|
|
||||||
if (deck != null && deck.getMaindeckCards().size() < deckMinSize && !deck.getSideboard().isEmpty()) {
|
if (deck != null && deck.getMaindeckCards().size() < deckMinSize && !deck.getSideboard().isEmpty()) {
|
||||||
if (chosenColors == null) {
|
if (chosenColors.isEmpty()) {
|
||||||
for (Card card : deck.getSideboard()) {
|
for (Card card : deck.getSideboard()) {
|
||||||
rememberPick(card, RateCard.rateCard(card, null));
|
rememberPick(card, RateCard.rateCard(card, Collections.emptyList()));
|
||||||
|
}
|
||||||
|
List<ColoredManaSymbol> deckColors = chooseDeckColorsIfPossible();
|
||||||
|
if (deckColors != null) {
|
||||||
|
chosenColors.addAll(deckColors);
|
||||||
}
|
}
|
||||||
chosenColors = chooseDeckColorsIfPossible();
|
|
||||||
}
|
}
|
||||||
deck = buildDeck(deckMinSize, new ArrayList<>(deck.getSideboard()), chosenColors);
|
deck = buildDeck(deckMinSize, new ArrayList<>(deck.getSideboard()), chosenColors);
|
||||||
}
|
}
|
||||||
|
|
@ -2525,7 +2543,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
if (pickedCardRate <= 30) {
|
if (pickedCardRate <= 30) {
|
||||||
// if card is bad
|
// if card is bad
|
||||||
// try to counter pick without any color restriction
|
// try to counter pick without any color restriction
|
||||||
Card counterPick = pickBestCard(cards, null);
|
Card counterPick = pickBestCard(cards, Collections.emptyList());
|
||||||
int counterPickScore = RateCard.getBaseCardScore(counterPick);
|
int counterPickScore = RateCard.getBaseCardScore(counterPick);
|
||||||
// card is really good
|
// card is really good
|
||||||
// take it!
|
// take it!
|
||||||
|
|
@ -2537,11 +2555,14 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
|
|
||||||
String colors = "not chosen yet";
|
String colors = "not chosen yet";
|
||||||
// remember card if colors are not chosen yet
|
// remember card if colors are not chosen yet
|
||||||
if (chosenColors == null) {
|
if (chosenColors.isEmpty()) {
|
||||||
rememberPick(bestCard, maxScore);
|
rememberPick(bestCard, maxScore);
|
||||||
chosenColors = chooseDeckColorsIfPossible();
|
List<ColoredManaSymbol> chosen = chooseDeckColorsIfPossible();
|
||||||
|
if (chosen != null) {
|
||||||
|
chosenColors.addAll(chosen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (chosenColors != null) {
|
if (!chosenColors.isEmpty()) {
|
||||||
colors = "";
|
colors = "";
|
||||||
for (ColoredManaSymbol symbol : chosenColors) {
|
for (ColoredManaSymbol symbol : chosenColors) {
|
||||||
colors += symbol.toString();
|
colors += symbol.toString();
|
||||||
|
|
@ -2612,7 +2633,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
if (colorsChosen.size() > 1) {
|
if (colorsChosen.size() > 1) {
|
||||||
// no need to remember picks anymore
|
// no need to remember picks anymore
|
||||||
pickedCards = null;
|
pickedCards.clear();
|
||||||
return colorsChosen;
|
return colorsChosen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2915,14 +2936,6 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
||||||
in.defaultReadObject();
|
|
||||||
unplayable = new TreeMap<>();
|
|
||||||
playableNonInstant = new ArrayList<>();
|
|
||||||
playableInstant = new ArrayList<>();
|
|
||||||
playableAbilities = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanUpOnMatchEnd() {
|
public void cleanUpOnMatchEnd() {
|
||||||
super.cleanUpOnMatchEnd();
|
super.cleanUpOnMatchEnd();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
package mage.cards.g;
|
package mage.cards.g;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
|
|
@ -12,20 +9,21 @@ import mage.constants.Duration;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
import mage.target.common.TargetCreaturePermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public final class GiantGrowth extends CardImpl {
|
public final class GiantGrowth extends CardImpl {
|
||||||
|
|
||||||
public GiantGrowth(UUID ownerId, CardSetInfo setInfo) {
|
public GiantGrowth(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}");
|
||||||
|
|
||||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
// Target creature gets +3/+3 until end of turn.
|
||||||
Effect effect = new BoostTargetEffect(3, 3, Duration.EndOfTurn);
|
Effect effect = new BoostTargetEffect(3, 3, Duration.EndOfTurn);
|
||||||
effect.setOutcome(Outcome.Benefit);
|
effect.setOutcome(Outcome.Benefit);
|
||||||
this.getSpellAbility().addEffect(effect);
|
this.getSpellAbility().addEffect(effect);
|
||||||
|
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GiantGrowth(final GiantGrowth card) {
|
private GiantGrowth(final GiantGrowth card) {
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,12 @@ public class SummonersEggTest extends CardTestPlayerBase {
|
||||||
/**
|
/**
|
||||||
* Summoner's Egg
|
* Summoner's Egg
|
||||||
* Artifact Creature — Construct 0/4, 4 (4)
|
* Artifact Creature — Construct 0/4, 4 (4)
|
||||||
* Imprint — When Summoner's Egg enters the battlefield, you may exile a
|
* Imprint — When Summoner's Egg enters the battlefield, you may exile a
|
||||||
* card from your hand face down.
|
* card from your hand face down.
|
||||||
* When Summoner's Egg dies, turn the exiled card face up. If it's a creature
|
* When Summoner's Egg dies, turn the exiled card face up. If it's a creature
|
||||||
* card, put it onto the battlefield under your control.
|
* card, put it onto the battlefield under your control.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// test that cards imprinted using Summoner's Egg are face down
|
// test that cards imprinted using Summoner's Egg are face down
|
||||||
@Test
|
@Test
|
||||||
public void testSummonersEggImprint() {
|
public void testSummonersEggImprint() {
|
||||||
|
|
@ -32,6 +31,10 @@ public class SummonersEggTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
||||||
|
setChoice(playerA, true); // use imprint
|
||||||
|
setChoice(playerA, "Goblin Roughrider");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
|
|
@ -40,7 +43,7 @@ public class SummonersEggTest extends CardTestPlayerBase {
|
||||||
assertHandCount(playerA, "Goblin Roughrider", 0);
|
assertHandCount(playerA, "Goblin Roughrider", 0);
|
||||||
|
|
||||||
assertExileCount("Goblin Roughrider", 1);
|
assertExileCount("Goblin Roughrider", 1);
|
||||||
for (Card card :currentGame.getExile().getAllCards(currentGame)){
|
for (Card card : currentGame.getExile().getAllCards(currentGame)) {
|
||||||
if (card.getName().equals("Goblin Roughrider")) {
|
if (card.getName().equals("Goblin Roughrider")) {
|
||||||
Assert.assertTrue("Exiled card is not face down", card.isFaceDown(currentGame));
|
Assert.assertTrue("Exiled card is not face down", card.isFaceDown(currentGame));
|
||||||
}
|
}
|
||||||
|
|
@ -57,23 +60,26 @@ public class SummonersEggTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||||
addCard(Zone.HAND, playerB, "Char");
|
addCard(Zone.HAND, playerB, "Char");
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
||||||
|
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
||||||
|
setChoice(playerA, true); // use imprint
|
||||||
|
setChoice(playerA, "Goblin Roughrider");
|
||||||
|
|
||||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Char", "Summoner's Egg");
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Char", "Summoner's Egg");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertHandCount(playerA, 1);
|
assertHandCount(playerA, 1);
|
||||||
assertHandCount(playerA, "Maritime Guard", 1);
|
assertHandCount(playerA, "Maritime Guard", 1);
|
||||||
assertHandCount(playerA, "Goblin Roughrider", 0);
|
assertHandCount(playerA, "Goblin Roughrider", 0);
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Summoner's Egg", 1);
|
assertGraveyardCount(playerA, "Summoner's Egg", 1);
|
||||||
|
|
||||||
assertExileCount("Goblin Roughrider", 0);
|
assertExileCount("Goblin Roughrider", 0);
|
||||||
assertPermanentCount(playerA, "Goblin Roughrider", 1);
|
assertPermanentCount(playerA, "Goblin Roughrider", 1);
|
||||||
for (Permanent p :currentGame.getBattlefield().getAllActivePermanents()){
|
for (Permanent p : currentGame.getBattlefield().getAllActivePermanents()) {
|
||||||
if (p.getName().equals("Goblin Roughrider")) {
|
if (p.getName().equals("Goblin Roughrider")) {
|
||||||
Assert.assertTrue("Permanent is not face up", !p.isFaceDown(currentGame));
|
Assert.assertTrue("Permanent is not face up", !p.isFaceDown(currentGame));
|
||||||
}
|
}
|
||||||
|
|
@ -89,26 +95,29 @@ public class SummonersEggTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
|
||||||
addCard(Zone.HAND, playerB, "Char");
|
addCard(Zone.HAND, playerB, "Char");
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
||||||
|
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summoner's Egg");
|
||||||
|
setChoice(playerA, true); // use imprint
|
||||||
|
setChoice(playerA, "Forest");
|
||||||
|
|
||||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Char", "Summoner's Egg");
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Char", "Summoner's Egg");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertHandCount(playerA, 1);
|
assertHandCount(playerA, 1);
|
||||||
assertHandCount(playerA, "Forest", 1);
|
assertHandCount(playerA, "Forest", 1);
|
||||||
|
|
||||||
assertGraveyardCount(playerA, "Summoner's Egg", 1);
|
assertGraveyardCount(playerA, "Summoner's Egg", 1);
|
||||||
|
|
||||||
assertExileCount("Forest", 1);
|
assertExileCount("Forest", 1);
|
||||||
for (Card card :currentGame.getExile().getAllCards(currentGame)){
|
for (Card card : currentGame.getExile().getAllCards(currentGame)) {
|
||||||
if (card.getName().equals("Forest")) {
|
if (card.getName().equals("Forest")) {
|
||||||
Assert.assertTrue("Exiled card is not face up", !card.isFaceDown(currentGame));
|
Assert.assertTrue("Exiled card is not face up", !card.isFaceDown(currentGame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package org.mage.test.cards.mana;
|
|
||||||
|
|
||||||
import mage.constants.PhaseStep;
|
|
||||||
import mage.constants.Zone;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author JayDi85
|
|
||||||
*/
|
|
||||||
public class EnergyRefractorManaCalculationsTest extends CardTestPlayerBase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_Single() {
|
|
||||||
// {2}: Add one mana of any color.
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", 1);
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
|
||||||
|
|
||||||
// make sure it works
|
|
||||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: ");
|
|
||||||
setChoice(playerA, "Red");
|
|
||||||
checkManaPool("mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
|
||||||
setStrictChooseMode(true);
|
|
||||||
execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore // TODO: must fix infinite mana calculation
|
|
||||||
public void test_Multiple() {
|
|
||||||
int cardsAmount = 2; // after fix make it 10+ for testing
|
|
||||||
|
|
||||||
// {2}: Add one mana of any color.
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", cardsAmount);
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 * cardsAmount);
|
|
||||||
|
|
||||||
runCode("simple way to cause freeze", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
|
||||||
player.getManaAvailable(game);
|
|
||||||
});
|
|
||||||
|
|
||||||
// make sure it works
|
|
||||||
//activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: ");
|
|
||||||
//setChoice(playerA, "Red");
|
|
||||||
//checkManaPool("mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
|
||||||
setStrictChooseMode(true);
|
|
||||||
execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.mage.test.cards.mana;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class InfiniteManaUsagesTest extends CardTestPlayerBaseWithAIHelps {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_EnergyRefractor_Single_AddToPool() {
|
||||||
|
// {2}: Add one mana of any color.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||||
|
|
||||||
|
// make sure it works
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: ");
|
||||||
|
setChoice(playerA, "Red");
|
||||||
|
checkManaPool("mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_EnergyRefractor_Multiple_AddToPool() {
|
||||||
|
int cardsAmount = 20;
|
||||||
|
|
||||||
|
// {2}: Add one mana of any color.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", cardsAmount);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 * cardsAmount);
|
||||||
|
|
||||||
|
// make sure it works
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: ");
|
||||||
|
setChoice(playerA, "Red");
|
||||||
|
checkManaPool("mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_EnergyRefractor_CastBears_Manual() {
|
||||||
|
// {2}: Add one mana of any color.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 1);
|
||||||
|
//
|
||||||
|
addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // {1}{G}
|
||||||
|
|
||||||
|
// make sure it works
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears");
|
||||||
|
setChoice(playerA, "Green");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_EnergyRefractor_CastBears_AI() {
|
||||||
|
// possible bug: StackOverflowError on mana usage
|
||||||
|
|
||||||
|
// {2}: Add one mana of any color.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Energy Refractor", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 1);
|
||||||
|
//
|
||||||
|
addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // {1}{G}
|
||||||
|
|
||||||
|
// ai must see playable card and cast it
|
||||||
|
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Grizzly Bears", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package org.mage.test.cards.triggers;
|
package org.mage.test.cards.triggers;
|
||||||
|
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
|
|
@ -6,37 +5,40 @@ import mage.constants.Zone;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author jeffwadsworth
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* Selvala, Heart of the Wilds {1}{G}{G} Whenever another creature enters the
|
* Selvala, Heart of the Wilds {1}{G}{G} Whenever another creature enters the
|
||||||
* battlefield, its controller may draw a card if its power is greater than each
|
* battlefield, its controller may draw a card if its power is greater than each
|
||||||
* other creature's power Add X mana in any combination of colors to your mana
|
* other creature's power Add X mana in any combination of colors to your mana
|
||||||
* pool, where X is the greatest power among creatures you control
|
* pool, where X is the greatest power among creatures you control
|
||||||
|
*
|
||||||
|
* @author jeffwadsworth
|
||||||
*/
|
*/
|
||||||
public class SelvalaHeartOfTheWildsTest extends CardTestPlayerBase {
|
public class SelvalaHeartOfTheWildsTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTrigger() {
|
public void test_NoTriggerOnLowerPower() {
|
||||||
// No card will be drawn due to the Memnite having a lower power than any other permanent on the battlefield
|
skipInitShuffling();
|
||||||
addCard(Zone.LIBRARY, playerA, "Island", 2);
|
addCard(Zone.LIBRARY, playerA, "Island", 1);
|
||||||
|
|
||||||
|
// Whenever another creature enters the battlefield, its controller may draw a card if its
|
||||||
|
// power is greater than each other creature's power.
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Selvala, Heart of the Wilds", 1); // 2/3
|
addCard(Zone.BATTLEFIELD, playerA, "Selvala, Heart of the Wilds", 1); // 2/3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Shivan Dragon", 1); // 5/5
|
//
|
||||||
addCard(Zone.HAND, playerA, "Memnite"); // 1/1
|
addCard(Zone.HAND, playerA, "Memnite"); // 1/1
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Shivan Dragon", 1); // 5/5
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit", 1); // 2/2
|
addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit", 1); // 2/2
|
||||||
|
// Nightmare's power and toughness are each equal to the number of Swamps you control.
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Nightmare", 1); // 4/4
|
addCard(Zone.BATTLEFIELD, playerB, "Nightmare", 1); // 4/4
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
|
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
|
||||||
|
|
||||||
|
// cast low power memnite - no draw trigger
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertHandCount(playerA, 0); // no cards drawn
|
assertHandCount(playerA, 0); // no draw
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,40 +46,45 @@ public class SelvalaHeartOfTheWildsTest extends CardTestPlayerBase {
|
||||||
* pumps its power to the highest on the battlefield allowing the controller to draw a card.
|
* pumps its power to the highest on the battlefield allowing the controller to draw a card.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTriggerWithGiantGrowth() {
|
public void test_TriggerOnBigBoosted() {
|
||||||
addCard(Zone.LIBRARY, playerA, "Island", 2);
|
skipInitShuffling();
|
||||||
// Whenever another creature enters the battlefield, its controller may draw a card if its power is greater than each other creature's power.
|
addCard(Zone.LIBRARY, playerA, "Island", 1);
|
||||||
// {G}, {T}: Add X mana in any combination of colors, where X is the greatest power among creatures you control.
|
|
||||||
|
// Whenever another creature enters the battlefield, its controller may draw a card if its
|
||||||
|
// power is greater than each other creature's power.
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Selvala, Heart of the Wilds", 1); // 2/3
|
addCard(Zone.BATTLEFIELD, playerA, "Selvala, Heart of the Wilds", 1); // 2/3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Shivan Dragon", 1); // 5/5
|
//
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
|
||||||
addCard(Zone.HAND, playerA, "Memnite"); // 1/1
|
addCard(Zone.HAND, playerA, "Memnite"); // 1/1
|
||||||
addCard(Zone.HAND, playerA, "Giant Growth", 2);
|
addCard(Zone.BATTLEFIELD, playerA, "Shivan Dragon", 1); // 5/5
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit", 1); // 2/2
|
addCard(Zone.BATTLEFIELD, playerB, "Blinking Spirit", 1); // 2/2
|
||||||
// Flying
|
|
||||||
// Nightmare's power and toughness are each equal to the number of Swamps you control.
|
// Nightmare's power and toughness are each equal to the number of Swamps you control.
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Nightmare", 1); // 4/4
|
addCard(Zone.BATTLEFIELD, playerB, "Nightmare", 1); // 4/4
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
|
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
|
||||||
|
//
|
||||||
|
// Target creature gets +3/+3 until end of turn.
|
||||||
|
addCard(Zone.HAND, playerA, "Giant Growth", 2); // {G}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
|
||||||
|
// prepare etb trigger
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
||||||
setChoice(playerA, "X=0");
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
|
||||||
setChoice(playerA, "X=0");
|
checkStackSize("must have etb", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1);
|
||||||
setChoice(playerA, "X=0");
|
checkStackObject("must have etb", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Whenever another creature", 1);
|
||||||
setChoice(playerA, "X=0");
|
|
||||||
setChoice(playerA, "X=5");
|
|
||||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN ,1);
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Memnite");
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Memnite"); // a whopping 7/7
|
|
||||||
|
|
||||||
setChoice(playerA, true);
|
// boost before trigger resolve (make memnite 7/7)
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Memnite", "Whenever another creature");
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Memnite", "Whenever another creature");
|
||||||
|
checkStackSize("must have etb + boost spells", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 3);
|
||||||
|
|
||||||
|
// trigger on grater power
|
||||||
|
setChoice(playerA, true); // draw card
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertPowerToughness(playerA, "Memnite", 7, 7);
|
assertPowerToughness(playerA, "Memnite", 7, 7);
|
||||||
assertGraveyardCount(playerA, "Giant Growth", 2);
|
assertGraveyardCount(playerA, "Giant Growth", 2);
|
||||||
assertHandCount(playerA, 1); // 2 cards drawn
|
assertHandCount(playerA, "Island", 1); // after draw
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package org.mage.test.utils;
|
||||||
|
|
||||||
|
import mage.Mana;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class ManaIncludesTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
private void assertFullyIncludes(boolean canPay, String cost, String pool) {
|
||||||
|
// workaround to add {Any} mana by string param
|
||||||
|
String strictMain = pool.replace("{Any}", "");
|
||||||
|
String stringPart = cost.replace("{Any}", "");
|
||||||
|
|
||||||
|
Mana manaMain = new ManaCostsImpl<>(strictMain).getMana();
|
||||||
|
Mana manaPart = new ManaCostsImpl<>(stringPart).getMana();
|
||||||
|
|
||||||
|
manaMain.add(new Mana(0, 0, 0, 0, 0, 0, (pool.length() - strictMain.length()) / 5, 0));
|
||||||
|
manaPart.add(new Mana(0, 0, 0, 0, 0, 0, (cost.length() - stringPart.length()) / 5, 0));
|
||||||
|
|
||||||
|
Assert.assertEquals(canPay, manaMain.includesMana(manaPart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_ManaIncludes() {
|
||||||
|
assertFullyIncludes(true, "", "");
|
||||||
|
assertFullyIncludes(true, "", "{R}");
|
||||||
|
assertFullyIncludes(true, "", "{C}");
|
||||||
|
assertFullyIncludes(true, "", "{1}");
|
||||||
|
assertFullyIncludes(true, "", "{Any}");
|
||||||
|
assertFullyIncludes(true, "", "{R}{G}");
|
||||||
|
assertFullyIncludes(true, "", "{C}{G}");
|
||||||
|
assertFullyIncludes(true, "", "{1}{G}");
|
||||||
|
assertFullyIncludes(true, "", "{Any}{G}");
|
||||||
|
|
||||||
|
assertFullyIncludes(false, "{B}", "");
|
||||||
|
assertFullyIncludes(false, "{B}", "{R}");
|
||||||
|
assertFullyIncludes(false, "{B}", "{C}");
|
||||||
|
assertFullyIncludes(false, "{B}", "{1}");
|
||||||
|
assertFullyIncludes(true, "{B}", "{Any}");
|
||||||
|
assertFullyIncludes(false, "{B}", "{R}{G}");
|
||||||
|
assertFullyIncludes(false, "{B}", "{C}{G}");
|
||||||
|
assertFullyIncludes(false, "{B}", "{1}{G}");
|
||||||
|
assertFullyIncludes(true, "{B}", "{Any}{G}");
|
||||||
|
|
||||||
|
assertFullyIncludes(false, "{G}", "");
|
||||||
|
assertFullyIncludes(false, "{G}", "{R}");
|
||||||
|
assertFullyIncludes(false, "{G}", "{C}");
|
||||||
|
assertFullyIncludes(false, "{G}", "{1}");
|
||||||
|
assertFullyIncludes(true, "{G}", "{Any}");
|
||||||
|
assertFullyIncludes(true, "{G}", "{R}{G}");
|
||||||
|
assertFullyIncludes(true, "{G}", "{C}{G}");
|
||||||
|
assertFullyIncludes(true, "{G}", "{1}{G}");
|
||||||
|
assertFullyIncludes(true, "{G}", "{Any}{G}");
|
||||||
|
|
||||||
|
assertFullyIncludes(false, "{C}", "");
|
||||||
|
assertFullyIncludes(false, "{C}", "{R}");
|
||||||
|
assertFullyIncludes(true, "{C}", "{C}");
|
||||||
|
assertFullyIncludes(false, "{C}", "{1}");
|
||||||
|
assertFullyIncludes(false, "{C}", "{Any}");
|
||||||
|
assertFullyIncludes(false, "{C}", "{R}{G}");
|
||||||
|
assertFullyIncludes(true, "{C}", "{C}{G}");
|
||||||
|
assertFullyIncludes(false, "{C}", "{1}{G}");
|
||||||
|
assertFullyIncludes(false, "{C}", "{Any}{G}");
|
||||||
|
|
||||||
|
assertFullyIncludes(false, "{1}", "");
|
||||||
|
assertFullyIncludes(true, "{1}", "{R}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{C}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{1}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{Any}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{R}{G}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{C}{G}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{1}{G}");
|
||||||
|
assertFullyIncludes(true, "{1}", "{Any}{G}");
|
||||||
|
|
||||||
|
assertFullyIncludes(false, "{Any}", "");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{R}");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{C}");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{1}");
|
||||||
|
assertFullyIncludes(true, "{Any}", "{Any}");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{R}{G}");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{C}{G}");
|
||||||
|
assertFullyIncludes(false, "{Any}", "{1}{G}");
|
||||||
|
assertFullyIncludes(true, "{Any}", "{Any}{G}");
|
||||||
|
|
||||||
|
// possible integer overflow problems
|
||||||
|
String maxGeneric = "{" + Integer.MAX_VALUE + "}";
|
||||||
|
assertFullyIncludes(false, maxGeneric, "");
|
||||||
|
assertFullyIncludes(false, maxGeneric, "{1}");
|
||||||
|
assertFullyIncludes(false, maxGeneric, "{R}");
|
||||||
|
assertFullyIncludes(true, "", maxGeneric);
|
||||||
|
assertFullyIncludes(true, "{1}", maxGeneric);
|
||||||
|
assertFullyIncludes(false, "{R}", maxGeneric);
|
||||||
|
assertFullyIncludes(true, maxGeneric, maxGeneric);
|
||||||
|
|
||||||
|
// data from infinite mana calcs bug
|
||||||
|
assertFullyIncludes(false, "{R}{R}", "{R}");
|
||||||
|
assertFullyIncludes(true, "{R}{R}", "{R}{R}");
|
||||||
|
assertFullyIncludes(true, "{R}{R}", "{R}{R}{R}");
|
||||||
|
assertFullyIncludes(true, "{R}{R}", "{R}{R}{R}{R}");
|
||||||
|
assertFullyIncludes(true, "{R}{R}", "{R}{R}{R}{R}{R}");
|
||||||
|
assertFullyIncludes(false, "{Any}{Any}", "{Any}");
|
||||||
|
assertFullyIncludes(true, "{Any}{Any}", "{Any}{Any}");
|
||||||
|
assertFullyIncludes(true, "{Any}{Any}", "{Any}{Any}{Any}");
|
||||||
|
assertFullyIncludes(true, "{Any}{Any}", "{Any}{Any}{Any}{Any}");
|
||||||
|
assertFullyIncludes(true, "{Any}{Any}", "{Any}{Any}{Any}{Any}{Any}");
|
||||||
|
|
||||||
|
// additional use cases
|
||||||
|
assertFullyIncludes(false, "{W}{W}", "{W}{B}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2765,7 +2765,7 @@ public class VerifyCardDataTest {
|
||||||
List<Card> cardsList = new ArrayList<>(CardScanner.getAllCards());
|
List<Card> cardsList = new ArrayList<>(CardScanner.getAllCards());
|
||||||
Map<String, Integer> cardRates = new HashMap<>();
|
Map<String, Integer> cardRates = new HashMap<>();
|
||||||
for (Card card : cardsList) {
|
for (Card card : cardsList) {
|
||||||
int curRate = RateCard.rateCard(card, null, false);
|
int curRate = RateCard.rateCard(card, Collections.emptyList(), false);
|
||||||
int prevRate = cardRates.getOrDefault(card.getName(), 0);
|
int prevRate = cardRates.getOrDefault(card.getName(), 0);
|
||||||
if (prevRate == 0) {
|
if (prevRate == 0) {
|
||||||
cardRates.putIfAbsent(card.getName(), curRate);
|
cardRates.putIfAbsent(card.getName(), curRate);
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,20 @@ import mage.util.Copyable;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WARNING, all mana operations must use overflow check, see usage of CardUtil.addWithOverflowCheck and same methods
|
* WARNING, all mana operations must use overflow check, see usage of CardUtil.addWithOverflowCheck and same methods
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
|
public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
|
||||||
|
|
||||||
private static final transient Logger logger = Logger.getLogger(Mana.class);
|
private static final Logger logger = Logger.getLogger(Mana.class);
|
||||||
|
|
||||||
protected int white;
|
protected int white;
|
||||||
protected int blue;
|
protected int blue;
|
||||||
|
|
@ -442,6 +445,20 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
|
||||||
any = CardUtil.overflowDec(any, mana.any);
|
any = CardUtil.overflowDec(any, mana.any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mana must contains only positive values
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return white >= 0
|
||||||
|
&& blue >= 0
|
||||||
|
&& black >= 0
|
||||||
|
&& red >= 0
|
||||||
|
&& green >= 0
|
||||||
|
&& generic >= 0
|
||||||
|
&& colorless >= 0
|
||||||
|
&& any >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtracts the passed in mana values from this instance. The difference
|
* Subtracts the passed in mana values from this instance. The difference
|
||||||
* between this and {@code subtract()} is that if we do not have the
|
* between this and {@code subtract()} is that if we do not have the
|
||||||
|
|
@ -1211,24 +1228,99 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this {@link Mana} object has more than or equal values of mana
|
* Compare two mana - is one part includes into another part. Support any mana types and uses a payment logic.
|
||||||
* as the passed in {@link Mana} object. Ignores {Any} mana to prevent
|
* <p>
|
||||||
* endless iterations.
|
* Used for AI and mana optimizations to remove duplicated mana options.
|
||||||
*
|
|
||||||
* @param mana the mana to compare with
|
|
||||||
* @return if this object has more than or equal mana to the passed in
|
|
||||||
* {@link Mana}.
|
|
||||||
*/
|
*/
|
||||||
public boolean includesMana(Mana mana) {
|
public boolean includesMana(Mana manaPart) {
|
||||||
return this.white >= mana.white
|
if (!this.isValid() || !manaPart.isValid()) {
|
||||||
&& this.blue >= mana.blue
|
// how-to fix: make sure mana calculations do not add or subtract values without result checks or isValid call
|
||||||
&& this.black >= mana.black
|
throw new IllegalArgumentException("Wrong code usage: found negative values in mana calculations: main " + this + ", part " + manaPart);
|
||||||
&& this.red >= mana.red
|
}
|
||||||
&& this.green >= mana.green
|
|
||||||
&& this.colorless >= mana.colorless
|
|
||||||
&& (this.generic >= mana.generic
|
|
||||||
|| CardUtil.overflowInc(this.countColored(), this.colorless) >= mana.count());
|
|
||||||
|
|
||||||
|
if (this.count() < manaPart.count()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manaPart.count() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's uses pay logic with additional {any} mana support:
|
||||||
|
// - {any} in cost - can be paid by {any} mana only
|
||||||
|
// - {any} in pay - can be used to pay {any}, {1} and colored mana (but not {C})
|
||||||
|
Mana pool = this.copy();
|
||||||
|
Mana cost = manaPart.copy();
|
||||||
|
|
||||||
|
// first pay type by type (it's important to pay {any} first)
|
||||||
|
// 10 - 3 = 7 in pool, 0 in cost
|
||||||
|
// 5 - 7 = 0 in pool, 2 in cost
|
||||||
|
pool.subtract(cost);
|
||||||
|
cost.white = Math.max(0, -1 * pool.white);
|
||||||
|
pool.white = Math.max(0, pool.white);
|
||||||
|
cost.blue = Math.max(0, -1 * pool.blue);
|
||||||
|
pool.blue = Math.max(0, pool.blue);
|
||||||
|
cost.black = Math.max(0, -1 * pool.black);
|
||||||
|
pool.black = Math.max(0, pool.black);
|
||||||
|
cost.red = Math.max(0, -1 * pool.red);
|
||||||
|
pool.red = Math.max(0, pool.red);
|
||||||
|
cost.green = Math.max(0, -1 * pool.green);
|
||||||
|
pool.green = Math.max(0, pool.green);
|
||||||
|
cost.generic = Math.max(0, -1 * pool.generic);
|
||||||
|
pool.generic = Math.max(0, pool.generic);
|
||||||
|
cost.colorless = Math.max(0, -1 * pool.colorless);
|
||||||
|
pool.colorless = Math.max(0, pool.colorless);
|
||||||
|
cost.any = Math.max(0, -1 * pool.any);
|
||||||
|
pool.any = Math.max(0, pool.any);
|
||||||
|
if (cost.count() > pool.count()) {
|
||||||
|
throw new IllegalArgumentException("Wrong mana calculation: " + cost + " - " + pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't pay {any} or {C}
|
||||||
|
if (cost.any > 0 || cost.colorless > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then pay colored by {any}
|
||||||
|
if (pool.any > 0 && cost.white > 0) {
|
||||||
|
int diff = Math.min(pool.any, cost.white);
|
||||||
|
pool.any -= diff;
|
||||||
|
cost.white -= diff;
|
||||||
|
}
|
||||||
|
if (pool.any > 0 && cost.blue > 0) {
|
||||||
|
int diff = Math.min(pool.any, cost.blue);
|
||||||
|
pool.any -= diff;
|
||||||
|
cost.blue -= diff;
|
||||||
|
}
|
||||||
|
if (pool.any > 0 && cost.black > 0) {
|
||||||
|
int diff = Math.min(pool.any, cost.black);
|
||||||
|
pool.any -= diff;
|
||||||
|
cost.black -= diff;
|
||||||
|
}
|
||||||
|
if (pool.any > 0 && cost.red > 0) {
|
||||||
|
int diff = Math.min(pool.any, cost.red);
|
||||||
|
pool.any -= diff;
|
||||||
|
cost.red -= diff;
|
||||||
|
}
|
||||||
|
if (pool.any > 0 && cost.green > 0) {
|
||||||
|
int diff = Math.min(pool.any, cost.green);
|
||||||
|
pool.any -= diff;
|
||||||
|
cost.green -= diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't pay colored
|
||||||
|
if (cost.countColored() > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then pay generic by {any}, colored or {C}
|
||||||
|
int leftPool = pool.count();
|
||||||
|
if (leftPool > 0 && cost.generic > 0) {
|
||||||
|
int diff = Math.min(leftPool, cost.generic);
|
||||||
|
cost.generic -= diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost.count() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMoreValuableThan(Mana that) {
|
public boolean isMoreValuableThan(Mana that) {
|
||||||
|
|
@ -1350,19 +1442,19 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
|
||||||
public int getDifferentColors() {
|
public int getDifferentColors() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
if (white > 0) {
|
if (white > 0) {
|
||||||
count = CardUtil.overflowInc(count, 1);
|
count++;
|
||||||
}
|
}
|
||||||
if (blue > 0) {
|
if (blue > 0) {
|
||||||
count = CardUtil.overflowInc(count, 1);
|
count++;
|
||||||
}
|
}
|
||||||
if (black > 0) {
|
if (black > 0) {
|
||||||
count = CardUtil.overflowInc(count, 1);
|
count++;
|
||||||
}
|
}
|
||||||
if (red > 0) {
|
if (red > 0) {
|
||||||
count = CardUtil.overflowInc(count, 1);
|
count++;
|
||||||
}
|
}
|
||||||
if (green > 0) {
|
if (green > 0) {
|
||||||
count = CardUtil.overflowInc(count, 1);
|
count++;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import org.apache.log4j.Logger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
|
||||||
* <p>
|
* <p>
|
||||||
* this class is used to build a list of all possible mana combinations it can
|
* this class is used to build a list of all possible mana combinations it can
|
||||||
* be used to find all the ways to pay a mana cost or all the different mana
|
* be used to find all the ways to pay a mana cost or all the different mana
|
||||||
|
|
@ -25,6 +24,8 @@ import java.util.*;
|
||||||
* <p>
|
* <p>
|
||||||
* A LinkedHashSet is used to get the performance benefits of automatic de-duplication of the Mana
|
* A LinkedHashSet is used to get the performance benefits of automatic de-duplication of the Mana
|
||||||
* to avoid performance issues related with manual de-duplication (see https://github.com/magefree/mage/issues/7710).
|
* to avoid performance issues related with manual de-duplication (see https://github.com/magefree/mage/issues/7710).
|
||||||
|
*
|
||||||
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class ManaOptions extends LinkedHashSet<Mana> {
|
public class ManaOptions extends LinkedHashSet<Mana> {
|
||||||
|
|
||||||
|
|
@ -378,54 +379,69 @@ public class ManaOptions extends LinkedHashSet<Mana> {
|
||||||
/**
|
/**
|
||||||
* Performs the simulation of a mana ability with costs
|
* Performs the simulation of a mana ability with costs
|
||||||
*
|
*
|
||||||
* @param cost cost to use the ability
|
* @param cost cost to use the ability
|
||||||
* @param manaToAdd one mana variation that can be added by using
|
* @param manaToAdd one mana variation that can be added by using
|
||||||
* this ability
|
* this ability
|
||||||
* @param onlyManaCosts flag to know if the costs are mana costs only
|
* @param onlyManaCosts flag to know if the costs are mana costs only (will try to use ability multiple times)
|
||||||
* @param currentMana the mana available before the usage of the
|
* @param startingMana the mana available before the usage of the
|
||||||
* ability
|
* ability
|
||||||
* @param oldManaWasReplaced returns the info if the new complete mana does
|
* @return true if the new complete mana does replace the current mana completely
|
||||||
* replace the current mana completely
|
|
||||||
*/
|
*/
|
||||||
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, final Mana currentMana, ManaAbility manaAbility, Game game) {
|
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, final Mana startingMana, ManaAbility manaAbility, Game game) {
|
||||||
boolean oldManaWasReplaced = false; // True if the newly created mana includes all mana possibilities of the old
|
boolean oldManaWasReplaced = false; // True if the newly created mana includes all mana possibilities of the old
|
||||||
boolean repeatable = manaToAdd != null // TODO: re-write "only replace to any with mana costs only will be repeated if able"
|
boolean repeatable = manaToAdd != null // TODO: re-write "only replace to any with mana costs only will be repeated if able"
|
||||||
&& onlyManaCosts
|
&& onlyManaCosts
|
||||||
&& (manaToAdd.getAny() > 0 || manaToAdd.countColored() > 0)
|
&& manaToAdd.countColored() > 0;
|
||||||
&& manaToAdd.count() > 0;
|
boolean canHaveBetterValues;
|
||||||
boolean newCombinations;
|
|
||||||
|
|
||||||
Mana newMana = new Mana();
|
Mana possibleMana = new Mana();
|
||||||
Mana currentManaCopy = new Mana();
|
Mana improvedMana = new Mana();
|
||||||
|
|
||||||
for (Mana payCombination : ManaOptions.getPossiblePayCombinations(cost, currentMana)) {
|
// simulate multiple calls of mana abilities and replace mana pool by better values
|
||||||
currentManaCopy.setToMana(currentMana); // copy start mana because in iteration it will be updated
|
// example: {G}: Add one mana of any color
|
||||||
do { // loop for multiple usage if possible
|
for (Mana possiblePay : ManaOptions.getPossiblePayCombinations(cost, startingMana)) {
|
||||||
newCombinations = false;
|
improvedMana.setToMana(startingMana);
|
||||||
|
do {
|
||||||
|
// loop until all mana replaced by better values
|
||||||
|
canHaveBetterValues = false;
|
||||||
|
|
||||||
newMana.setToMana(currentManaCopy);
|
// it's impossible to analyse all payment order (pay {R} for {1}, {Any} for {G}, etc)
|
||||||
newMana.subtract(payCombination);
|
// so use simple cost simulation by subtract
|
||||||
// Get the mana to iterate over.
|
possibleMana.setToMana(improvedMana);
|
||||||
// If manaToAdd is specified add it, otherwise add the mana produced by the mana ability
|
possibleMana.subtract(possiblePay);
|
||||||
List<Mana> manasToAdd = (manaToAdd != null) ? Collections.singletonList(manaToAdd) : manaAbility.getNetMana(game, newMana);
|
if (!possibleMana.isValid()) {
|
||||||
for (Mana mana : manasToAdd) {
|
//if (possibleMana.canPayMana(possiblePay)) {
|
||||||
newMana.add(mana);
|
// TODO: canPayMana/includesMana uses better pay logic, so subtract can be improved somehow
|
||||||
if (this.contains(newMana)) {
|
//logger.warn("found un-supported payment combination: pool " + possibleMana + ", cost " + possiblePay);
|
||||||
|
//}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find resulting mana (it can have multiple options)
|
||||||
|
List<Mana> addingManaOptions = (manaToAdd != null) ? Collections.singletonList(manaToAdd) : manaAbility.getNetMana(game, possibleMana);
|
||||||
|
for (Mana addingMana : addingManaOptions) {
|
||||||
|
// TODO: is it bugged on addingManaOptions.size() > 1 (adding multiple times)?
|
||||||
|
possibleMana.add(addingMana);
|
||||||
|
|
||||||
|
// already found that combination before
|
||||||
|
if (this.contains(possibleMana)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.add(newMana.copy()); // add the new combination
|
// found new combination - add it to final options
|
||||||
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
|
this.add(possibleMana.copy());
|
||||||
|
canHaveBetterValues = true;
|
||||||
|
|
||||||
if (newMana.isMoreValuableThan(currentManaCopy)) {
|
// remove old worse options
|
||||||
oldManaWasReplaced = true; // the new mana includes all possible mana of the old one, so no need to add it after return
|
if (possibleMana.isMoreValuableThan(improvedMana)) {
|
||||||
if (!currentMana.equalManaValue(currentManaCopy)) {
|
oldManaWasReplaced = true;
|
||||||
this.removeEqualMana(currentManaCopy);
|
if (!startingMana.equalManaValue(improvedMana)) {
|
||||||
|
this.removeEqualMana(improvedMana);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentManaCopy.setToMana(newMana);
|
improvedMana.setToMana(possibleMana);
|
||||||
}
|
}
|
||||||
} while (repeatable && newCombinations && currentManaCopy.includesMana(payCombination));
|
} while (repeatable && canHaveBetterValues && improvedMana.includesMana(possiblePay));
|
||||||
}
|
}
|
||||||
return oldManaWasReplaced;
|
return oldManaWasReplaced;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue