Refactor: extract card names compare logic (is empty name, is same name)

Fixed last broken tests
This commit is contained in:
Oleg Agafonov 2018-12-07 00:26:50 +04:00
parent 96187ad3c0
commit 02b7e2cf10
63 changed files with 614 additions and 466 deletions

View file

@ -1,5 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -11,9 +9,9 @@ import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class DestroyAllNamedPermanentsEffect extends OneShotEffect {
@ -38,12 +36,12 @@ public class DestroyAllNamedPermanentsEffect extends OneShotEffect {
return false;
}
FilterPermanent filter = new FilterPermanent();
if (targetPermanent.getName().isEmpty()) {
if (CardUtil.haveEmptyName(targetPermanent)) {
filter.add(new PermanentIdPredicate(targetPermanent.getId())); // if no name (face down creature) only the creature itself is selected
} else {
filter.add(new NamePredicate(targetPermanent.getName()));
}
for (Permanent perm: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
perm.destroy(source.getSourceId(), game, false);
}
return true;

View file

@ -327,7 +327,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return spellAbility;
}
// @Override
// @Override
// public void adjustCosts(Ability ability, Game game) {
// }
@Override
@ -720,7 +720,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public String getLogName() {
if (name.isEmpty()) {
return GameLog.getNeutralColoredText("face down card");
return GameLog.getNeutralColoredText(EmptyNames.FACE_DOWN_CREATURE.toString());
} else {
return GameLog.getColoredObjectIdName(this);
}

View file

@ -0,0 +1,24 @@
package mage.constants;
/**
*
* @author JayDi85
*/
public enum EmptyNames {
// TODO: make names for that cards and enable Assert.assertNotEquals("", permanentName); for assertXXX tests
// TODO: replace all getName().equals to haveSameNames and haveEmptyName
FACE_DOWN_CREATURE(""), // "Face down creature"
FACE_DOWN_TOKEN(""); // "Face down token"
private final String cardName;
EmptyNames(String cardName) {
this.cardName = cardName;
}
@Override
public String toString() {
return cardName;
}
}

View file

@ -1,4 +1,3 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
@ -7,17 +6,23 @@ import mage.constants.SpellAbilityType;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.util.CardUtil;
/**
*
* @author North
*/
public class NamePredicate implements Predicate<MageObject> {
private final String name;
private final Boolean ignoreMtgRuleForEmptyNames; // NamePredicate uses at test and checks, it's must ignore that rules (empty names is not equals in mtg)
public NamePredicate(String name) {
this(name, false);
}
public NamePredicate(String name, Boolean ignoreMtgRuleForEmptyNames) {
this.name = name;
this.ignoreMtgRuleForEmptyNames = ignoreMtgRuleForEmptyNames;
}
@Override
@ -25,17 +30,20 @@ public class NamePredicate implements Predicate<MageObject> {
// If a player names a card, the player may name either half of a split card, but not both.
// A split card has the chosen name if one of its two names matches the chosen name.
if (input instanceof SplitCard) {
return name.equals(((SplitCard)input).getLeftHalfCard().getName()) || name.equals(((SplitCard)input).getRightHalfCard().getName());
} else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED){
SplitCard card = (SplitCard) ((Spell)input).getCard();
return name.equals(card.getLeftHalfCard().getName()) || name.equals(card.getRightHalfCard().getName());
return CardUtil.haveSameNames(name, ((SplitCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, ((SplitCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames);
} else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard card = (SplitCard) ((Spell) input).getCard();
return CardUtil.haveSameNames(name, card.getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, card.getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames);
} else {
if (name.contains(" // ")) {
String leftName = name.substring(0, name.indexOf(" // "));
String rightName = name.substring(name.indexOf(" // ") + 4, name.length());
return leftName.equals(input.getName()) || rightName.equals(input.getName());
return CardUtil.haveSameNames(leftName, input.getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(rightName, input.getName(), this.ignoreMtgRuleForEmptyNames);
} else {
return name.equals(input.getName());
return CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
}
}
}

View file

@ -1,7 +1,5 @@
package mage.game.command.emblems;
import java.util.List;
import mage.abilities.Ability;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.costs.common.DiscardCardCost;
@ -13,19 +11,16 @@ import mage.cards.Sets;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.constants.*;
import mage.game.Game;
import mage.game.command.Emblem;
import mage.game.permanent.token.EmptyToken;
import mage.util.CardUtil;
import mage.util.RandomUtil;
import java.util.List;
/**
*
* @author spjspj
*/
public final class MomirEmblem extends Emblem {
@ -70,7 +65,7 @@ class MomirEffect extends OneShotEffect {
return false;
}
EmptyToken token = new EmptyToken(); // search for a non custom set creature
while (token.getName().isEmpty() && !options.isEmpty()) {
while (!options.isEmpty()) {
int index = RandomUtil.nextInt(options.size());
ExpansionSet expansionSet = Sets.findSet(options.get(index).getSetCode());
if (expansionSet == null || expansionSet.getSetType() == SetType.CUSTOM_SET) {
@ -79,6 +74,7 @@ class MomirEffect extends OneShotEffect {
Card card = options.get(index).getCard();
if (card != null) {
CardUtil.copyTo(token).from(card);
break;
} else {
options.remove(index);
}

View file

@ -1,4 +1,3 @@
package mage.game.permanent;
import mage.MageObject;
@ -50,6 +49,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.counter = counter;
this.sourceObject = sourceObject;
}
Counter counter;
MageObject sourceObject;
}
@ -164,7 +164,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public String toString() {
StringBuilder sb = threadLocalBuilder.get();
sb.append(this.name).append('-').append(this.expansionSetCode);
sb.append(this.getName()).append('-').append(this.expansionSetCode);
if (copy) {
sb.append(" [Copy]");
}
@ -195,10 +195,23 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
}
@Override
public String getName() {
if (name.isEmpty()) {
if (faceDown) {
return EmptyNames.FACE_DOWN_CREATURE.toString();
} else {
return "";
}
} else {
return name;
}
}
@Override
public String getValue(GameState state) {
StringBuilder sb = threadLocalBuilder.get();
sb.append(controllerId).append(name).append(tapped).append(damage);
sb.append(controllerId).append(getName()).append(tapped).append(damage);
sb.append(subtype).append(supertype).append(power.getValue()).append(toughness.getValue());
sb.append(abilities.getValue());
for (Counter counter : getCounters(state).values()) {
@ -245,7 +258,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
/**
*
* @param ability
* @param game
*/
@ -665,7 +677,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.attachedTo = attachToObjectId;
this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(attachToObjectId);
for (Ability ability : this.getAbilities()) {
for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) {
for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext(); ) {
ContinuousEffect effect = (ContinuousEffect) ite.next();
game.getContinuousEffects().setOrder(effect);
// It's important to update the timestamp of the copied effect in ContinuousEffects because it does the action
@ -715,8 +727,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
* @param game
* @param preventable
* @param combat
* @param markDamage If true, damage will be dealt later in applyDamage
* method
* @param markDamage If true, damage will be dealt later in applyDamage
* method
* @return
*/
private int damage(int damageAmount, UUID sourceId, Game game, boolean preventable, boolean combat, boolean markDamage, List<UUID> appliedEffects) {
@ -1441,20 +1453,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
//If an object leaves the zone it's in, all attached permanents become unattached
//note that this code doesn't actually detach anything, and is a bit of a bandaid
public void detachAllAttachments(Game game) {
for(UUID attachmentId : getAttachments()) {
for (UUID attachmentId : getAttachments()) {
Permanent attachment = game.getPermanent(attachmentId);
Card attachmentCard = game.getCard(attachmentId);
if(attachment != null && attachmentCard != null) {
if (attachment != null && attachmentCard != null) {
//make bestow cards and licids into creatures
//aura test to stop bludgeon brawl shenanigans from using this code
//consider adding code to handle that case?
if(attachment.hasSubtype(SubType.AURA, game) && attachmentCard.isCreature()) {
if (attachment.hasSubtype(SubType.AURA, game) && attachmentCard.isCreature()) {
BestowAbility.becomeCreature(attachment, game);
}
}
}
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
Zone fromZone = game.getState().getZone(objectId);
@ -1480,7 +1492,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
Zone fromZone = game.getState().getZone(objectId);
ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects);
ZoneChangeInfo.Exile info = new ZoneChangeInfo.Exile(event, exileId, name);
boolean successfullyMoved = ZonesHandler.moveCard(info, game);
//20180810 - 701.3d
detachAllAttachments(game);

View file

@ -1,15 +1,15 @@
package mage.game.permanent;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.mana.ManaCost;
import mage.constants.EmptyNames;
import mage.game.Game;
import mage.game.permanent.token.Token;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class PermanentToken extends PermanentImpl {
@ -42,6 +42,15 @@ public class PermanentToken extends PermanentImpl {
this.toughness.resetToBaseValue();
}
@Override
public String getName() {
if (name.isEmpty()) {
return EmptyNames.FACE_DOWN_TOKEN.toString();
} else {
return name;
}
}
private void copyFromToken(Token token, Game game, boolean reset) {
this.name = token.getName();
this.abilities.clear();

View file

@ -1,9 +1,5 @@
package mage.util;
import java.util.UUID;
import java.util.stream.Stream;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
@ -12,11 +8,14 @@ import mage.abilities.SpellAbility;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.*;
import mage.cards.Card;
import mage.constants.EmptyNames;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token;
import mage.util.functions.CopyTokenFunction;
import java.util.UUID;
/**
* @author nantuko
*/
@ -25,10 +24,10 @@ public final class CardUtil {
private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
static final String[] numberStrings = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
static final String[] ordinalStrings = {"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eightth", "ninth",
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
/**
* Increase spell or ability cost to be paid.
@ -129,8 +128,8 @@ public final class CardUtil {
*
* @param spellAbility
* @param manaCostsToReduce costs to reduce
* @param convertToGeneric colored mana does reduce generic mana if no
* appropriate colored mana is in the costs included
* @param convertToGeneric colored mana does reduce generic mana if no
* appropriate colored mana is in the costs included
*/
public static void adjustCost(SpellAbility spellAbility, ManaCosts<ManaCost> manaCostsToReduce, boolean convertToGeneric) {
ManaCosts<ManaCost> previousCost = spellAbility.getManaCostsToPay();
@ -315,7 +314,7 @@ public final class CardUtil {
*
* @param number number to convert to text
* @param forOne if the number is 1, this string will be returnedinstead of
* "one".
* "one".
* @return
*/
public static String numberToText(int number, String forOne) {
@ -346,7 +345,7 @@ public final class CardUtil {
if (number >= 1 && number < 21) {
return ordinalStrings[number - 1];
}
return Integer.toString(number) + "th";
return number + "th";
}
public static String replaceSourceName(String message, String sourceName) {
@ -388,7 +387,7 @@ public final class CardUtil {
/**
* Creates and saves a (card + zoneChangeCounter) specific exileId.
*
* @param game the current game
* @param game the current game
* @param source source ability
* @return the specific UUID
*/
@ -423,9 +422,9 @@ public final class CardUtil {
* be specific to a permanent instance. So they won't match, if a permanent
* was e.g. exiled and came back immediately.
*
* @param text short value to describe the value
* @param text short value to describe the value
* @param cardId id of the card
* @param game the game
* @param game the game
* @return
*/
public static String getCardZoneString(String text, UUID cardId, Game game) {
@ -525,9 +524,39 @@ public final class CardUtil {
title = textSuffix == null ? "" : textSuffix;
}
} else {
title = textSuffix == null ? "" : textSuffix;;
title = textSuffix == null ? "" : textSuffix;
;
}
return title;
}
/**
* Face down cards and their copy tokens don't have names and that's "empty" names is not equals
*/
public static boolean haveSameNames(String name1, String name2, Boolean ignoreMtgRuleForEmptyNames) {
if (ignoreMtgRuleForEmptyNames) {
// simple compare for tests and engine
return name1 != null && name2 != null && name1.equals(name2);
} else {
// mtg logic compare for game (empty names can't be same)
return !haveEmptyName(name1) && !haveEmptyName(name2) && name1.equals(name2);
}
}
public static boolean haveSameNames(String name1, String name2) {
return haveSameNames(name1, name2, false);
}
public static boolean haveSameNames(MageObject object1, MageObject object2) {
return object1 != null && object2 != null && haveSameNames(object1.getName(), object2.getName());
}
public static boolean haveEmptyName(String name) {
return name == null || name.isEmpty() || name.equals(EmptyNames.FACE_DOWN_CREATURE.toString()) || name.equals(EmptyNames.FACE_DOWN_TOKEN.toString());
}
public static boolean haveEmptyName(MageObject object) {
return object == null || haveEmptyName(object.getName());
}
}