@Override public void resolve(SpellAbility sa) { CardCollectionView tgtCards; final Game game = sa.getActivatingPlayer().getGame(); final Card source = sa.getHostCard(); final boolean phaseInOrOut = sa.hasParam("PhaseInOrOutAll"); if (sa.hasParam("AllValid")) { if (phaseInOrOut) { tgtCards = game.getCardsIncludePhasingIn(ZoneType.Battlefield); } else { tgtCards = game.getCardsIn(ZoneType.Battlefield); } tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa); } else if (sa.hasParam("Defined")) { tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); } else { tgtCards = getTargetCards(sa); } if (phaseInOrOut) { // Time and Tide for (final Card tgtC : tgtCards) { tgtC.phase(); } } else { // just phase out for (final Card tgtC : tgtCards) { if (!tgtC.isPhasedOut()) { tgtC.phase(); } } } }
private boolean phasesUnpreferredTargeting( final Game game, final SpellAbility sa, final boolean mandatory) { final Card source = sa.getHostCard(); final TargetRestrictions tgt = sa.getTargetRestrictions(); CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); list = CardLists.getTargetableCards( CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source), sa); return false; }
// returns a List<Card> that is a subset of list with cards that share a name // with a permanent on the battlefield private static CardCollection sharesNameWithCardOnBattlefield( final Game game, final List<Card> list) { final CardCollection toReturn = new CardCollection(); final CardCollectionView play = game.getCardsIn(ZoneType.Battlefield); for (final Card c : list) { for (final Card p : play) { if (p.getName().equals(c.getName()) && !toReturn.contains(c)) { toReturn.add(c); } } } return toReturn; }
public boolean checkOtherRestrictions() { final Card source = this.getHostCard(); Player activator = getActivatingPlayer(); final Game game = activator.getGame(); // CantBeCast static abilities final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))); allp.add(source); for (final Card ca : allp) { final FCollectionView<StaticAbility> staticAbilities = ca.getStaticAbilities(); for (final StaticAbility stAb : staticAbilities) { if (stAb.applyAbility("CantBeCast", source, activator)) { return false; } } } return true; }
@Override public PaymentDecision visit(CostPutCardToLib cost) { Integer c = cost.convertAmount(); final Game game = player.getGame(); List<Card> chosen = new ArrayList<Card>(); List<Card> list; if (cost.isSameZone()) { list = new ArrayList<Card>(game.getCardsIn(cost.getFrom())); } else { list = new ArrayList<Card>(player.getCardsIn(cost.getFrom())); } if (c == null) { final String sVar = ability.getSVar(cost.getAmount()); // Generalize cost if (sVar.equals("XChoice")) { return null; } c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); } list = CardLists.getValidCards(list, cost.getType().split(";"), player, source); if (cost.isSameZone()) { // Jotun Grunt // TODO: improve AI final List<Player> players = game.getPlayers(); for (Player p : players) { List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p)); if (enoughType.size() >= c) { chosen.addAll(enoughType); break; } } chosen = chosen.subList(0, c); } else { chosen = ComputerUtil.choosePutToLibraryFrom( player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c); } return chosen.isEmpty() ? null : PaymentDecision.card(chosen); }
@Override public void resolve(SpellAbility sa) { final Card host = sa.getHostCard(); final Player player = sa.getActivatingPlayer(); final Game game = player.getGame(); // make list of creatures that controller has on Battlefield CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); choices = CardLists.getValidCards(choices, "Creature.YouCtrl", host.getController(), host); // if no creatures on battlefield, cannot encoded if (choices.isEmpty()) { return; } // Handle choice of whether or not to encoded final StringBuilder sb = new StringBuilder(); sb.append("Do you want to exile " + host + " and encode it onto a creature you control?"); if (!player.getController().confirmAction(sa, null, sb.toString())) { return; } // move host card to exile Card movedCard = game.getAction().moveTo(ZoneType.Exile, host); // choose a creature Card choice = player .getController() .chooseSingleEntityForEffect( choices, sa, "Choose a creature you control to encode ", true); if (choice == null) { return; } StringBuilder codeLog = new StringBuilder(); codeLog.append("Encoding ").append(host.toString()).append(" to ").append(choice.toString()); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, codeLog.toString()); // store hostcard in encoded array choice.addEncodedCard(movedCard); // add trigger final int numEncoded = choice.getEncodedCards().size(); final StringBuilder cipherTrigger = new StringBuilder(); cipherTrigger .append( "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ PlayEncoded") .append(numEncoded); cipherTrigger.append(" | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ "); cipherTrigger.append( "Whenever CARDNAME deals combat damage to a player, its controller may cast a copy of "); cipherTrigger.append(movedCard).append(" without paying its mana cost."); final String abName = "PlayEncoded" + numEncoded; final String abString = "AB$ Play | Cost$ 0 | Encoded$ " + numEncoded + " | WithoutManaCost$ True | CopyCard$ True"; final Trigger parsedTrigger = TriggerHandler.parseTrigger(cipherTrigger.toString(), choice, false); choice.addTrigger(parsedTrigger); choice.setSVar(abName, abString); return; }
private boolean pumpMandatoryTarget( final Player ai, final SpellAbility sa, final boolean mandatory) { final Game game = ai.getGame(); List<Card> list = game.getCardsIn(ZoneType.Battlefield); final TargetRestrictions tgt = sa.getTargetRestrictions(); final Player opp = ai.getOpponent(); list = CardLists.getValidCards( list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard()); list = CardLists.getTargetableCards(list, sa); if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) { sa.resetTargets(); return false; } // Remove anything that's already been targeted for (final Card c : sa.getTargets().getTargetCards()) { list.remove(c); } List<Card> pref; List<Card> forced; final Card source = sa.getHostCard(); if (sa.isCurse()) { pref = CardLists.filterControlledBy(list, opp); forced = CardLists.filterControlledBy(list, ai); } else { pref = CardLists.filterControlledBy(list, ai); forced = CardLists.filterControlledBy(list, opp); } while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) { if (pref.isEmpty()) { break; } Card c; if (CardLists.getNotType(pref, "Creature").isEmpty()) { c = ComputerUtilCard.getBestCreatureAI(pref); } else { c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true); } pref.remove(c); sa.getTargets().add(c); } while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) { if (forced.isEmpty()) { break; } Card c; if (CardLists.getNotType(forced, "Creature").isEmpty()) { c = ComputerUtilCard.getWorstCreatureAI(forced); } else { c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true); } forced.remove(c); sa.getTargets().add(c); } if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) { sa.resetTargets(); return false; } return true; } // pumpMandatoryTarget()
private boolean pumpTgtAI( final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory) { final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>(); final Game game = ai.getGame(); final Card source = sa.getHostCard(); if (!mandatory && !sa.isTrigger() && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !(sa.isCurse() && defense < 0) && !this.containsNonCombatKeyword(keywords) && !sa.hasParam("UntilYourNextTurn")) { return false; } final Player opp = ai.getOpponent(); final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); sa.setTargetingPlayer(targetingPlayer); return targetingPlayer.getController().chooseTargetsFor(sa); } List<Card> list = new ArrayList<Card>(); if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("HighestPower")) { list = CardLists.getValidCards( CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source); list = CardLists.getTargetableCards(list, sa); CardLists.sortByPowerDesc(list); if (!list.isEmpty()) { sa.getTargets().add(list.get(0)); return true; } else { return false; } } if (sa.getParam("AILogic").equals("Fight") || sa.getParam("AILogic").equals("PowerDmg")) { final AbilitySub tgtFight = sa.getSubAbility(); List<Card> aiCreatures = ai.getCreaturesInPlay(); aiCreatures = CardLists.getTargetableCards(aiCreatures, sa); aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures); ComputerUtilCard.sortByEvaluateCreature(aiCreatures); // sort is suboptimal due to conflicting needs depending on game state: // -deathtouch for deal damage // -max power for damage to player // -survivability for generic "fight" // -no support for "heroic" List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay(); humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight); ComputerUtilCard.sortByEvaluateCreature(humCreatures); if (humCreatures.isEmpty() || aiCreatures.isEmpty()) { return false; } int buffedAtk = attack, buffedDef = defense; for (Card humanCreature : humCreatures) { for (Card aiCreature : aiCreatures) { if (sa.isSpell()) { // heroic triggers adding counters for (Trigger t : aiCreature.getTriggers()) { if (t.getMode() == TriggerType.SpellCast) { final Map<String, String> params = t.getMapParams(); if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer")) && params.containsKey("Execute")) { SpellAbility heroic = AbilityFactory.getAbility( aiCreature.getSVar(params.get("Execute")), aiCreature); if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { int amount = AbilityUtils.calculateAmount( aiCreature, heroic.getParam("CounterNum"), heroic); buffedAtk += amount; buffedDef += amount; } break; } } } } if (sa.getParam("AILogic").equals("PowerDmg")) { if (FightAi.canKill(aiCreature, humanCreature, buffedAtk)) { sa.getTargets().add(aiCreature); tgtFight.getTargets().add(humanCreature); return true; } } else { if (FightAi.shouldFight(aiCreature, humanCreature, buffedAtk, buffedDef)) { sa.getTargets().add(aiCreature); tgtFight.getTargets().add(humanCreature); return true; } } } } } return false; } else if (sa.isCurse()) { if (sa.canTarget(opp)) { sa.getTargets().add(opp); return true; } list = this.getCurseCreatures(ai, sa, defense, attack, keywords); } else { if (!tgt.canTgtCreature()) { ZoneType zone = tgt.getZone().get(0); list = game.getCardsIn(zone); } else { list = this.getPumpCreatures(ai, sa, defense, attack, keywords); } if (sa.canTarget(ai)) { sa.getTargets().add(ai); return true; } } list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source); if (game.getStack().isEmpty()) { // If the cost is tapping, don't activate before declare // attack/block if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) { if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && game.getPhaseHandler().isPlayerTurn(ai)) { list.remove(sa.getHostCard()); } if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && game.getPhaseHandler().isPlayerTurn(opp)) { list.remove(sa.getHostCard()); } } } if (list.isEmpty()) { return mandatory && this.pumpMandatoryTarget(ai, sa, mandatory); } if (!sa.isCurse()) { // Don't target cards that will die. list = ComputerUtil.getSafeTargets(ai, sa, list); } while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) { Card t = null; // boolean goodt = false; if (list.isEmpty()) { if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || (sa.getTargets().getNumTargeted() == 0)) { if (mandatory) { return this.pumpMandatoryTarget(ai, sa, mandatory); } sa.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } t = ComputerUtilCard.getBestAI(list); // option to hold removal instead only applies for single targeted removal if (!sa.isTrigger() && tgt.getMaxTargets(source, sa) == 1 && sa.isCurse()) { if (!ComputerUtilCard.useRemovalNow(sa, t, -defense, ZoneType.Graveyard)) { return false; } } sa.getTargets().add(t); list.remove(t); } return true; } // pumpTgtAI()
@Override public void resolve(final SpellAbility sa) { final Card host = sa.getHostCard(); final Map<String, String> svars = host.getSVars(); // AF specific sa int power = -1; if (sa.hasParam("Power")) { power = AbilityUtils.calculateAmount(host, sa.getParam("Power"), sa); } int toughness = -1; if (sa.hasParam("Toughness")) { toughness = AbilityUtils.calculateAmount(host, sa.getParam("Toughness"), sa); } final Game game = sa.getActivatingPlayer().getGame(); // Every Animate event needs a unique time stamp final long timestamp = game.getNextTimestamp(); final boolean permanent = sa.hasParam("Permanent"); final CardType types = new CardType(); if (sa.hasParam("Types")) { types.addAll(Arrays.asList(sa.getParam("Types").split(","))); } final CardType removeTypes = new CardType(); if (sa.hasParam("RemoveTypes")) { removeTypes.addAll(Arrays.asList(sa.getParam("RemoveTypes").split(","))); } // allow ChosenType - overrides anything else specified if (types.hasSubtype("ChosenType")) { types.clear(); types.add(host.getChosenType()); } final List<String> keywords = new ArrayList<String>(); if (sa.hasParam("Keywords")) { keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); } final List<String> removeKeywords = new ArrayList<String>(); if (sa.hasParam("RemoveKeywords")) { removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & "))); } final List<String> hiddenKeywords = new ArrayList<String>(); if (sa.hasParam("HiddenKeywords")) { hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & "))); } // allow SVar substitution for keywords for (int i = 0; i < keywords.size(); i++) { final String k = keywords.get(i); if (svars.containsKey(k)) { keywords.add(svars.get(k)); keywords.remove(k); } } // colors to be added or changed to String tmpDesc = ""; if (sa.hasParam("Colors")) { final String colors = sa.getParam("Colors"); if (colors.equals("ChosenColor")) { tmpDesc = CardUtil.getShortColorsString(host.getChosenColors()); } else { tmpDesc = CardUtil.getShortColorsString(new ArrayList<String>(Arrays.asList(colors.split(",")))); } } final String finalDesc = tmpDesc; // abilities to add to the animated being final List<String> abilities = new ArrayList<String>(); if (sa.hasParam("Abilities")) { abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(","))); } // replacement effects to add to the animated being final List<String> replacements = new ArrayList<String>(); if (sa.hasParam("Replacements")) { replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(","))); } // triggers to add to the animated being final List<String> triggers = new ArrayList<String>(); if (sa.hasParam("Triggers")) { triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(","))); } // sVars to add to the animated being final List<String> sVars = new ArrayList<String>(); if (sa.hasParam("sVars")) { sVars.addAll(Arrays.asList(sa.getParam("sVars").split(","))); } String valid = ""; if (sa.hasParam("ValidCards")) { valid = sa.getParam("ValidCards"); } CardCollectionView list; List<Player> tgtPlayers = getTargetPlayers(sa); if (!sa.usesTargeting() && !sa.hasParam("Defined")) { list = game.getCardsIn(ZoneType.Battlefield); } else { list = tgtPlayers.get(0).getCardsIn(ZoneType.Battlefield); } list = CardLists.getValidCards(list, valid.split(","), host.getController(), host); for (final Card c : list) { doAnimate( c, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp); // give abilities final List<SpellAbility> addedAbilities = new ArrayList<SpellAbility>(); if (abilities.size() > 0) { for (final String s : abilities) { final String actualAbility = host.getSVar(s); final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); addedAbilities.add(grantedAbility); c.addSpellAbility(grantedAbility); } } // remove abilities final List<SpellAbility> removedAbilities = new ArrayList<SpellAbility>(); if (sa.hasParam("OverwriteAbilities") || sa.hasParam("RemoveAllAbilities")) { for (final SpellAbility ab : c.getSpellAbilities()) { if (ab.isAbility()) { c.removeSpellAbility(ab); removedAbilities.add(ab); } } } // give replacement effects final List<ReplacementEffect> addedReplacements = new ArrayList<ReplacementEffect>(); if (replacements.size() > 0) { for (final String s : replacements) { final String actualReplacement = host.getSVar(s); final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, c, false); addedReplacements.add(c.addReplacementEffect(parsedReplacement)); } } // Grant triggers final List<Trigger> addedTriggers = new ArrayList<Trigger>(); if (triggers.size() > 0) { for (final String s : triggers) { final String actualTrigger = host.getSVar(s); final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, false); addedTriggers.add(c.addTrigger(parsedTrigger)); } } // suppress triggers from the animated card final List<Trigger> removedTriggers = new ArrayList<Trigger>(); if (sa.hasParam("OverwriteTriggers") || sa.hasParam("RemoveAllAbilities")) { final FCollectionView<Trigger> triggersToRemove = c.getTriggers(); for (final Trigger trigger : triggersToRemove) { trigger.setSuppressed(true); removedTriggers.add(trigger); } } // suppress static abilities from the animated card final List<StaticAbility> removedStatics = new ArrayList<StaticAbility>(); if (sa.hasParam("OverwriteStatics") || sa.hasParam("RemoveAllAbilities")) { final FCollectionView<StaticAbility> staticsToRemove = c.getStaticAbilities(); for (final StaticAbility stAb : staticsToRemove) { stAb.setTemporarilySuppressed(true); removedStatics.add(stAb); } } // suppress static abilities from the animated card final List<ReplacementEffect> removedReplacements = new ArrayList<ReplacementEffect>(); if (sa.hasParam("OverwriteReplacements") || sa.hasParam("RemoveAllAbilities")) { final FCollectionView<ReplacementEffect> replacementsToRemove = c.getReplacementEffects(); for (final ReplacementEffect re : replacementsToRemove) { re.setTemporarilySuppressed(true); removedReplacements.add(re); } } // give sVars if (sVars.size() > 0) { for (final String s : sVars) { final String actualsVar = host.getSVar(s); c.setSVar(s, actualsVar); } } game.fireEvent(new GameEventCardStatsChanged(c)); final GameCommand unanimate = new GameCommand() { private static final long serialVersionUID = -5861759814760561373L; @Override public void run() { doUnanimate( c, sa, finalDesc, hiddenKeywords, addedAbilities, addedTriggers, addedReplacements, false, removedAbilities, timestamp); // give back suppressed triggers for (final Trigger t : removedTriggers) { t.setSuppressed(false); } // give back suppressed static abilities for (final StaticAbility s : removedStatics) { s.setTemporarilySuppressed(false); } // give back suppressed replacement effects for (final ReplacementEffect re : removedReplacements) { re.setTemporarilySuppressed(false); } game.fireEvent(new GameEventCardStatsChanged(c)); } }; if (!permanent) { if (sa.hasParam("UntilEndOfCombat")) { game.getEndOfCombat().addUntil(unanimate); } else { game.getEndOfTurn().addUntil(unanimate); } } } } // animateAllResolve
/* (non-Javadoc) * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) */ @Override protected boolean canPlayAI(final Player ai, SpellAbility sa) { // TODO: AI Support! Currently this is copied from AF ChooseCard. // When implementing AI, I believe AI also needs to be made aware of the damage sources // chosen // to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any // damage // to the player because a CoP was pre-activated on it - unless, of course, there's // another // possible reason to attack with that creature). final Card host = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); final Card source = sa.getHostCard(); if (abCost != null) { // AI currently disabled for these costs if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) { return false; } if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { return false; } if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) { return false; } if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { return false; } } final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { sa.resetTargets(); if (sa.canTarget(ai.getOpponent())) { sa.getTargets().add(ai.getOpponent()); } else { return false; } } if (sa.hasParam("AILogic")) { final Game game = ai.getGame(); if (sa.getParam("AILogic").equals("NeedsPrevention")) { if (!game.getStack().isEmpty()) { final SpellAbility topStack = game.getStack().peekAbility(); if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) { return false; } final ApiType threatApi = topStack.getApi(); if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) { return false; } final Card threatSource = topStack.getHostCard(); List<? extends GameObject> objects = getTargets(topStack); if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) { objects = AbilityUtils.getDefinedPlayers( threatSource, topStack.getParam("ValidPlayers"), topStack); } if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) { return false; } int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack); if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) { return false; } return true; } if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) { return false; } CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); if (sa.hasParam("Choices")) { choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); } final Combat combat = game.getCombat(); choices = CardLists.filter( choices, new Predicate<Card>() { @Override public boolean apply(final Card c) { if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0; } }); if (choices.isEmpty()) { return false; } } } return true; }