forked from External/mage
Refactor: Significant speed-up for ManaOptions (#9233)
This commit is contained in:
parent
23a4d2640b
commit
55a6acba22
32 changed files with 782 additions and 408 deletions
|
|
@ -3,11 +3,13 @@ package mage.abilities.mana;
|
|||
import mage.ConditionalMana;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.ManaType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ManaEvent;
|
||||
import mage.game.events.TappedForManaEvent;
|
||||
import mage.players.Player;
|
||||
import mage.util.TreeNode;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -19,10 +21,14 @@ import java.util.*;
|
|||
* be used to find all the ways to pay a mana cost or all the different mana
|
||||
* combinations available to a player
|
||||
* <p>
|
||||
* TODO: Conditional Mana is not supported yet. The mana adding removes the
|
||||
* condition of conditional mana
|
||||
* TODO: Conditional Mana is not supported yet.
|
||||
* The mana adding removes the condition of conditional mana
|
||||
* <p>
|
||||
* 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).
|
||||
*
|
||||
*/
|
||||
public class ManaOptions extends ArrayList<Mana> {
|
||||
public class ManaOptions extends LinkedHashSet<Mana> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ManaOptions.class);
|
||||
|
||||
|
|
@ -39,69 +45,68 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
if (isEmpty()) {
|
||||
this.add(new Mana());
|
||||
}
|
||||
if (!abilities.isEmpty()) {
|
||||
if (abilities.size() == 1) {
|
||||
//if there is only one mana option available add it to all the existing options
|
||||
List<Mana> netManas = abilities.get(0).getNetMana(game);
|
||||
if (netManas.size() == 1) {
|
||||
checkManaReplacementAndTriggeredMana(abilities.get(0), game, netManas.get(0));
|
||||
addMana(netManas.get(0));
|
||||
addTriggeredMana(game, abilities.get(0));
|
||||
} else if (netManas.size() > 1) {
|
||||
addManaVariation(netManas, abilities.get(0), game);
|
||||
}
|
||||
if (abilities.isEmpty()) {
|
||||
return; // Do nothing
|
||||
}
|
||||
|
||||
} else { // mana source has more than 1 ability
|
||||
//perform a union of all existing options and the new options
|
||||
List<Mana> copy = copy();
|
||||
this.clear();
|
||||
for (ActivatedManaAbilityImpl ability : abilities) {
|
||||
for (Mana netMana : ability.getNetMana(game)) {
|
||||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
SkipAddMana:
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
newMana.add(triggeredManaVariation);
|
||||
for (Mana existingMana : this) {
|
||||
if (existingMana.equalManaValue(newMana)) {
|
||||
continue SkipAddMana;
|
||||
}
|
||||
Mana moreValuable = Mana.getMoreValuableMana(newMana, existingMana);
|
||||
if (moreValuable != null) {
|
||||
// only keep the more valuable mana
|
||||
existingMana.setToMana(moreValuable);
|
||||
continue SkipAddMana;
|
||||
}
|
||||
if (abilities.size() == 1) {
|
||||
//if there is only one mana option available add it to all the existing options
|
||||
List<Mana> netManas = abilities.get(0).getNetMana(game);
|
||||
if (netManas.size() == 1) {
|
||||
checkManaReplacementAndTriggeredMana(abilities.get(0), game, netManas.get(0));
|
||||
addMana(netManas.get(0));
|
||||
addTriggeredMana(game, abilities.get(0));
|
||||
} else if (netManas.size() > 1) {
|
||||
addManaVariation(netManas, abilities.get(0), game);
|
||||
}
|
||||
|
||||
} else { // mana source has more than 1 ability
|
||||
//perform a union of all existing options and the new options
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (ActivatedManaAbilityImpl ability : abilities) {
|
||||
for (Mana netMana : ability.getNetMana(game)) {
|
||||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
SkipAddMana:
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
newMana.add(triggeredManaVariation);
|
||||
for (Mana existingMana : this) {
|
||||
if (existingMana.equalManaValue(newMana)) {
|
||||
continue SkipAddMana;
|
||||
}
|
||||
Mana moreValuable = Mana.getMoreValuableMana(newMana, existingMana);
|
||||
if (moreValuable != null) {
|
||||
// only keep the more valuable mana
|
||||
existingMana.setToMana(moreValuable);
|
||||
continue SkipAddMana;
|
||||
}
|
||||
this.add(newMana);
|
||||
}
|
||||
this.add(newMana);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceManaDeduplication();
|
||||
}
|
||||
|
||||
private void addManaVariation(List<Mana> netManas, ActivatedManaAbilityImpl ability, Game game) {
|
||||
List<Mana> copy = copy();
|
||||
Mana newMana;
|
||||
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (Mana netMana : netManas) {
|
||||
for (Mana mana : copy) {
|
||||
if (!ability.hasTapCost() || checkManaReplacementAndTriggeredMana(ability, game, netMana)) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
newMana = mana.copy();
|
||||
newMana.add(netMana);
|
||||
this.add(newMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceManaDeduplication();
|
||||
}
|
||||
|
||||
private static List<List<Mana>> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) {
|
||||
|
|
@ -138,8 +143,7 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
}
|
||||
|
||||
/**
|
||||
* This adds the mana the abilities can produce to the possible mana
|
||||
* variabtion.
|
||||
* This adds the mana the abilities can produce to the possible mana variation.
|
||||
*
|
||||
* @param abilities
|
||||
* @param game
|
||||
|
|
@ -147,14 +151,13 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
*/
|
||||
public boolean addManaWithCost(List<ActivatedManaAbilityImpl> abilities, Game game) {
|
||||
boolean wasUsable = false;
|
||||
int replaces = 0;
|
||||
if (isEmpty()) {
|
||||
this.add(new Mana()); // needed if this is the first available mana, otherwise looping over existing options woold not loop
|
||||
}
|
||||
if (!abilities.isEmpty()) {
|
||||
if (abilities.size() == 1) {
|
||||
List<Mana> netManas = abilities.get(0).getNetMana(game);
|
||||
if (netManas.size() > 0) { // ability can produce mana
|
||||
if (!netManas.isEmpty()) { // ability can produce mana
|
||||
ActivatedManaAbilityImpl ability = abilities.get(0);
|
||||
// The ability has no mana costs
|
||||
if (ability.getManaCosts().isEmpty()) { // No mana costs, so no mana to subtract from available
|
||||
|
|
@ -163,14 +166,13 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
addMana(netManas.get(0));
|
||||
addTriggeredMana(game, ability);
|
||||
} else {
|
||||
List<Mana> copy = copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (Mana netMana : netManas) {
|
||||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
Mana newMana = new Mana(mana);
|
||||
newMana.add(triggeredManaVariation);
|
||||
this.add(newMana);
|
||||
wasUsable = true;
|
||||
|
|
@ -179,23 +181,22 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
}
|
||||
}
|
||||
} else {// The ability has mana costs
|
||||
List<Mana> copy = copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
Mana manaCosts = ability.getManaCosts().getMana();
|
||||
for (Mana netMana : netManas) {
|
||||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
for (Mana prevMana : copy) {
|
||||
Mana startingMana = prevMana.copy();
|
||||
Mana manaCosts = ability.getManaCosts().getMana();
|
||||
for (Mana startingMana : copy) {
|
||||
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
|
||||
if (!subtractCostAddMana(manaCosts, triggeredManaVariation, ability.getCosts().isEmpty(), startingMana, ability, game)) {
|
||||
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
|
||||
add(prevMana);
|
||||
add(startingMana);
|
||||
}
|
||||
wasUsable = true;
|
||||
} else {
|
||||
// mana costs can't be paid so keep starting mana
|
||||
add(prevMana);
|
||||
add(startingMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,7 +205,7 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
}
|
||||
} else {
|
||||
//perform a union of all existing options and the new options
|
||||
List<Mana> copy = copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (ActivatedManaAbilityImpl ability : abilities) {
|
||||
List<Mana> netManas = ability.getNetMana(game);
|
||||
|
|
@ -213,8 +214,7 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
Mana newMana = new Mana(mana);
|
||||
newMana.add(triggeredManaVariation);
|
||||
this.add(newMana);
|
||||
wasUsable = true;
|
||||
|
|
@ -226,9 +226,9 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||||
for (Mana previousMana : copy) {
|
||||
CombineWithExisting:
|
||||
for (Mana manaOption : ability.getManaCosts().getManaOptions()) {
|
||||
if (previousMana.includesMana(manaOption)) { // costs can be paid
|
||||
// subtractCostAddMana has side effects on {this}. Do not add wasUsable to the if-statement above
|
||||
wasUsable |= subtractCostAddMana(manaOption, triggeredManaVariation, ability.getCosts().isEmpty(), previousMana, ability, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -241,41 +241,41 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.size() > 30 || replaces > 30) {
|
||||
logger.trace("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString());
|
||||
if (logger.isTraceEnabled() && this.size() > 30) {
|
||||
logger.trace("ManaOptionsCosts " + this.size());
|
||||
logger.trace("Abilities: " + abilities.toString());
|
||||
}
|
||||
forceManaDeduplication();
|
||||
|
||||
return wasUsable;
|
||||
}
|
||||
|
||||
public boolean addManaPoolDependant(List<ActivatedManaAbilityImpl> abilities, Game game) {
|
||||
if (abilities.isEmpty() || abilities.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean wasUsable = false;
|
||||
if (!abilities.isEmpty()) {
|
||||
if (abilities.size() == 1) {
|
||||
ActivatedManaAbilityImpl ability = (ActivatedManaAbilityImpl) abilities.get(0);
|
||||
List<Mana> copy = copy();
|
||||
this.clear();
|
||||
for (Mana previousMana : copy) {
|
||||
Mana startingMana = previousMana.copy();
|
||||
Mana manaCosts = ability.getManaCosts().getMana();
|
||||
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
|
||||
for (Mana manaOption : ability.getManaCosts().getManaOptions()) {
|
||||
if (!subtractCostAddMana(manaOption, null, ability.getCosts().isEmpty(), startingMana, ability, game)) {
|
||||
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
|
||||
add(previousMana);
|
||||
}
|
||||
}
|
||||
wasUsable = true;
|
||||
} else {
|
||||
// mana costs can't be paid so keep starting mana
|
||||
ActivatedManaAbilityImpl ability = (ActivatedManaAbilityImpl) abilities.get(0);
|
||||
Mana manaCosts = ability.getManaCosts().getMana();
|
||||
|
||||
Set<Mana> copy = copy();
|
||||
this.clear();
|
||||
|
||||
for (Mana previousMana : copy) {
|
||||
if (previousMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
|
||||
for (Mana manaOption : ability.getManaCosts().getManaOptions()) {
|
||||
if (!subtractCostAddMana(manaOption, null, ability.getCosts().isEmpty(), previousMana, ability, game)) {
|
||||
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
|
||||
add(previousMana);
|
||||
}
|
||||
}
|
||||
|
||||
wasUsable = true;
|
||||
} else {
|
||||
// mana costs can't be paid so keep starting mana
|
||||
add(previousMana);
|
||||
}
|
||||
}
|
||||
|
||||
return wasUsable;
|
||||
}
|
||||
|
||||
|
|
@ -311,20 +311,17 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
addMana(triggeredNetMana.get(0));
|
||||
} else if (triggeredNetMana.size() > 1) {
|
||||
// Add variations
|
||||
List<Mana> copy = copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (Mana triggeredMana : triggeredNetMana) {
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
Mana newMana = new Mana(mana);
|
||||
newMana.add(triggeredMana);
|
||||
this.add(newMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceManaDeduplication();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -337,7 +334,7 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
this.add(new Mana());
|
||||
}
|
||||
if (addMana instanceof ConditionalMana) {
|
||||
ManaOptions copy = this.copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (Mana mana : copy) {
|
||||
ConditionalMana condMana = ((ConditionalMana) addMana).copy();
|
||||
|
|
@ -351,8 +348,6 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
mana.add(addMana);
|
||||
}
|
||||
}
|
||||
|
||||
forceManaDeduplication();
|
||||
}
|
||||
|
||||
public void addMana(ManaOptions options) {
|
||||
|
|
@ -362,32 +357,20 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
if (!options.isEmpty()) {
|
||||
if (options.size() == 1) {
|
||||
//if there is only one mana option available add it to all the existing options
|
||||
addMana(options.get(0));
|
||||
addMana(options.getAtIndex(0));
|
||||
} else {
|
||||
//perform a union of all existing options and the new options
|
||||
List<Mana> copy = copy();
|
||||
List<Mana> copy = new ArrayList<>(this);
|
||||
this.clear();
|
||||
for (Mana addMana : options) {
|
||||
for (Mana mana : copy) {
|
||||
Mana newMana = new Mana();
|
||||
newMana.add(mana);
|
||||
Mana newMana = new Mana(mana);
|
||||
newMana.add(addMana);
|
||||
this.add(newMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceManaDeduplication();
|
||||
}
|
||||
|
||||
private void forceManaDeduplication() {
|
||||
// memory overflow protection - force de-duplication on too much mana sources
|
||||
// bug example: https://github.com/magefree/mage/issues/6938
|
||||
// use it after new mana adding
|
||||
if (this.size() > 1000) {
|
||||
this.removeDuplicated();
|
||||
}
|
||||
}
|
||||
|
||||
public ManaOptions copy() {
|
||||
|
|
@ -406,62 +389,46 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
* @param oldManaWasReplaced returns the info if the new complete mana does
|
||||
* replace the current mana completely
|
||||
*/
|
||||
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, Mana currentMana, ManaAbility manaAbility, Game game) {
|
||||
boolean oldManaWasReplaced = false; // true if the newly created mana includes all mana possibilities of the old
|
||||
boolean repeatable = false;
|
||||
if (manaToAdd != null && (manaToAdd.countColored() > 0 || manaToAdd.getAny() > 0) && manaToAdd.count() > 0 && onlyManaCosts) {
|
||||
repeatable = true; // only replace to any with mana costs only will be repeated if able
|
||||
}
|
||||
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, final Mana currentMana, ManaAbility manaAbility, Game game) {
|
||||
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"
|
||||
&& onlyManaCosts
|
||||
&& (manaToAdd.getAny() > 0 || manaToAdd.countColored() > 0)
|
||||
&& manaToAdd.count() > 0;
|
||||
boolean newCombinations;
|
||||
|
||||
Mana newMana = new Mana();
|
||||
Mana currentManaCopy = new Mana();
|
||||
|
||||
for (Mana payCombination : ManaOptions.getPossiblePayCombinations(cost, currentMana)) {
|
||||
Mana currentManaCopy = currentMana.copy(); // copy start mana because in iteration it will be updated
|
||||
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
|
||||
boolean newCombinations = false;
|
||||
currentManaCopy.setToMana(currentMana); // copy start mana because in iteration it will be updated
|
||||
do { // loop for multiple usage if possible
|
||||
newCombinations = false;
|
||||
|
||||
if (manaToAdd == null) {
|
||||
Mana newMana = currentManaCopy.copy();
|
||||
newMana.subtract(payCombination);
|
||||
for (Mana mana : manaAbility.getNetMana(game, newMana)) { // get the mana to add from the ability related to the currently generated possible mana pool
|
||||
newMana.add(mana);
|
||||
if (!isExistingManaCombination(newMana)) {
|
||||
this.add(newMana); // add the new combination
|
||||
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
|
||||
newMana.setToMana(currentManaCopy);
|
||||
newMana.subtract(payCombination);
|
||||
// Get the mana to iterate over.
|
||||
// If manaToAdd is specified add it, otherwise add the mana produced by the mana ability
|
||||
List<Mana> manasToAdd = (manaToAdd != null) ? Collections.singletonList(manaToAdd) : manaAbility.getNetMana(game, newMana);
|
||||
for (Mana mana : manasToAdd) {
|
||||
newMana.add(mana);
|
||||
if (this.contains(newMana)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Mana moreValuable = Mana.getMoreValuableMana(currentManaCopy, newMana);
|
||||
if (newMana.equals(moreValuable)) {
|
||||
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return
|
||||
if (!currentMana.equalManaValue(currentManaCopy)) {
|
||||
this.removeEqualMana(currentManaCopy);
|
||||
}
|
||||
}
|
||||
currentManaCopy = newMana.copy();
|
||||
this.add(newMana.copy()); // add the new combination
|
||||
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
|
||||
|
||||
if (newMana.isMoreValuableThan(currentManaCopy)) {
|
||||
oldManaWasReplaced = true; // the new mana includes all possible mana of the old one, so no need to add it after return
|
||||
if (!currentMana.equalManaValue(currentManaCopy)) {
|
||||
this.removeEqualMana(currentManaCopy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Mana newMana = currentManaCopy.copy();
|
||||
newMana.subtract(payCombination);
|
||||
newMana.add(manaToAdd);
|
||||
if (!isExistingManaCombination(newMana)) {
|
||||
this.add(newMana); // add the new combination
|
||||
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
|
||||
Mana moreValuable = Mana.getMoreValuableMana(currentManaCopy, newMana);
|
||||
if (newMana.equals(moreValuable)) {
|
||||
oldManaWasReplaced = true; // the new mana includes all possible mana of the old one, so no need to add it after return
|
||||
if (!currentMana.equalManaValue(currentManaCopy)) {
|
||||
this.removeEqualMana(currentManaCopy);
|
||||
}
|
||||
}
|
||||
currentManaCopy = newMana.copy();
|
||||
}
|
||||
currentManaCopy.setToMana(newMana);
|
||||
}
|
||||
if (!newCombinations || !repeatable) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} while (repeatable && newCombinations && currentManaCopy.includesMana(payCombination));
|
||||
}
|
||||
forceManaDeduplication();
|
||||
|
||||
return oldManaWasReplaced;
|
||||
}
|
||||
|
||||
|
|
@ -470,86 +437,72 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
* @param manaAvailable
|
||||
* @return
|
||||
*/
|
||||
public static List<Mana> getPossiblePayCombinations(Mana manaCost, Mana manaAvailable) {
|
||||
List<Mana> payCombinations = new ArrayList<>();
|
||||
List<String> payCombinationsStrings = new ArrayList<>();
|
||||
// handle fixed mana costs
|
||||
public static Set<Mana> getPossiblePayCombinations(Mana manaCost, Mana manaAvailable) {
|
||||
Set<Mana> payCombinations = new HashSet<>();
|
||||
|
||||
Mana fixedMana = manaCost.copy();
|
||||
|
||||
// If there are no generic costs, then there is only one combination of colors available to pay for it.
|
||||
// That combination is itself (fixedMana)
|
||||
if (manaCost.getGeneric() == 0) {
|
||||
payCombinations.add(fixedMana);
|
||||
return payCombinations;
|
||||
}
|
||||
|
||||
// Get the available mana left to pay for the cost after subtracting the non-generic parts of the cost from it
|
||||
fixedMana.setGeneric(0);
|
||||
Mana manaAfterFixedPayment = manaAvailable.copy();
|
||||
manaAfterFixedPayment.subtract(fixedMana);
|
||||
|
||||
// handle generic mana costs
|
||||
if (manaAvailable.countColored() > 0) {
|
||||
|
||||
if (manaAvailable.countColored() == 0) {
|
||||
payCombinations.add(Mana.ColorlessMana(manaCost.getGeneric()));
|
||||
} else {
|
||||
ManaType[] manaTypes = ManaType.values(); // Do not move, here for optimization reasons.
|
||||
Mana manaToPayFrom = new Mana();
|
||||
for (int i = 0; i < manaCost.getGeneric(); i++) {
|
||||
List<Mana> existingManas = new ArrayList<>();
|
||||
List<Mana> existingManas = new ArrayList<>(payCombinations.size());
|
||||
if (i > 0) {
|
||||
existingManas.addAll(payCombinations);
|
||||
payCombinations.clear();
|
||||
payCombinationsStrings.clear();
|
||||
} else {
|
||||
existingManas.add(new Mana());
|
||||
}
|
||||
for (Mana existingMana : existingManas) {
|
||||
Mana manaToPayFrom = manaAfterFixedPayment.copy();
|
||||
manaToPayFrom.subtract(existingMana);
|
||||
String manaString = existingMana.toString();
|
||||
|
||||
if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(manaString + Mana.BlackMana(1))) {
|
||||
manaToPayFrom.subtract(Mana.BlackMana(1));
|
||||
ManaOptions.addManaCombination(Mana.BlackMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
}
|
||||
if (manaToPayFrom.getBlue() > 0 && !payCombinationsStrings.contains(manaString + Mana.BlueMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.BlueMana(1));
|
||||
ManaOptions.addManaCombination(Mana.BlueMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
}
|
||||
if (manaToPayFrom.getGreen() > 0 && !payCombinationsStrings.contains(manaString + Mana.GreenMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.GreenMana(1));
|
||||
ManaOptions.addManaCombination(Mana.GreenMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
}
|
||||
if (manaToPayFrom.getRed() > 0 && !payCombinationsStrings.contains(manaString + Mana.RedMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.RedMana(1));
|
||||
ManaOptions.addManaCombination(Mana.RedMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
}
|
||||
if (manaToPayFrom.getWhite() > 0 && !payCombinationsStrings.contains(manaString + Mana.WhiteMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.WhiteMana(1));
|
||||
ManaOptions.addManaCombination(Mana.WhiteMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
}
|
||||
if (manaToPayFrom.getColorless() > 0 && !payCombinationsStrings.contains(manaString + Mana.ColorlessMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.ColorlessMana(1));
|
||||
ManaOptions.addManaCombination(Mana.ColorlessMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
for (Mana existingMana : existingManas) {
|
||||
manaToPayFrom.setToMana(manaAfterFixedPayment);
|
||||
manaToPayFrom.subtract(existingMana);
|
||||
|
||||
for (ManaType manaType : manaTypes) {
|
||||
existingMana.increase(manaType);
|
||||
if (manaToPayFrom.get(manaType) > 0 && !payCombinations.contains(existingMana)) {
|
||||
payCombinations.add(existingMana.copy());
|
||||
|
||||
manaToPayFrom.decrease(manaType);
|
||||
}
|
||||
existingMana.decrease(manaType);
|
||||
}
|
||||
|
||||
// Pay with any only needed if colored payment was not possible
|
||||
if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0 && !payCombinationsStrings.contains(manaString + Mana.AnyMana(1).toString())) {
|
||||
manaToPayFrom.subtract(Mana.AnyMana(1));
|
||||
ManaOptions.addManaCombination(Mana.AnyMana(1), existingMana, payCombinations, payCombinationsStrings);
|
||||
// NOTE: This isn't in the for loop since ManaType doesn't include ANY.
|
||||
existingMana.increaseAny();
|
||||
if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0) {
|
||||
payCombinations.add(existingMana.copy());
|
||||
|
||||
manaToPayFrom.decreaseAny();
|
||||
}
|
||||
existingMana.decreaseAny();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
payCombinations.add(Mana.ColorlessMana(manaCost.getGeneric()));
|
||||
}
|
||||
|
||||
for (Mana mana : payCombinations) {
|
||||
mana.add(fixedMana);
|
||||
}
|
||||
// All mana values in here are of length 5
|
||||
return payCombinations;
|
||||
}
|
||||
|
||||
private boolean isExistingManaCombination(Mana newMana) {
|
||||
for (Mana mana : this) {
|
||||
Mana moreValuable = Mana.getMoreValuableMana(mana, newMana);
|
||||
if (mana.equals(moreValuable)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean removeEqualMana(Mana manaToRemove) {
|
||||
boolean result = false;
|
||||
for (Iterator<Mana> iterator = this.iterator(); iterator.hasNext(); ) {
|
||||
|
|
@ -562,22 +515,20 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static void addManaCombination(Mana mana, Mana existingMana, List<Mana> payCombinations, List<String> payCombinationsStrings) {
|
||||
Mana newMana = existingMana.copy();
|
||||
newMana.add(mana);
|
||||
payCombinations.add(newMana);
|
||||
payCombinationsStrings.add(newMana.toString());
|
||||
}
|
||||
|
||||
public void removeDuplicated() {
|
||||
/**
|
||||
* Remove fully included variations.
|
||||
* E.g. If both {R} and {R}{W} are in this, then {R} will be removed.
|
||||
*/
|
||||
public void removeFullyIncludedVariations() {
|
||||
List<Mana> that = new ArrayList<>(this);
|
||||
Set<String> list = new HashSet<>();
|
||||
|
||||
for (int i = this.size() - 1; i >= 0; i--) {
|
||||
String s;
|
||||
if (this.get(i) instanceof ConditionalMana) {
|
||||
s = this.get(i).toString() + ((ConditionalMana) this.get(i)).getConditionString();
|
||||
if (that.get(i) instanceof ConditionalMana) {
|
||||
s = that.get(i).toString() + ((ConditionalMana) that.get(i)).getConditionString();
|
||||
} else {
|
||||
s = this.get(i).toString();
|
||||
s = that.get(i).toString();
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
this.remove(i);
|
||||
|
|
@ -590,18 +541,19 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
}
|
||||
|
||||
// Remove fully included variations
|
||||
// TODO: research too many manas and freeze (put 1 card to slow down, put 3 cards to freeze here)
|
||||
// battlefield:Human:Cascading Cataracts:1
|
||||
for (int i = this.size() - 1; i >= 0; i--) {
|
||||
for (int ii = 0; ii < i; ii++) {
|
||||
Mana moreValuable = Mana.getMoreValuableMana(this.get(i), this.get(ii));
|
||||
Mana moreValuable = Mana.getMoreValuableMana(that.get(i), that.get(ii));
|
||||
if (moreValuable != null) {
|
||||
this.get(ii).setToMana(moreValuable);
|
||||
this.remove(i);
|
||||
that.get(ii).setToMana(moreValuable);
|
||||
that.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.clear();
|
||||
this.addAll(that);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -648,4 +600,81 @@ public class ManaOptions extends ArrayList<Mana> {
|
|||
sb.append(',').append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get a Mana from ManaOptions at the specified position.
|
||||
* Since the implementation uses a LinkedHashSet the ordering of the items is preserved.
|
||||
*
|
||||
* NOTE: Do not use in tight loops as performance of the lookup is much worse than
|
||||
* for ArrayList (the previous superclass of ManaOptions).
|
||||
*/
|
||||
public Mana getAtIndex(int i) {
|
||||
if (i < 0 || i >= this.size()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
Iterator<Mana> itr = this.iterator();
|
||||
while(itr.hasNext()) {
|
||||
if (i == 0) {
|
||||
return itr.next();
|
||||
} else {
|
||||
itr.next(); // Ignore the value
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return null; // Not sure how we'd ever get here, but leave just in case since IDE complains.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* from: https://stackoverflow.com/a/35000727/7983747
|
||||
* @author Gili Tzabari
|
||||
*/
|
||||
final class Comparators
|
||||
{
|
||||
/**
|
||||
* Verify that a comparator is transitive.
|
||||
*
|
||||
* @param <T> the type being compared
|
||||
* @param comparator the comparator to test
|
||||
* @param elements the elements to test against
|
||||
* @throws AssertionError if the comparator is not transitive
|
||||
*/
|
||||
public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements) {
|
||||
for (T first: elements) {
|
||||
for (T second: elements) {
|
||||
int result1 = comparator.compare(first, second);
|
||||
int result2 = comparator.compare(second, first);
|
||||
if (result1 != -result2 && !(result1 == 0 && result1 == result2)) {
|
||||
// Uncomment the following line to step through the failed case
|
||||
comparator.compare(first, second);
|
||||
comparator.compare(second, first);
|
||||
throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
|
||||
" but swapping the parameters returns " + result2);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (T first: elements) {
|
||||
for (T second: elements) {
|
||||
int firstGreaterThanSecond = comparator.compare(first, second);
|
||||
if (firstGreaterThanSecond <= 0)
|
||||
continue;
|
||||
for (T third: elements) {
|
||||
int secondGreaterThanThird = comparator.compare(second, third);
|
||||
if (secondGreaterThanThird <= 0)
|
||||
continue;
|
||||
int firstGreaterThanThird = comparator.compare(first, third);
|
||||
if (firstGreaterThanThird <= 0) {
|
||||
// Uncomment the following line to step through the failed case
|
||||
comparator.compare(first, second);
|
||||
comparator.compare(second, third);
|
||||
comparator.compare(first, third);
|
||||
throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
|
||||
"compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
|
||||
firstGreaterThanThird);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private Comparators() {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue