forked from External/mage
701 lines
No EOL
29 KiB
Java
701 lines
No EOL
29 KiB
Java
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 org.apache.log4j.Logger;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* <p>
|
||
* 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
|
||
* combinations available to a player
|
||
* <p>
|
||
* 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).
|
||
*
|
||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||
*/
|
||
public class ManaOptions extends LinkedHashSet<Mana> {
|
||
|
||
private static final Logger logger = Logger.getLogger(ManaOptions.class);
|
||
|
||
public ManaOptions() {
|
||
}
|
||
|
||
protected ManaOptions(final ManaOptions options) {
|
||
for (Mana mana : options) {
|
||
this.add(mana.copy());
|
||
}
|
||
}
|
||
|
||
public void addMana(List<ActivatedManaAbilityImpl> abilities, Game game) {
|
||
if (isEmpty()) {
|
||
this.add(new Mana());
|
||
}
|
||
if (abilities.isEmpty()) {
|
||
return; // Do nothing
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void addManaVariation(List<Mana> netManas, ActivatedManaAbilityImpl ability, Game game) {
|
||
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)) {
|
||
newMana = mana.copy();
|
||
newMana.add(netMana);
|
||
this.add(newMana);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private static List<List<Mana>> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) {
|
||
Player player = game.getPlayer(ability.getControllerId());
|
||
List<List<Mana>> newList = new ArrayList<>();
|
||
if (player != null) {
|
||
newList.addAll(player.getAvailableTriggeredMana());
|
||
player.getAvailableTriggeredMana().clear();
|
||
}
|
||
return newList;
|
||
}
|
||
|
||
/**
|
||
* Generates triggered mana and checks replacement of Tapped_For_Mana event.
|
||
* Also generates triggered mana for MANA_ADDED event.
|
||
*
|
||
* @param ability
|
||
* @param game
|
||
* @param mana
|
||
* @return false if mana production was completely replaced
|
||
*/
|
||
private boolean checkManaReplacementAndTriggeredMana(Ability ability, Game game, Mana mana) {
|
||
if (ability.hasTapCost()) {
|
||
ManaEvent event = new TappedForManaEvent(ability.getSourceId(), ability, ability.getControllerId(), mana, game);
|
||
if (game.replaceEvent(event)) {
|
||
return false;
|
||
}
|
||
game.fireEvent(event);
|
||
}
|
||
ManaEvent manaEvent = new ManaEvent(GameEvent.EventType.MANA_ADDED, ability.getSourceId(), ability, ability.getControllerId(), mana);
|
||
manaEvent.setData(mana.toString());
|
||
game.fireEvent(manaEvent);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* This adds the mana the abilities can produce to the possible mana variation.
|
||
*
|
||
* @param abilities
|
||
* @param game
|
||
* @return false if the costs could not be paid
|
||
*/
|
||
public boolean addManaWithCost(List<ActivatedManaAbilityImpl> abilities, Game game) {
|
||
boolean wasUsable = false;
|
||
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.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
|
||
if (netManas.size() == 1) {
|
||
checkManaReplacementAndTriggeredMana(ability, game, netManas.get(0));
|
||
addMana(netManas.get(0));
|
||
addTriggeredMana(game, ability);
|
||
} else {
|
||
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 = mana.copy();
|
||
newMana.add(triggeredManaVariation);
|
||
this.add(newMana);
|
||
wasUsable = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {// The ability has mana costs
|
||
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 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(startingMana);
|
||
}
|
||
wasUsable = true;
|
||
} else {
|
||
// mana costs can't be paid so keep starting mana
|
||
add(startingMana);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
//perform a union of all existing options and the new options
|
||
List<Mana> copy = new ArrayList<>(this);
|
||
this.clear();
|
||
for (ActivatedManaAbilityImpl ability : abilities) {
|
||
List<Mana> netManas = ability.getNetMana(game);
|
||
if (ability.getManaCosts().isEmpty()) {
|
||
for (Mana netMana : netManas) {
|
||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||
for (Mana mana : copy) {
|
||
Mana newMana = mana.copy();
|
||
newMana.add(triggeredManaVariation);
|
||
this.add(newMana);
|
||
wasUsable = true;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
for (Mana netMana : netManas) {
|
||
checkManaReplacementAndTriggeredMana(ability, game, netMana);
|
||
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
|
||
for (Mana previousMana : copy) {
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
if (logger.isTraceEnabled() && this.size() > 30) {
|
||
logger.trace("ManaOptionsCosts " + this.size());
|
||
logger.trace("Abilities: " + abilities.toString());
|
||
}
|
||
|
||
return wasUsable;
|
||
}
|
||
|
||
public boolean addManaPoolDependant(List<ActivatedManaAbilityImpl> abilities, Game game) {
|
||
if (abilities.isEmpty() || abilities.size() != 1) {
|
||
return false;
|
||
}
|
||
|
||
boolean wasUsable = false;
|
||
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;
|
||
}
|
||
|
||
public static List<Mana> getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) {
|
||
List<Mana> baseManaPlusTriggeredMana = new ArrayList<>();
|
||
baseManaPlusTriggeredMana.add(baseMana);
|
||
List<List<Mana>> availableTriggeredManaList = ManaOptions.getSimulatedTriggeredManaFromPlayer(game, ability);
|
||
for (List<Mana> availableTriggeredMana : availableTriggeredManaList) {
|
||
if (availableTriggeredMana.size() == 1) {
|
||
for (Mana prevMana : baseManaPlusTriggeredMana) {
|
||
prevMana.add(availableTriggeredMana.get(0));
|
||
}
|
||
} else if (availableTriggeredMana.size() > 1) {
|
||
List<Mana> copy = new ArrayList<>(baseManaPlusTriggeredMana);
|
||
baseManaPlusTriggeredMana.clear();
|
||
for (Mana triggeredMana : availableTriggeredMana) {
|
||
for (Mana prevMana : copy) {
|
||
Mana newMana = new Mana();
|
||
newMana.add(prevMana);
|
||
newMana.add(triggeredMana);
|
||
baseManaPlusTriggeredMana.add(newMana);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return baseManaPlusTriggeredMana;
|
||
}
|
||
|
||
private void addTriggeredMana(Game game, Ability ability) {
|
||
List<List<Mana>> netManaList = getSimulatedTriggeredManaFromPlayer(game, ability);
|
||
for (List<Mana> triggeredNetMana : netManaList) {
|
||
if (triggeredNetMana.size() == 1) {
|
||
addMana(triggeredNetMana.get(0));
|
||
} else if (triggeredNetMana.size() > 1) {
|
||
// Add variations
|
||
List<Mana> copy = new ArrayList<>(this);
|
||
this.clear();
|
||
for (Mana triggeredMana : triggeredNetMana) {
|
||
for (Mana mana : copy) {
|
||
Mana newMana = mana.copy();
|
||
newMana.add(triggeredMana);
|
||
this.add(newMana);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Adds the given mana value to all existing options
|
||
*
|
||
* @param addMana Mana to add to the existing options
|
||
*/
|
||
public void addMana(Mana addMana) {
|
||
if (isEmpty()) {
|
||
this.add(new Mana());
|
||
}
|
||
if (addMana instanceof ConditionalMana) {
|
||
List<Mana> copy = new ArrayList<>(this);
|
||
this.clear();
|
||
for (Mana mana : copy) {
|
||
ConditionalMana condMana = ((ConditionalMana) addMana).copy();
|
||
condMana.add(mana);
|
||
add(condMana); // Add mana as option with condition
|
||
add(mana); // Add old mana without the condition
|
||
}
|
||
|
||
} else {
|
||
for (Mana mana : this) {
|
||
mana.add(addMana);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void addMana(ManaOptions options) {
|
||
if (isEmpty()) {
|
||
this.add(new 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.getAtIndex(0));
|
||
} else {
|
||
//perform a union of all existing options and the new options
|
||
List<Mana> copy = new ArrayList<>(this);
|
||
this.clear();
|
||
for (Mana addMana : options) {
|
||
for (Mana mana : copy) {
|
||
Mana newMana = mana.copy();
|
||
newMana.add(addMana);
|
||
this.add(newMana);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public ManaOptions copy() {
|
||
return new ManaOptions(this);
|
||
}
|
||
|
||
/**
|
||
* Performs the simulation of a mana ability with costs
|
||
*
|
||
* @param cost cost to use the ability
|
||
* @param manaToAdd one mana variation that can be added by using
|
||
* this ability
|
||
* @param onlyManaCosts flag to know if the costs are mana costs only (will try to use ability multiple times)
|
||
* @param startingMana the mana available before the usage of the
|
||
* ability
|
||
* @return true if the new complete mana does replace the current mana completely
|
||
*/
|
||
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 repeatable = manaToAdd != null // TODO: re-write "only replace to any with mana costs only will be repeated if able"
|
||
&& onlyManaCosts
|
||
&& manaToAdd.countColored() > 0;
|
||
boolean canHaveBetterValues;
|
||
int maxRepeat = manaAbility.getMaxMoreActivationsThisTurn(game);
|
||
|
||
Mana possibleMana = new Mana();
|
||
Mana improvedMana = new Mana();
|
||
|
||
// simulate multiple calls of mana abilities and replace mana pool by better values
|
||
// example: {G}: Add one mana of any color
|
||
for (Mana possiblePay : ManaOptions.getPossiblePayCombinations(cost, startingMana)) {
|
||
improvedMana.setToMana(startingMana);
|
||
int currentAttempt = 0;
|
||
do {
|
||
// loop until all mana replaced by better values
|
||
canHaveBetterValues = false;
|
||
currentAttempt++;
|
||
|
||
// it's impossible to analyse all payment order (pay {R} for {1}, {Any} for {G}, etc)
|
||
// so use simple cost simulation by subtract
|
||
possibleMana.setToMana(improvedMana);
|
||
possibleMana.subtract(possiblePay);
|
||
if (!possibleMana.isValid()) {
|
||
//if (possibleMana.canPayMana(possiblePay)) {
|
||
// TODO: canPayMana/includesMana uses better pay logic, so subtract can be improved somehow
|
||
//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;
|
||
}
|
||
|
||
// found new combination - add it to final options
|
||
this.add(possibleMana.copy());
|
||
canHaveBetterValues = true;
|
||
|
||
// remove old worse options
|
||
if (possibleMana.isMoreValuableThan(improvedMana)) {
|
||
oldManaWasReplaced = true;
|
||
if (!startingMana.equalManaValue(improvedMana)) {
|
||
this.removeEqualMana(improvedMana);
|
||
}
|
||
}
|
||
improvedMana.setToMana(possibleMana);
|
||
}
|
||
} while (repeatable && (currentAttempt < maxRepeat) && canHaveBetterValues && improvedMana.includesMana(possiblePay));
|
||
}
|
||
return oldManaWasReplaced;
|
||
}
|
||
|
||
/**
|
||
* @param manaCost
|
||
* @param manaAvailable
|
||
* @return
|
||
*/
|
||
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) {
|
||
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<>(payCombinations.size());
|
||
if (i > 0) {
|
||
existingManas.addAll(payCombinations);
|
||
payCombinations.clear();
|
||
} else {
|
||
existingManas.add(new Mana());
|
||
}
|
||
|
||
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
|
||
// 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();
|
||
}
|
||
}
|
||
}
|
||
|
||
for (Mana mana : payCombinations) {
|
||
mana.add(fixedMana);
|
||
}
|
||
// All mana values in here are of length 5
|
||
return payCombinations;
|
||
}
|
||
|
||
public boolean removeEqualMana(Mana manaToRemove) {
|
||
boolean result = false;
|
||
for (Iterator<Mana> iterator = this.iterator(); iterator.hasNext(); ) {
|
||
Mana next = iterator.next();
|
||
if (next.equalManaValue(manaToRemove)) {
|
||
iterator.remove();
|
||
result = true;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 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 (that.get(i) instanceof ConditionalMana) {
|
||
s = that.get(i).toString() + ((ConditionalMana) that.get(i)).getConditionString();
|
||
} else {
|
||
s = that.get(i).toString();
|
||
}
|
||
if (s.isEmpty()) {
|
||
this.remove(i);
|
||
} else if (list.contains(s)) {
|
||
// remove duplicated
|
||
this.remove(i);
|
||
} else {
|
||
list.add(s);
|
||
}
|
||
}
|
||
|
||
// Remove fully included variations
|
||
for (int i = this.size() - 1; i >= 0; i--) {
|
||
for (int ii = 0; ii < i; ii++) {
|
||
Mana moreValuable = Mana.getMoreValuableMana(that.get(i), that.get(ii));
|
||
if (moreValuable != null) {
|
||
that.get(ii).setToMana(moreValuable);
|
||
that.remove(i);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
this.clear();
|
||
this.addAll(that);
|
||
}
|
||
|
||
/**
|
||
* Checks if the given mana (cost) is already included in one available mana
|
||
* option
|
||
*
|
||
* @param mana
|
||
* @return
|
||
*/
|
||
public boolean enough(Mana mana) {
|
||
// 117.5. Some costs are represented by {0}, or are reduced to {0}. The action necessary for a player to pay
|
||
// such a cost is the player’s acknowledgment that he or she is paying it. Even though such a cost requires
|
||
// no resources, it’s not automatically paid.
|
||
if (mana.count() == 0) {
|
||
return true;
|
||
}
|
||
|
||
for (Mana avail : this) {
|
||
if (mana.enough(avail)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
Iterator<Mana> it = this.iterator();
|
||
if (!it.hasNext()) {
|
||
return "[]";
|
||
}
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append('[');
|
||
for (; ; ) {
|
||
Mana mana = it.next();
|
||
sb.append(mana.toString());
|
||
if (mana instanceof ConditionalMana) {
|
||
sb.append(((ConditionalMana) mana).getConditionString());
|
||
}
|
||
if (!it.hasNext()) {
|
||
return sb.append(']').toString();
|
||
}
|
||
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.
|
||
* <p>
|
||
* 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() {
|
||
}
|
||
} |