Miscallaneous Cleanup Part 2 - Lambdas and Method References (#5737)

* Cleanup - Unnecessary Boxing

* Cleanup - Unnecessary Unboxing

* Cleanup - For-Each Loops

* Cleanup - `indexOf != -1` -> `contains`

* Cleanup - Merge identical catch blocks

* Cleanup - Try-with-resources

* Cleanup - System.lineSeparator

* Cleanup - Reference types to primitives
Some loops over Integers were switched to IntStreams to hopefully cut down on overall boxing.

* Cleanup - Manually filling and copying arrays

* Remove unused imports

* Switch a lambda to a method reference

* Cleanup - CardPredicate Aggregates to method references

* Cleanup - Other static functions to method references

* Cleanup - Ambiguous class reference
Unclear when or how this happened...

* Cleanup - Anonymous -> Method reference

* Cleanup - Anonymous -> Lambda

* Cleanup - Comparator helper methods

* Cleanup - final method in final class

* Cleanup - private final methods

* Remove unused imports

* Convert a couple more lambdas to comparators.

* Simplify creature type list comparison.

---------

Co-authored-by: Jetz <Jetz722@gmail.com>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
This commit is contained in:
Jetz72
2024-08-02 01:23:58 -04:00
committed by GitHub
parent 86ac0349ca
commit d62dfe1c8c
490 changed files with 6692 additions and 13091 deletions

View File

@@ -124,12 +124,7 @@ public class AiAttackController {
List<Card> defenders = defender.getCreaturesInPlay();
int totalMana = ComputerUtilMana.getAvailableManaEstimate(defender, true);
int manaReserved = 0; // for paying the cost to transform
Predicate<Card> canAnimate = new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return !c.isTapped() && !c.isCreature() && !c.isPlaneswalker();
}
};
Predicate<Card> canAnimate = c -> !c.isTapped() && !c.isCreature() && !c.isPlaneswalker();
CardCollection tappedDefenders = new CardCollection();
for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) {
@@ -331,12 +326,7 @@ public class AiAttackController {
}
public final static List<Card> getPossibleBlockers(final List<Card> blockers, final List<Card> attackers, final boolean nextTurn) {
return CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return canBlockAnAttacker(c, attackers, nextTurn);
}
});
return CardLists.filter(blockers, c -> canBlockAnAttacker(c, attackers, nextTurn));
}
public final static boolean canBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
@@ -395,15 +385,10 @@ public class AiAttackController {
}
}
// reduce the search space
final List<Card> opponentsAttackers = CardLists.filter(ai.getOpponents().getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.hasSVar("EndOfTurnLeavePlay")
&& (c.toughnessAssignsDamage() || c.getNetCombatDamage() > 0 // performance shortcuts
|| c.getNetCombatDamage() + ComputerUtilCombat.predictPowerBonusOfAttacker(c, null, null, true) > 0)
&& ComputerUtilCombat.canAttackNextTurn(c);
}
});
final List<Card> opponentsAttackers = CardLists.filter(ai.getOpponents().getCreaturesInPlay(), c -> !c.hasSVar("EndOfTurnLeavePlay")
&& (c.toughnessAssignsDamage() || c.getNetCombatDamage() > 0 // performance shortcuts
|| c.getNetCombatDamage() + ComputerUtilCombat.predictPowerBonusOfAttacker(c, null, null, true) > 0)
&& ComputerUtilCombat.canAttackNextTurn(c));
// don't hold back creatures that can't block any of the human creatures
final List<Card> blockers = getPossibleBlockers(potentialAttackers, opponentsAttackers, true);
@@ -770,7 +755,7 @@ public class AiAttackController {
return false;
}
private final Pair<Integer, Integer> getDamageFromBlockingTramplers(final List<Card> blockedAttackers, final List<Card> blockers, final int myFreeMana) {
private Pair<Integer, Integer> getDamageFromBlockingTramplers(final List<Card> blockedAttackers, final List<Card> blockers, final int myFreeMana) {
int currentAttackTax = 0;
int trampleDamage = 0;
CardCollection remainingBlockers = new CardCollection(blockers);
@@ -797,7 +782,7 @@ public class AiAttackController {
return Pair.of(trampleDamage, currentAttackTax);
}
private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
private GameEntity chooseDefender(final Combat c, final boolean bAssault) {
final FCollectionView<GameEntity> defs = c.getDefenders();
if (defs.size() == 1) {
return defs.getFirst();
@@ -932,31 +917,28 @@ public class AiAttackController {
// check defenders in order of maximum requirements
List<Pair<GameEntity, Integer>> reqs = combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements();
final GameEntity def = defender;
reqs.sort(new Comparator<Pair<GameEntity, Integer>>() {
@Override
public int compare(Pair<GameEntity, Integer> r1, Pair<GameEntity, Integer> r2) {
if (r1.getValue() == r2.getValue()) {
// try to attack the designated defender
if (r1.getKey().equals(def) && !r2.getKey().equals(def)) {
return -1;
}
if (r2.getKey().equals(def) && !r1.getKey().equals(def)) {
return 1;
}
// otherwise PW
if (r1.getKey() instanceof Card && r2.getKey() instanceof Player) {
return -1;
}
if (r2.getKey() instanceof Card && r1.getKey() instanceof Player) {
return 1;
}
// or weakest player
if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) {
return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife();
}
reqs.sort((r1, r2) -> {
if (r1.getValue() == r2.getValue()) {
// try to attack the designated defender
if (r1.getKey().equals(def) && !r2.getKey().equals(def)) {
return -1;
}
if (r2.getKey().equals(def) && !r1.getKey().equals(def)) {
return 1;
}
// otherwise PW
if (r1.getKey() instanceof Card && r2.getKey() instanceof Player) {
return -1;
}
if (r2.getKey() instanceof Card && r1.getKey() instanceof Player) {
return 1;
}
// or weakest player
if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) {
return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife();
}
return r2.getValue() - r1.getValue();
}
return r2.getValue() - r1.getValue();
});
for (Pair<GameEntity, Integer> e : reqs) {
if (e.getRight() == 0) continue;
@@ -1406,14 +1388,9 @@ public class AiAttackController {
}
// contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, new Predicate<Card>() {
@Override
public boolean apply(Card defender) {
return CombatUtil.canBlock(attacker, defender);
}
});
CardCollection validBlockers = CardLists.filter(defenders, defender1 -> CombatUtil.canBlock(attacker, defender1));
boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, CardPredicates.Accessors.fnGetNetToughness);
boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, Card::getNetToughness);
// used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present
boolean dangerousBlockersPresent = Iterables.any(validBlockers, Predicates.or(

View File

@@ -140,23 +140,20 @@ public class AiBlockController {
ComputerUtilCard.sortByEvaluateCreature(attackers);
CardLists.sortByPowerDesc(attackers);
//move cards like Phage the Untouchable to the front
attackers.sort(new Comparator<Card>() {
@Override
public int compare(final Card o1, final Card o2) {
if (o1.hasSVar("MustBeBlocked") && !o2.hasSVar("MustBeBlocked")) {
return -1;
}
if (!o1.hasSVar("MustBeBlocked") && o2.hasSVar("MustBeBlocked")) {
return 1;
}
if (attackingCmd.contains(o1) && !attackingCmd.contains(o2)) {
return -1;
}
if (!attackingCmd.contains(o1) && attackingCmd.contains(o2)) {
return 1;
}
return 0;
attackers.sort((o1, o2) -> {
if (o1.hasSVar("MustBeBlocked") && !o2.hasSVar("MustBeBlocked")) {
return -1;
}
if (!o1.hasSVar("MustBeBlocked") && o2.hasSVar("MustBeBlocked")) {
return 1;
}
if (attackingCmd.contains(o1) && !attackingCmd.contains(o2)) {
return -1;
}
if (!attackingCmd.contains(o1) && attackingCmd.contains(o2)) {
return 1;
}
return 0;
});
return attackers;
}
@@ -330,43 +327,35 @@ public class AiBlockController {
}
private Predicate<Card> rampagesOrNeedsManyToBlock(final Combat combat) {
return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), new Predicate<Card>() {
@Override
public boolean apply(Card input) {
// select creature that has a max blocker
return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE;
}
return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), input -> {
// select creature that has a max blocker
return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE;
});
}
private Predicate<Card> changesPTWhenBlocked(final boolean onlyForDefVsTrample) {
return new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (final Trigger tr : card.getTriggers()) {
if (tr.getMode() == TriggerType.AttackerBlocked) {
SpellAbility ab = tr.getOverridingAbility();
if (ab != null) {
if (ab.getApi() == ApiType.Pump && "Self".equals(ab.getParam("Defined"))) {
String rawP = ab.getParam("NumAtt");
String rawT = ab.getParam("NumDef");
if ("+X".equals(rawP) && "+X".equals(rawT) && card.getSVar("X").startsWith("Count$Valid Creature.blockingTriggeredAttacker")) {
return true;
}
// TODO: maybe also predict calculated bonus above certain threshold?
} else if (ab.getApi() == ApiType.PumpAll && ab.hasParam("ValidCards")
&& ab.getParam("ValidCards").startsWith("Creature.blockingSource")) {
int pBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumAtt"), ab);
int tBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumDef"), ab);
return (!onlyForDefVsTrample && pBonus < 0) || tBonus < 0;
return card -> {
for (final Trigger tr : card.getTriggers()) {
if (tr.getMode() == TriggerType.AttackerBlocked) {
SpellAbility ab = tr.getOverridingAbility();
if (ab != null) {
if (ab.getApi() == ApiType.Pump && "Self".equals(ab.getParam("Defined"))) {
String rawP = ab.getParam("NumAtt");
String rawT = ab.getParam("NumDef");
if ("+X".equals(rawP) && "+X".equals(rawT) && card.getSVar("X").startsWith("Count$Valid Creature.blockingTriggeredAttacker")) {
return true;
}
// TODO: maybe also predict calculated bonus above certain threshold?
} else if (ab.getApi() == ApiType.PumpAll && ab.hasParam("ValidCards")
&& ab.getParam("ValidCards").startsWith("Creature.blockingSource")) {
int pBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumAtt"), ab);
int tBonus = AbilityUtils.calculateAmount(card, ab.getParam("NumDef"), ab);
return (!onlyForDefVsTrample && pBonus < 0) || tBonus < 0;
}
}
}
return false;
}
return false;
};
}
@@ -452,15 +441,12 @@ public class AiBlockController {
// Try to add blockers that could be destroyed, but are worth less than the attacker
// Don't use blockers without First Strike or Double Strike if attacker has it
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
return false;
}
return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
usableBlockers = CardLists.filter(blockers, c -> {
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
return false;
}
return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
});
if (usableBlockers.size() < 2) {
return;
@@ -579,13 +565,8 @@ public class AiBlockController {
final List<Card> blockGang = new ArrayList<>();
int absorbedDamage; // The amount of damage needed to kill the first blocker
List<Card> usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetToughness() > attacker.getNetCombatDamage() // performance shortcut
|| c.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, c, true) > attacker.getNetCombatDamage();
}
});
List<Card> usableBlockers = CardLists.filter(blockers, c -> c.getNetToughness() > attacker.getNetCombatDamage() // performance shortcut
|| c.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, c, true) > attacker.getNetCombatDamage());
if (usableBlockers.size() < 2) {
return;
}
@@ -829,12 +810,7 @@ public class AiBlockController {
blockers.removeAll(combat.getBlockers(attacker));
// Don't add any blockers that won't kill the attacker because the damage would be prevented by a static effect
blockers = CardLists.filter(blockers, new Predicate<Card>() {
@Override
public boolean apply(Card blocker) {
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
}
});
blockers = CardLists.filter(blockers, blocker -> !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage()));
// Try to use safe blockers first
if (blockers.size() > 0) {
@@ -921,13 +897,9 @@ public class AiBlockController {
CardCollection pwsWithChumpBlocks = new CardCollection();
CardCollection chosenChumpBlockers = new CardCollection();
CardCollection chumpPWDefenders = CardLists.filter(this.blockersLeft, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.evaluateCreature(card) <= (card.isToken() ? evalThresholdToken
: evalThresholdNonToken);
}
});
CardCollection chumpPWDefenders = CardLists.filter(this.blockersLeft,
card -> ComputerUtilCard.evaluateCreature(card) <= (card.isToken() ? evalThresholdToken : evalThresholdNonToken)
);
CardLists.sortByPowerAsc(chumpPWDefenders);
if (!chumpPWDefenders.isEmpty()) {
for (final Card attacker : attackers) {

View File

@@ -37,7 +37,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.SpellApiBased;
import forge.game.card.*;
import forge.game.card.CardPredicates.Accessors;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -418,7 +417,7 @@ public class AiController {
CardCollection allCards = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
allCards.addAll(player.getCardsIn(ZoneType.Command));
allCards.addAll(cardsInPlay);
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
int maxCmcInHand = Aggregates.max(hand, Card::getCMC);
int max = Math.max(maxCmcInHand, 6);
// consider not playing lands if there are enough already and an ability with a discard cost is present
if (landsInPlay.size() + landList.size() > max) {
@@ -437,43 +436,40 @@ public class AiController {
}
}
landList = CardLists.filter(landList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
if (canPlaySpellBasic(c, null) != AiPlayDecision.WillPlay) {
landList = CardLists.filter(landList, c -> {
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
if (canPlaySpellBasic(c, null) != AiPlayDecision.WillPlay) {
return false;
}
String name = c.getName();
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
return false;
}
String name = c.getName();
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
return false;
}
}
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
CardCollection lands = new CardCollection(battlefield);
lands.addAll(hand);
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
// don't play MDFC land if other side is spell and enough lands are available
if (!c.isLand() || (c.isModal() && !c.getState(CardStateName.Modal).getType().isLand())) {
return false;
}
// don't play the land if it has cycling and enough lands are available
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
for (final SpellAbility sa : spellAbilities) {
if (sa.isCycling()) {
return false;
}
}
}
return player.canPlayLand(c);
}
final CardCollectionView hand1 = player.getCardsIn(ZoneType.Hand);
CardCollection lands = new CardCollection(battlefield);
lands.addAll(hand1);
lands = CardLists.filter(lands, Presets.LANDS);
int maxCmcInHand = Aggregates.max(hand1, Card::getCMC);
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
// don't play MDFC land if other side is spell and enough lands are available
if (!c.isLand() || (c.isModal() && !c.getState(CardStateName.Modal).getType().isLand())) {
return false;
}
// don't play the land if it has cycling and enough lands are available
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
for (final SpellAbility sa : spellAbilities) {
if (sa.isCycling()) {
return false;
}
}
}
return player.canPlayLand(c);
});
return landList;
}
@@ -1441,8 +1437,8 @@ public class AiController {
}
}
int totalCMCInHand = Aggregates.sum(inHand, CardPredicates.Accessors.fnGetCmc);
int minCMCInHand = Aggregates.min(inHand, CardPredicates.Accessors.fnGetCmc);
int totalCMCInHand = Aggregates.sum(inHand, Card::getCMC);
int minCMCInHand = Aggregates.min(inHand, Card::getCMC);
if (minCMCInHand == Integer.MAX_VALUE)
minCMCInHand = 0;
int predictedMana = ComputerUtilMana.getAvailableManaEstimate(player, true);
@@ -1450,66 +1446,60 @@ public class AiController {
boolean canCastWithLandDrop = (predictedMana + 1 >= minCMCInHand) && minCMCInHand > 0 && !isTapLand;
boolean cantCastAnythingNow = predictedMana < minCMCInHand;
boolean hasRelevantAbsOTB = Iterables.any(otb, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean isTapLand = false;
for (ReplacementEffect repl : card.getReplacementEffects()) {
// TODO: improve the detection of taplands
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
isTapLand = true;
}
boolean hasRelevantAbsOTB = Iterables.any(otb, card -> {
boolean isTapLand1 = false;
for (ReplacementEffect repl : card.getReplacementEffects()) {
// TODO: improve the detection of taplands
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
isTapLand1 = true;
}
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.isAbility()
&& sa.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
&& (!sa.hasParam("ActivationZone") || sa.getParam("ActivationZone").contains("Battlefield"))) {
return true;
}
}
return false;
}
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.isAbility()
&& sa.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
&& (!sa.getPayCosts().hasTapCost() || !isTapLand1)
&& (!sa.hasParam("ActivationZone") || sa.getParam("ActivationZone").contains("Battlefield"))) {
return true;
}
}
return false;
});
boolean hasLandBasedEffect = Iterables.any(otb, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (Trigger t : card.getTriggers()) {
Map<String, String> params = t.getMapParams();
if ("ChangesZone".equals(params.get("Mode"))
&& params.containsKey("ValidCard")
&& (!params.containsKey("AILogic") || !params.get("AILogic").equals("SafeToHold"))
&& !params.get("ValidCard").contains("nonLand")
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
&& "Battlefield".equals(params.get("Destination"))) {
// Landfall and other similar triggers
return true;
}
boolean hasLandBasedEffect = Iterables.any(otb, card -> {
for (Trigger t : card.getTriggers()) {
Map<String, String> params = t.getMapParams();
if ("ChangesZone".equals(params.get("Mode"))
&& params.containsKey("ValidCard")
&& (!params.containsKey("AILogic") || !params.get("AILogic").equals("SafeToHold"))
&& !params.get("ValidCard").contains("nonLand")
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
&& "Battlefield".equals(params.get("Destination"))) {
// Landfall and other similar triggers
return true;
}
for (String sv : card.getSVars().keySet()) {
String varValue = card.getSVar(sv);
if (varValue.equals("Count$Domain")) {
for (String type : landToPlay.getType().getLandTypes()) {
if (CardType.isABasicLandType(type) && CardLists.getType(otb, type).isEmpty()) {
return true;
}
}
}
if (varValue.startsWith("Count$Valid") || sv.equals("BuffedBy")) {
if (varValue.contains("Land") || varValue.contains("Plains") || varValue.contains("Forest")
|| varValue.contains("Mountain") || varValue.contains("Island") || varValue.contains("Swamp")
|| varValue.contains("Wastes")) {
// In presence of various cards that get buffs like "equal to the number of lands you control",
// safer for our AI model to just play the land earlier rather than make a blunder
}
for (String sv : card.getSVars().keySet()) {
String varValue = card.getSVar(sv);
if (varValue.equals("Count$Domain")) {
for (String type : landToPlay.getType().getLandTypes()) {
if (CardType.isABasicLandType(type) && CardLists.getType(otb, type).isEmpty()) {
return true;
}
}
}
return false;
if (varValue.startsWith("Count$Valid") || sv.equals("BuffedBy")) {
if (varValue.contains("Land") || varValue.contains("Plains") || varValue.contains("Forest")
|| varValue.contains("Mountain") || varValue.contains("Island") || varValue.contains("Swamp")
|| varValue.contains("Wastes")) {
// In presence of various cards that get buffs like "equal to the number of lands you control",
// safer for our AI model to just play the land earlier rather than make a blunder
return true;
}
}
}
return false;
});
// TODO: add prediction for effects that will untap a tapland as it enters the battlefield
@@ -1528,7 +1518,7 @@ public class AiController {
return true;
}
private final SpellAbility getSpellAbilityToPlay() {
private SpellAbility getSpellAbilityToPlay() {
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
cards = ComputerUtilCard.dedupeCards(cards);
List<SpellAbility> saList = Lists.newArrayList();
@@ -1572,12 +1562,9 @@ public class AiController {
saList = ComputerUtilAbility.getSpellAbilities(cards, player);
}
Iterables.removeIf(saList, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility spellAbility) { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card
// TODO allow when experimental profile?
return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
}
Iterables.removeIf(saList, spellAbility -> { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card
// TODO allow when experimental profile?
return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
});
//update LivingEndPlayer
useLivingEnd = Iterables.any(player.getZone(ZoneType.Library), CardPredicates.nameEquals("Living End"));
@@ -1997,14 +1984,9 @@ public class AiController {
case FlipOntoBattlefield:
if ("DamageCreatures".equals(sa.getParam("AILogic"))) {
int maxToughness = Integer.parseInt(sa.getSubAbility().getParam("NumDmg"));
CardCollectionView rightToughness = CardLists.filter(pool, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.getController().isOpponentOf(sa.getActivatingPlayer())
&& card.getNetToughness() <= maxToughness
&& card.canBeDestroyed();
}
});
CardCollectionView rightToughness = CardLists.filter(pool, card -> card.getController().isOpponentOf(sa.getActivatingPlayer())
&& card.getNetToughness() <= maxToughness
&& card.canBeDestroyed());
Card bestCreature = ComputerUtilCard.getBestCreatureAI(rightToughness.isEmpty() ? pool : rightToughness);
if (bestCreature != null) {
result.add(bestCreature);
@@ -2140,7 +2122,7 @@ public class AiController {
CardCollection all = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.NONLAND_PERMANENTS);
CardCollection left = CardLists.filterControlledBy(all, game.getNextPlayerAfter(player, Direction.Left));
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(player, Direction.Right));
return Aggregates.sum(left, Accessors.fnGetCmc) > Aggregates.sum(right, Accessors.fnGetCmc);
return Aggregates.sum(left, Card::getCMC) > Aggregates.sum(right, Card::getCMC);
}
return MyRandom.getRandom().nextBoolean();
}
@@ -2167,8 +2149,8 @@ public class AiController {
} else if (aiLogic.equals("CMCOppControlsByPower")) {
// TODO: improve this to check for how dangerous those creatures actually are relative to host card
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Accessors.fnGetNetPower);
int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Accessors.fnGetNetPower);
int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Card::getNetPower);
int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Card::getNetPower);
return powerOdd > powerEven;
}
return MyRandom.getRandom().nextBoolean(); // outside of any specific logic, choose randomly

View File

@@ -1,6 +1,5 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import forge.card.CardType;
@@ -445,16 +444,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
CardCollectionView toExclude =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"),
ability.getActivatingPlayer(), ability.getHostCard(), ability);
toExclude = CardLists.filter(toExclude, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
toExclude = CardLists.filter(toExclude, card -> {
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
return false;
}
return false;
});
exclude.addAll(toExclude);
}
@@ -642,16 +638,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
// filter for negative counters
if (c > toRemove && cost.counter == null) {
List<Card> negatives = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (CounterType cType : table.filterToRemove(crd).keySet()) {
if (ComputerUtil.isNegativeCounter(cType, crd)) {
return true;
}
List<Card> negatives = CardLists.filter(typeList, crd -> {
for (CounterType cType : table.filterToRemove(crd).keySet()) {
if (ComputerUtil.isNegativeCounter(cType, crd)) {
return true;
}
return false;
}
return false;
});
if (!negatives.isEmpty()) {
@@ -673,16 +666,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
// filter for useless counters
// they have no effect on the card, if they are there or removed
if (c > toRemove && cost.counter == null) {
List<Card> useless = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
for (CounterType ctype : table.filterToRemove(crd).keySet()) {
if (ComputerUtil.isUselessCounter(ctype, crd)) {
return true;
}
List<Card> useless = CardLists.filter(typeList, crd -> {
for (CounterType ctype : table.filterToRemove(crd).keySet()) {
if (ComputerUtil.isUselessCounter(ctype, crd)) {
return true;
}
return false;
}
return false;
});
if (!useless.isEmpty()) {
@@ -710,17 +700,14 @@ public class AiCostDecision extends CostDecisionMakerBase {
// try to remove Quest counter on something with enough counters for the
// effect to continue
if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) {
List<Card> prefs = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
// a Card without MaxQuestEffect doesn't need any Quest
// counters
int e = 0;
if (crd.hasSVar("MaxQuestEffect")) {
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
}
return crd.getCounters(CounterEnumType.QUEST) > e;
List<Card> prefs = CardLists.filter(typeList, crd -> {
// a Card without MaxQuestEffect doesn't need any Quest
// counters
int e = 0;
if (crd.hasSVar("MaxQuestEffect")) {
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
}
return crd.getCounters(CounterEnumType.QUEST) > e;
});
prefs.sort(Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));

View File

@@ -11,28 +11,24 @@ import forge.game.zone.ZoneType;
public final class AiPlayerPredicates {
public static final Comparator<Player> compareByZoneValue(final String type, final ZoneType zone,
final SpellAbility sa) {
return new Comparator<Player>() {
@Override
public int compare(Player arg0, Player arg1) {
CardCollectionView list0 = AbilityUtils.filterListByType(arg0.getCardsIn(zone), type, sa);
CardCollectionView list1 = AbilityUtils.filterListByType(arg1.getCardsIn(zone), type, sa);
public static Comparator<Player> compareByZoneValue(final String type, final ZoneType zone, final SpellAbility sa) {
return (arg0, arg1) -> {
CardCollectionView list0 = AbilityUtils.filterListByType(arg0.getCardsIn(zone), type, sa);
CardCollectionView list1 = AbilityUtils.filterListByType(arg1.getCardsIn(zone), type, sa);
int v0, v1;
int v0, v1;
if ((CardLists.getNotType(list0, "Creature").isEmpty())
&& (CardLists.getNotType(list1, "Creature").isEmpty())) {
v0 = ComputerUtilCard.evaluateCreatureList(list0);
v1 = ComputerUtilCard.evaluateCreatureList(list1);
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else {
v0 = ComputerUtilCard.evaluatePermanentList(list0);
v1 = ComputerUtilCard.evaluatePermanentList(list1);
}
return Integer.compare(v0, v1);
if ((CardLists.getNotType(list0, "Creature").isEmpty())
&& (CardLists.getNotType(list1, "Creature").isEmpty())) {
v0 = ComputerUtilCard.evaluateCreatureList(list0);
v1 = ComputerUtilCard.evaluateCreatureList(list1);
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else {
v0 = ComputerUtilCard.evaluatePermanentList(list0);
v1 = ComputerUtilCard.evaluatePermanentList(list1);
}
return Integer.compare(v0, v1);
};
}
}

View File

@@ -93,7 +93,7 @@ public class AiProfileUtil {
* Load a single profile.
* @param profileName a profile to load.
*/
private static final Map<AiProps, String> loadProfile(final String profileName) {
private static Map<AiProps, String> loadProfile(final String profileName) {
Map<AiProps, String> profileMap = new HashMap<>();
List<String> lines = FileUtil.readFile(buildFileName(profileName));

View File

@@ -408,13 +408,9 @@ public class ComputerUtil {
return ComputerUtilCard.getWorstLand(landsInPlay);
}
}
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) == priority)
|| (priority == 1 && shouldSacrificeThreatenedCard(ai, c, sa));
}
});
final CardCollection sacMeList = CardLists.filter(typeList, c -> (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) == priority)
|| (priority == 1 && shouldSacrificeThreatenedCard(ai, c, sa))
);
if (!sacMeList.isEmpty()) {
CardLists.shuffle(sacMeList);
return sacMeList.getFirst();
@@ -429,16 +425,13 @@ public class ComputerUtil {
int maxCreatureEval = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL);
boolean allowTokens = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS);
List<String> dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal");
CardCollection allowList = CardLists.filter(typeList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) {
return false;
}
return (allowTokens && card.isToken())
|| (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName()));
CardCollection allowList = CardLists.filter(typeList, card -> {
if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) {
return false;
}
return (allowTokens && card.isToken())
|| (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName()));
});
if (!allowList.isEmpty()) {
CardLists.sortByCmcDesc(allowList);
@@ -455,7 +448,7 @@ public class ComputerUtil {
final int landsInHand = Math.min(2, CardLists.getType(ai.getCardsIn(ZoneType.Hand), "Land").size());
final CardCollection nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land");
nonLandsInHand.addAll(ai.getCardsIn(ZoneType.Library));
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc));
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, Card::getCMC));
if (landsInPlay.size() + landsInHand >= highestCMC) {
// Don't need more land.
return ComputerUtilCard.getWorstLand(landsInPlay);
@@ -539,7 +532,7 @@ public class ComputerUtil {
if (!landsInHand.isEmpty()) {
final int numLandsInPlay = CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
final CardCollection nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land");
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc));
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, Card::getCMC));
if (numLandsInPlay >= highestCMC
|| (numLandsInPlay + landsInHand.size() > 6 && landsInHand.size() > 1)) {
// Don't need more land.
@@ -735,13 +728,10 @@ public class ComputerUtil {
// FIXME: This is suboptimal, maybe implement a single comparator that'll take care of all of this?
CardLists.sortByCmcDesc(typeList);
Collections.reverse(typeList);
typeList.sort(new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
if (!a.isInPlay() && b.isInPlay()) return -1;
else if (!b.isInPlay() && a.isInPlay()) return 1;
else return 0;
}
typeList.sort((a, b) -> {
if (!a.isInPlay() && b.isInPlay()) return -1;
else if (!b.isInPlay() && a.isInPlay()) return 1;
else return 0;
}); // something that's not on the battlefield should come first
}
final CardCollection exileList = new CardCollection();
@@ -958,40 +948,37 @@ public class ComputerUtil {
}
}
}
remaining = CardLists.filter(remaining, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
int sacThreshold = 190;
remaining = CardLists.filter(remaining, c -> {
int sacThreshold = 190;
String logic = source.getParamOrDefault("AILogic", "");
if (logic.startsWith("SacForDamage")) {
final int damageAmt = logic.contains("cmc") ? c.getManaCost().getCMC() : c.getNetPower();
if (damageAmt <= 0) {
return false;
} else if (damageAmt >= ai.getOpponentsSmallestLifeTotal()) {
return true;
} else if (logic.endsWith(".GiantX2") && c.getType().hasCreatureType("Giant")
&& damageAmt * 2 >= ai.getOpponentsSmallestLifeTotal()) {
return true; // TODO: generalize this for any type and actually make the AI prefer giants?
}
}
if ("DesecrationDemon".equals(logic)) {
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
} else if (considerSacThreshold != -1) {
sacThreshold = considerSacThreshold;
}
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) < sacThreshold) {
String logic = source.getParamOrDefault("AILogic", "");
if (logic.startsWith("SacForDamage")) {
final int damageAmt = logic.contains("cmc") ? c.getManaCost().getCMC() : c.getNetPower();
if (damageAmt <= 0) {
return false;
} else if (damageAmt >= ai.getOpponentsSmallestLifeTotal()) {
return true;
} else if (logic.endsWith(".GiantX2") && c.getType().hasCreatureType("Giant")
&& damageAmt * 2 >= ai.getOpponentsSmallestLifeTotal()) {
return true; // TODO: generalize this for any type and actually make the AI prefer giants?
}
if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
return true;
}
return false;
}
if ("DesecrationDemon".equals(logic)) {
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
} else if (considerSacThreshold != -1) {
sacThreshold = considerSacThreshold;
}
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) < sacThreshold) {
return true;
}
if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
return true;
}
return false;
});
}
@@ -1362,7 +1349,7 @@ public class ComputerUtil {
final CardCollection landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA);
final CardCollection landsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS);
final CardCollection nonLandsInHand = CardLists.getNotType(ai.getCardsIn(ZoneType.Hand), "Land");
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, CardPredicates.Accessors.fnGetCmc));
final int highestCMC = Math.max(6, Aggregates.max(nonLandsInHand, Card::getCMC));
final int discardCMC = discard.getCMC();
if (discard.isLand()) {
if (landsInPlay.size() >= highestCMC
@@ -2183,12 +2170,7 @@ public class ComputerUtil {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
if (aic.getBooleanProperty(AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) {
// Try to avoid targeting creatures that are dead on board
List<Card> willBeKilled = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.isCreature() && predictCreatureWillDieThisTurn(ai, card, excludeSa);
}
});
List<Card> willBeKilled = CardLists.filter(list, card -> card.isCreature() && predictCreatureWillDieThisTurn(ai, card, excludeSa));
list.removeAll(willBeKilled);
}
return list;
@@ -2245,13 +2227,8 @@ public class ComputerUtil {
return finalHandSize;
}
final CardCollectionView lands = CardLists.filter(handList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getManaCost().getCMC() <= 0 && !c.hasSVar("NeedsToPlay")
&& (c.isLand() || c.isArtifact());
}
});
final CardCollectionView lands = CardLists.filter(handList, c -> c.getManaCost().getCMC() <= 0 && !c.hasSVar("NeedsToPlay")
&& (c.isLand() || c.isArtifact()));
final int handSize = handList.size();
final int landSize = lands.size();
@@ -2265,12 +2242,7 @@ public class ComputerUtil {
score += 10;
}
final CardCollectionView castables = CardLists.filter(handList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getManaCost().getCMC() <= 0 || c.getManaCost().getCMC() <= landSize;
}
});
final CardCollectionView castables = CardLists.filter(handList, c -> c.getManaCost().getCMC() <= 0 || c.getManaCost().getCMC() <= landSize);
score += castables.size() * 2;
@@ -2431,7 +2403,7 @@ public class ComputerUtil {
} else if (c.isCreature()) {
CardCollection creaturesOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.CREATURES);
int avgCreatureValue = numCards != 0 ? ComputerUtilCard.evaluateCreatureList(allCreatures) / numCards : 0;
int maxControlledCMC = Aggregates.max(creaturesOTB, CardPredicates.Accessors.fnGetCmc);
int maxControlledCMC = Aggregates.max(creaturesOTB, Card::getCMC);
if (ComputerUtilCard.evaluateCreature(c) < avgCreatureValue) {
if (creaturesOTB.size() > minCreatsToScryCreatsAway) {
@@ -2460,12 +2432,7 @@ public class ComputerUtil {
}
public static CardCollection getCardsToDiscardFromOpponent(Player chooser, Player discarder, SpellAbility sa, CardCollection validCards, int min, int max) {
CardCollection goodChoices = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.hasSVar("DiscardMeByOpp") && !c.hasSVar("DiscardMe");
}
});
CardCollection goodChoices = CardLists.filter(validCards, c -> !c.hasSVar("DiscardMeByOpp") && !c.hasSVar("DiscardMe"));
if (goodChoices.isEmpty()) {
goodChoices = validCards;
}
@@ -2859,25 +2826,17 @@ public class ComputerUtil {
}
public static CardCollection getSafeTargets(final Player ai, SpellAbility sa, CardCollectionView validCards) {
CardCollection safeCards = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController() == ai) {
return !c.getSVar("Targeting").equals("Dies") && !c.getSVar("Targeting").equals("Counter");
}
return true;
CardCollection safeCards = CardLists.filter(validCards, c -> {
if (c.getController() == ai) {
return !c.getSVar("Targeting").equals("Dies") && !c.getSVar("Targeting").equals("Counter");
}
return true;
});
return safeCards;
}
public static Card getKilledByTargeting(final SpellAbility sa, CardCollectionView validCards) {
CardCollection killables = CardLists.filter(validCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies");
}
});
CardCollection killables = CardLists.filter(validCards, c -> c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies"));
return ComputerUtilCard.getBestCreatureAI(killables);
}
@@ -3233,12 +3192,7 @@ public class ComputerUtil {
CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
CardCollectionView inDeck = ai.getCardsIn(ZoneType.Library);
Predicate<Card> markedAsReanimator = new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return "true".equalsIgnoreCase(card.getSVar("IsReanimatorCard"));
}
};
Predicate<Card> markedAsReanimator = card -> "true".equalsIgnoreCase(card.getSVar("IsReanimatorCard"));
int numInHand = CardLists.count(inHand, markedAsReanimator);
int numInDeck = CardLists.count(inDeck, markedAsReanimator);
@@ -3271,12 +3225,7 @@ public class ComputerUtil {
value = ComputerUtilCard.evaluateCreature(source);
}
final int totalValue = value;
list = CardLists.filter(srcList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCard.evaluateCreature(c) > totalValue + 30;
}
});
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
} else {
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
}

View File

@@ -4,7 +4,6 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardStateName;
@@ -38,18 +37,15 @@ public class ComputerUtilAbility {
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
//filter out cards that can't be played
landList = CardLists.filter(landList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.getSVar("NeedsToPlay").isEmpty()) {
final String needsToPlay = c.getSVar("NeedsToPlay");
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null);
if (list.isEmpty()) {
return false;
}
landList = CardLists.filter(landList, c -> {
if (!c.getSVar("NeedsToPlay").isEmpty()) {
final String needsToPlay = c.getSVar("NeedsToPlay");
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null);
if (list.isEmpty()) {
return false;
}
return player.canPlayLand(c);
}
return player.canPlayLand(c);
});
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));

View File

@@ -58,12 +58,7 @@ public class ComputerUtilCard {
public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) {
CardCollectionView all = list;
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
all = CardLists.filter(all, c -> c.canBeTargetedBy(spell));
}
return getMostExpensivePermanentAI(all);
}
@@ -96,7 +91,7 @@ public class ComputerUtilCard {
return null;
}
// get biggest Artifact
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
return Aggregates.itemWithMax(all, Card::getCMC);
}
/**
@@ -111,7 +106,7 @@ public class ComputerUtilCard {
return null;
}
// no AI logic, just return most expensive
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
return Aggregates.itemWithMax(all, Card::getCMC);
}
/**
@@ -126,7 +121,7 @@ public class ComputerUtilCard {
return null;
}
// no AI logic, just return least expensive
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
return Aggregates.itemWithMin(all, Card::getCMC);
}
public static Card getBestPlaneswalkerToDamage(final List<Card> pws) {
@@ -194,16 +189,11 @@ public class ComputerUtilCard {
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS);
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
all = CardLists.filter(all, c -> c.canBeTargetedBy(spell));
}
// get biggest Enchantment
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
return Aggregates.itemWithMax(all, Card::getCMC);
}
/**
@@ -342,12 +332,7 @@ public class ComputerUtilCard {
*/
public static Card getCheapestPermanentAI(Iterable<Card> all, final SpellAbility spell, final boolean targeted) {
if (targeted) {
all = CardLists.filter(all, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(spell);
}
});
all = CardLists.filter(all, c -> c.canBeTargetedBy(spell));
}
if (Iterables.isEmpty(all)) {
return null;
@@ -524,12 +509,10 @@ public class ComputerUtilCard {
}
if (hasEnchantmants || hasArtifacts) {
final List<Card> ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !card.hasSVar("DoNotDiscardIfAble");
}
}));
final List<Card> ae = CardLists.filter(list, Predicates.and(
Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS),
card -> !card.hasSVar("DoNotDiscardIfAble")
));
return getCheapestPermanentAI(ae, null, false);
}
@@ -568,18 +551,10 @@ public class ComputerUtilCard {
return null;
}
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
@Override
public int compare(final Card a, final Card b) {
return evaluateCreature(b) - evaluateCreature(a);
}
};
public static final Comparator<SpellAbility> EvaluateCreatureSpellComparator = new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility a, final SpellAbility b) {
// TODO ideally we could reuse the value from the previous pass with false
return ComputerUtilAbility.saEvaluator.compareEvaluator(a, b, true);
}
public static final Comparator<Card> EvaluateCreatureComparator = (a, b) -> evaluateCreature(b) - evaluateCreature(a);
public static final Comparator<SpellAbility> EvaluateCreatureSpellComparator = (a, b) -> {
// TODO ideally we could reuse the value from the previous pass with false
return ComputerUtilAbility.saEvaluator.compareEvaluator(a, b, true);
};
private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
@@ -774,7 +749,7 @@ public class ComputerUtilCard {
// Add all cost of all auras with the same controller
if (card.isEnchanted()) {
final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
curCMC += Aggregates.sum(auras, Card::getCMC) + auras.size();
}
if (curCMC >= bigCMC) {
@@ -990,12 +965,7 @@ public class ComputerUtilCard {
if (color.hasGreen()) map.get(4).setValue(map.get(4).getValue() + 1);
}
Collections.sort(map, new Comparator<Pair<Byte, Integer>>() {
@Override
public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
Collections.sort(map, Comparator.<Pair<Byte, Integer>>comparingInt(Pair::getValue).reversed());
// will this part be once dropped?
List<String> result = new ArrayList<>(cntColors);
@@ -1006,17 +976,14 @@ public class ComputerUtilCard {
return result;
}
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
@Override
public boolean apply(Deck d) {
for (Entry<DeckSection, CardPool> cp : d) {
for (Entry<PaperCard, Integer> e : cp.getValue()) {
if (e.getKey().getRules().getAiHints().getRemAIDecks())
return false;
}
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = d -> {
for (Entry<DeckSection, CardPool> cp : d) {
for (Entry<PaperCard, Integer> e : cp.getValue()) {
if (e.getKey().getRules().getAiHints().getRemAIDecks())
return false;
}
return true;
}
return true;
};
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {

View File

@@ -17,7 +17,6 @@
*/
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -79,11 +78,7 @@ public class ComputerUtilCombat {
*/
public static boolean canAttackNextTurn(final Card attacker) {
final Iterable<GameEntity> defenders = CombatUtil.getAllPossibleDefenders(attacker.getController());
return Iterables.any(defenders, new Predicate<GameEntity>() {
@Override public boolean apply(final GameEntity input) {
return canAttackNextTurn(attacker, input);
}
});
return Iterables.any(defenders, input -> canAttackNextTurn(attacker, input));
}
/**
@@ -138,12 +133,7 @@ public class ComputerUtilCombat {
*/
public static int getTotalFirstStrikeBlockPower(final Card attacker, final Player player) {
List<Card> list = player.getCreaturesInPlay();
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.hasFirstStrike() || c.hasDoubleStrike()) && CombatUtil.canBlock(attacker, c);
}
});
list = CardLists.filter(list, c -> (c.hasFirstStrike() || c.hasDoubleStrike()) && CombatUtil.canBlock(attacker, c));
return totalFirstStrikeDamageOfBlockers(attacker, list);
}

View File

@@ -1,6 +1,5 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -472,12 +471,7 @@ public class ComputerUtilCost {
String totalP = type.split("withTotalPowerGE")[1];
type = TextUtil.fastReplace(type, TextUtil.concatNoSpace("+withTotalPowerGE", totalP), "");
CardCollection exclude = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sa);
exclude = CardLists.filter(exclude, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCard.evaluateCreature(c) >= vehicleValue;
}
}); // exclude creatures >= vehicle
exclude = CardLists.filter(exclude, c -> ComputerUtilCard.evaluateCreature(c) >= vehicleValue); // exclude creatures >= vehicle
exclude.addAll(alreadyTapped);
CardCollection tappedCrew = ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, Integer.parseInt(totalP), exclude);
if (tappedCrew != null) {
@@ -616,18 +610,15 @@ public class ComputerUtilCost {
CardCollectionView nonManaSources =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), part.getType().split(";"),
sa.getActivatingPlayer(), sa.getHostCard(), sa);
nonManaSources = CardLists.filter(nonManaSources, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean hasManaSa = false;
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
hasManaSa = true;
break;
}
nonManaSources = CardLists.filter(nonManaSources, card -> {
boolean hasManaSa = false;
for (final SpellAbility sa1 : card.getSpellAbilities()) {
if (sa1.isManaAbility() && sa1.getPayCosts().hasTapCost()) {
hasManaSa = true;
break;
}
return !hasManaSa;
}
return !hasManaSa;
});
if (nonManaSources.size() < part.convertAmount()) {
return false;

View File

@@ -1,6 +1,5 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import forge.ai.AiCardMemory.MemorySet;
@@ -126,12 +125,7 @@ public class ComputerUtilMana {
}
}
}
orderedCards.sort(new Comparator<Card>() {
@Override
public int compare(final Card card1, final Card card2) {
return Integer.compare(manaCardMap.get(card1), manaCardMap.get(card2));
}
});
orderedCards.sort(Comparator.comparingInt(manaCardMap::get));
if (DEBUG_MANA_PAYMENT) {
System.out.print("Ordered Cards: " + orderedCards.size());
@@ -149,29 +143,26 @@ public class ComputerUtilMana {
System.out.println("Unsorted Abilities: " + newAbilities);
}
newAbilities.sort(new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
int preOrder = orderedCards.indexOf(ability1.getHostCard()) - orderedCards.indexOf(ability2.getHostCard());
newAbilities.sort((ability1, ability2) -> {
int preOrder = orderedCards.indexOf(ability1.getHostCard()) - orderedCards.indexOf(ability2.getHostCard());
if (preOrder != 0) {
return preOrder;
}
// Mana abilities on the same card
String shardMana = shard.toString().replaceAll("\\{", "").replaceAll("\\}", "");
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);
if (payWithAb1 && !payWithAb2) {
return -1;
} else if (payWithAb2 && !payWithAb1) {
return 1;
}
return ability1.compareTo(ability2);
if (preOrder != 0) {
return preOrder;
}
// Mana abilities on the same card
String shardMana = shard.toString().replaceAll("\\{", "").replaceAll("\\}", "");
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);
if (payWithAb1 && !payWithAb2) {
return -1;
} else if (payWithAb2 && !payWithAb1) {
return 1;
}
return ability1.compareTo(ability2);
});
if (DEBUG_MANA_PAYMENT) {
@@ -195,27 +186,21 @@ public class ComputerUtilMana {
final List<SpellAbility> prefSortedAbilities = new ArrayList<>(newAbilities);
final List<SpellAbility> otherSortedAbilities = new ArrayList<>(newAbilities);
prefSortedAbilities.sort(new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
if (ability1.getManaPart().mana(ability1).contains(preferredShard))
return -1;
else if (ability2.getManaPart().mana(ability2).contains(preferredShard))
return 1;
prefSortedAbilities.sort((ability1, ability2) -> {
if (ability1.getManaPart().mana(ability1).contains(preferredShard))
return -1;
else if (ability2.getManaPart().mana(ability2).contains(preferredShard))
return 1;
return 0;
}
return 0;
});
otherSortedAbilities.sort(new Comparator<SpellAbility>() {
@Override
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
if (ability1.getManaPart().mana(ability1).contains(preferredShard))
return 1;
else if (ability2.getManaPart().mana(ability2).contains(preferredShard))
return -1;
otherSortedAbilities.sort((ability1, ability2) -> {
if (ability1.getManaPart().mana(ability1).contains(preferredShard))
return 1;
else if (ability2.getManaPart().mana(ability2).contains(preferredShard))
return -1;
return 0;
}
return 0;
});
final List<SpellAbility> finalAbilities = new ArrayList<>();
@@ -251,24 +236,14 @@ public class ComputerUtilMana {
List<SpellAbility> filteredList = Lists.newArrayList(saList);
switch (manaSourceType) {
case "Snow":
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().isSnow()
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1;
}
});
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().isSnow()
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1);
saList = filteredList;
break;
case "Treasure":
// Try to spend only one Treasure if possible
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
}
});
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
SpellAbility first = filteredList.get(0);
if (first.getHostCard() != null && first.getHostCard().getType().hasSubtype("Treasure")) {
saList.remove(first);
@@ -280,22 +255,12 @@ public class ComputerUtilMana {
break;
case "TreasureMax":
// Ok to spend as many Treasures as possible
filteredList.sort(new Comparator<SpellAbility>() {
@Override
public int compare(SpellAbility ab1, SpellAbility ab2) {
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
}
});
filteredList.sort((ab1, ab2) -> ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1);
saList = filteredList;
break;
case "NotSameCard":
saList = Lists.newArrayList(Iterables.filter(filteredList, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility saPay) {
return !saPay.getHostCard().getName().equals(sa.getHostCard().getName());
}
}));
saList = Lists.newArrayList(Iterables.filter(filteredList, saPay -> !saPay.getHostCard().getName().equals(sa.getHostCard().getName())));
break;
default:
break;
@@ -1096,12 +1061,7 @@ public class ComputerUtilMana {
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost, Multimap<ManaCostShard, SpellAbility> sourcesForShards) {
List<ManaCostShard> shardsToPay = Lists.newArrayList(cost.getDistinctShards());
// optimize order so that the shards with less available sources are considered first
shardsToPay.sort(new Comparator<ManaCostShard>() {
@Override
public int compare(final ManaCostShard shard1, final ManaCostShard shard2) {
return sourcesForShards.get(shard1).size() - sourcesForShards.get(shard2).size();
}
});
shardsToPay.sort(Comparator.comparingInt(shard -> sourcesForShards.get(shard).size()));
// mind the priorities
// * Pay mono-colored first
// * Pay 2/C with matching colors
@@ -1387,12 +1347,7 @@ public class ComputerUtilMana {
public static int getAvailableManaEstimate(final Player p, final boolean checkPlayable) {
int availableMana = 0;
final List<Card> srcs = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.getManaAbilities().isEmpty();
}
});
final List<Card> srcs = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), c -> !c.getManaAbilities().isEmpty());
int maxProduced = 0;
int producedWithCost = 0;
@@ -1437,17 +1392,14 @@ public class ComputerUtilMana {
//This method is currently used by AI to estimate available mana
public static CardCollection getAvailableManaSources(final Player ai, final boolean checkPlayable) {
final CardCollectionView list = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
final List<Card> manaSources = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai, true);
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
return true;
}
final List<Card> manaSources = CardLists.filter(list, c -> {
for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai, true);
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
return true;
}
return false;
}
return false;
}); // CardListFilter
final CardCollection sortedManaSources = new CardCollection();

View File

@@ -570,12 +570,9 @@ public class PlayerControllerAi extends PlayerController {
if (destinationZone == ZoneType.Graveyard) {
// In presence of Volrath's Shapeshifter in deck, try to place the best creature on top of the graveyard
if (Iterables.any(getGame().getCardsInGame(), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
}
if (Iterables.any(getGame().getCardsInGame(), card -> {
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
})) {
int bestValue = 0;
Card bestCreature = null;
@@ -702,7 +699,7 @@ public class PlayerControllerAi extends PlayerController {
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
Iterable<Card> cardsOfType = Iterables.filter(hand, CardPredicates.restriction(uType.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa));
if (!Iterables.isEmpty(cardsOfType)) {
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
Card toDiscard = Aggregates.itemWithMin(cardsOfType, Card::getCMC);
return new CardCollection(toDiscard);
}
return getAi().getCardsToDiscard(num, null, sa);
@@ -1389,21 +1386,12 @@ public class PlayerControllerAi extends PlayerController {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
//Filter out mana sources that will interfere with payManaCost()
CardCollection untapped = CardLists.filter(untappedCards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getManaAbilities().isEmpty();
}
});
CardCollection untapped = CardLists.filter(untappedCards, c -> c.getManaAbilities().isEmpty());
// Filter out creatures if AI hasn't attacked yet
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (improvise) {
untapped = CardLists.filter(untapped, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {return !c.isCreature();
}
});
untapped = CardLists.filter(untapped, c -> !c.isCreature());
} else {
return new HashMap<>();
}

View File

@@ -1,6 +1,5 @@
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -91,13 +90,10 @@ public class SpecialAiLogic {
bestOwnCardToUpgrade = Iterables.getFirst(CardLists.getKeyword(listOwn, Keyword.INDESTRUCTIBLE), null);
}
if (bestOwnCardToUpgrade == null) {
bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
}
}));
bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, card -> card.isCreature()
&& (ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card))
));
}
if (bestOwnCardToUpgrade != null) {
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) {
@@ -145,14 +141,9 @@ public class SpecialAiLogic {
if (indestructible || (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg)) {
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
}
}
card -> ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| ComputerUtilCard.evaluateCreature(card) < selfEval // Maybe around 150 is OK?
);
return sacFodder.size() >= numCreatsToSac;
}
@@ -196,21 +187,16 @@ public class SpecialAiLogic {
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
// than the card we attacked with.
final CardCollection sacTgts = CardLists.filter(ai.getCreaturesInPlay(),
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(card) < selfEval;
}
}
card -> ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(card) < selfEval
);
if (sacTgts.isEmpty()) {
return false;
}
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
final int minDefT = Aggregates.min(combat.getBlockers(source), Card::getNetToughness);
final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
@@ -218,14 +204,9 @@ public class SpecialAiLogic {
} else {
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
}
}
card -> ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| ComputerUtilCard.evaluateCreature(card) < selfEval // Maybe around 150 is OK?
);
return !sacFodder.isEmpty();
@@ -302,13 +283,8 @@ public class SpecialAiLogic {
// Check if there's anything that will die anyway that can be eaten to gain a perma-bonus
final CardCollection forcedSacTgts = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat));
}
}
card -> ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat))
);
if (!forcedSacTgts.isEmpty()) {
return true;
@@ -327,14 +303,9 @@ public class SpecialAiLogic {
// than the card we attacked with. Since we're getting a permanent bonus, consider sacrificing
// things that are also threatened to be destroyed anyway.
final CardCollection sacTgts = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
}
}
card -> ComputerUtilCard.isUselessCreature(ai, card)
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
);
if (sacTgts.isEmpty()) {
@@ -342,8 +313,8 @@ public class SpecialAiLogic {
}
final boolean sourceCantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
final int minDefT = Aggregates.min(combat.getBlockers(source), Card::getNetToughness);
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
@@ -352,15 +323,10 @@ public class SpecialAiLogic {
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
final boolean isBlocking = combat != null && combat.isBlocking(source);
final CardCollection sacFodder = CardLists.filter(relevantCreats,
new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
}
}
card -> ComputerUtilCard.isUselessCreature(ai, card)
|| card.hasSVar("SacMe")
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
);
return !sacFodder.isEmpty();

View File

@@ -17,7 +17,6 @@
*/
package forge.ai;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -478,8 +477,8 @@ public class SpecialCardAi {
boolean cantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
CardCollection opposition = isBlocking ? combat.getAttackersBlockedBy(source) : combat.getBlockers(source);
int oppP = Aggregates.sum(opposition, CardPredicates.Accessors.fnGetAttack);
int oppT = Aggregates.sum(opposition, CardPredicates.Accessors.fnGetNetToughness);
int oppP = Aggregates.sum(opposition, Card::getNetCombatDamage);
int oppT = Aggregates.sum(opposition, Card::getNetToughness);
boolean oppHasFirstStrike = false;
boolean oppCantDie = true;
@@ -561,7 +560,7 @@ public class SpecialCardAi {
}
Pair<Integer, Integer> predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
int oppT = Aggregates.sum(potentialBlockers, CardPredicates.Accessors.fnGetNetToughness);
int oppT = Aggregates.sum(potentialBlockers, Card::getNetToughness);
return potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife);
}
@@ -754,28 +753,23 @@ public class SpecialCardAi {
public static Card getBestAttachTarget(final Player ai, final SpellAbility sa, final List<Card> list) {
Card chosen = null;
List<Card> aiStuffies = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// Don't enchant creatures that can survive
if (!c.getController().equals(ai)) {
return false;
}
final String name = c.getName();
return name.equals("Stuffy Doll") || name.equals("Boros Reckoner") || name.equals("Spitemare");
List<Card> aiStuffies = CardLists.filter(list, c -> {
// Don't enchant creatures that can survive
if (!c.getController().equals(ai)) {
return false;
}
final String name = c.getName();
return name.equals("Stuffy Doll") || name.equals("Boros Reckoner") || name.equals("Spitemare");
});
if (!aiStuffies.isEmpty()) {
chosen = aiStuffies.get(0);
} else {
List<Card> creatures = CardLists.filterControlledBy(list, ai.getOpponents());
creatures = CardLists.filter(creatures, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// Don't enchant creatures that can survive
return c.canBeDestroyed() && c.getNetCombatDamage() >= c.getNetToughness() && !c.isEnchantedBy("Guilty Conscience");
}
});
// Don't enchant creatures that can survive
creatures = CardLists.filter(creatures, c -> c.canBeDestroyed()
&& c.getNetCombatDamage() >= c.getNetToughness()
&& !c.isEnchantedBy("Guilty Conscience")
);
chosen = ComputerUtilCard.getBestCreatureAI(creatures);
}
@@ -1385,12 +1379,7 @@ public class SpecialCardAi {
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
// TODO: expand this logic to make the AI force the opponent to sacrifice a big threat bigger than a 5/5 flier?
CardCollection creatures = ai.getCreaturesInPlay();
boolean hasValidTgt = !CardLists.filter(creatures, new Predicate<Card>() {
@Override
public boolean apply(Card t) {
return t.getNetPower() < 5 && t.getNetToughness() < 5;
}
}).isEmpty();
boolean hasValidTgt = !CardLists.filter(creatures, t -> t.getNetPower() < 5 && t.getNetToughness() < 5).isEmpty();
if (hasValidTgt) {
Card worstCreature = ComputerUtilCard.getWorstCreatureAI(creatures);
sa.getTargets().add(worstCreature);
@@ -1416,12 +1405,7 @@ public class SpecialCardAi {
public static class SaviorOfOllenbock {
public static boolean consider(final Player ai, final SpellAbility sa) {
CardCollection oppTargetables = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection threats = CardLists.filter(oppTargetables, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !ComputerUtilCard.isUselessCreature(card.getController(), card);
}
});
CardCollection threats = CardLists.filter(oppTargetables, card -> !ComputerUtilCard.isUselessCreature(card.getController(), card));
CardCollection ownTgts = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES);
// TODO: improve the conditions for when the AI is considered threatened (check the possibility of being attacked?)
@@ -1462,13 +1446,10 @@ public class SpecialCardAi {
public static boolean consider(final Player ai, final SpellAbility sa) {
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
final Card copy = CardCopyService.getLKICopy(card);
ComputerUtilCard.applyStaticContPT(ai.getGame(), copy, null);
return copy.getNetToughness() > 0;
}
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), card -> {
final Card copy = CardCopyService.getLKICopy(card);
ComputerUtilCard.applyStaticContPT(ai.getGame(), copy, null);
return copy.getNetToughness() > 0;
}));
CardLists.sortByCmcDesc(creaturesToGet);
@@ -1515,12 +1496,9 @@ public class SpecialCardAi {
// Cards in library that are either below/at (preferred) or above the max CMC affordable by the AI
// (the latter might happen if we're playing a Reanimator deck with lots of fatties)
CardCollection atTargetCMCInLib = CardLists.filter(creatsInLib, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilMana.hasEnoughManaSourcesToCast(card.getSpellPermanent(), ai);
}
});
CardCollection atTargetCMCInLib = CardLists.filter(creatsInLib,
card -> ComputerUtilMana.hasEnoughManaSourcesToCast(card.getSpellPermanent(), ai)
);
if (atTargetCMCInLib.isEmpty()) {
atTargetCMCInLib = CardLists.filter(creatsInLib, CardPredicates.greaterCMC(numManaSrcs));
}
@@ -1585,12 +1563,9 @@ public class SpecialCardAi {
int numManaSrcs = ComputerUtilMana.getAvailableManaEstimate(ai, false)
+ Math.min(1, manaSrcsInHand.size());
CardCollection atTargetCMCInLib = CardLists.filter(creatsInLib, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilMana.hasEnoughManaSourcesToCast(card.getSpellPermanent(), ai);
}
});
CardCollection atTargetCMCInLib = CardLists.filter(creatsInLib,
card -> ComputerUtilMana.hasEnoughManaSourcesToCast(card.getSpellPermanent(), ai)
);
if (atTargetCMCInLib.isEmpty()) {
atTargetCMCInLib = CardLists.filter(creatsInLib, CardPredicates.greaterCMC(numManaSrcs));
}

View File

@@ -5,7 +5,6 @@ import java.util.*;
import forge.game.card.*;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -333,26 +332,23 @@ public class AttachAi extends SpellAbilityAi {
list = CardLists.getNotType(list, type); // Filter out Basic Lands that have the same type as the changing type
// Don't target fetchlands
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
list = CardLists.filter(list, c -> {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
}
}
return true;
}
return true;
});
final Card c = ComputerUtilCard.getBestAI(list);
@@ -385,43 +381,40 @@ public class AttachAi extends SpellAbilityAi {
*/
private static Card attachAIKeepTappedPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory, final Card attachSource) {
// AI For Cards like Paralyzing Grasp and Glimmerdust Nap
final List<Card> prefList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// Don't do Untapped Vigilance cards
if (c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
final List<Card> prefList = CardLists.filter(list, c -> {
// Don't do Untapped Vigilance cards
if (c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
return false;
}
if (!mandatory) {
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
// try to identify if this thing can actually tap
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getPayCosts().hasTapCost()) {
return true;
}
}
return false;
}
}
if (!mandatory) {
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
// try to identify if this thing can actually tap
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getPayCosts().hasTapCost()) {
return true;
}
}
if (!c.isEnchanted()) {
return true;
}
final Iterable<Card> auras = c.getEnchantedBy();
for (Card aura : auras) {
SpellAbility auraSA = aura.getSpells().get(0);
if (auraSA.getApi() == ApiType.Attach) {
if ("KeepTapped".equals(auraSA.getParam("AILogic"))) {
// Don't attach multiple KeepTapped Auras to one card
return false;
}
}
if (!c.isEnchanted()) {
return true;
}
final Iterable<Card> auras = c.getEnchantedBy();
for (Card aura : auras) {
SpellAbility auraSA = aura.getSpells().get(0);
if (auraSA.getApi() == ApiType.Attach) {
if ("KeepTapped".equals(auraSA.getParam("AILogic"))) {
// Don't attach multiple KeepTapped Auras to one card
return false;
}
}
}
return true;
}
return true;
});
final Card c = ComputerUtilCard.getBestAI(prefList);
@@ -506,24 +499,14 @@ public class AttachAi extends SpellAbilityAi {
if (list.isEmpty()) {
return null;
}
Card c = null;
Card card = null;
// AI For choosing a Card to Animate.
List<Card> betterList = CardLists.getNotType(list, "Creature");
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Animate Artifact")) {
betterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getCMC() > 0;
}
});
c = ComputerUtilCard.getMostExpensivePermanentAI(betterList);
betterList = CardLists.filter(betterList, c -> c.getCMC() > 0);
card = ComputerUtilCard.getMostExpensivePermanentAI(betterList);
} else {
List<Card> evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF);
}
});
List<Card> evenBetterList = CardLists.filter(betterList, c -> c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF));
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
@@ -531,40 +514,32 @@ public class AttachAi extends SpellAbilityAi {
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn();
}
});
evenBetterList = CardLists.filter(betterList, c -> c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn());
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return false;
}
evenBetterList = CardLists.filter(betterList, c -> {
for (final SpellAbility sa1 : c.getSpellAbilities()) {
if (sa1.isAbility() && sa1.getPayCosts().hasTapCost()) {
return false;
}
return true;
}
return true;
});
if (!evenBetterList.isEmpty()) {
betterList = evenBetterList;
}
c = ComputerUtilCard.getWorstAI(betterList);
card = ComputerUtilCard.getWorstAI(betterList);
}
// If Mandatory (brought directly into play without casting) gotta
// choose something
if (c == null && mandatory) {
if (card == null && mandatory) {
return chooseLessPreferred(mandatory, list);
}
return c;
return card;
}
/**
@@ -591,29 +566,26 @@ public class AttachAi extends SpellAbilityAi {
//TODO for Reanimate Auras i need the new Attach Spell, in later versions it might be part of the Enchant Keyword
attachSourceLki.addSpellAbility(AbilityFactory.getAbility(attachSourceLki, "NewAttach"));
List<Card> betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Card lki = CardCopyService.getLKICopy(c);
// need to fake it as if lki would be on the battlefield
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
List<Card> betterList = CardLists.filter(list, c -> {
final Card lki = CardCopyService.getLKICopy(c);
// need to fake it as if lki would be on the battlefield
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
attachSourceLki.clearRemembered();
attachSourceLki.addRemembered(lki);
// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
attachSourceLki.clearRemembered();
attachSourceLki.addRemembered(lki);
// need to check what the cards would be on the battlefield
// do not attach yet, that would cause Events
CardCollection preList = new CardCollection(lki);
preList.add(attachSourceLki);
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
boolean result = lki.canBeAttached(attachSourceLki, null);
// need to check what the cards would be on the battlefield
// do not attach yet, that would cause Events
CardCollection preList = new CardCollection(lki);
preList.add(attachSourceLki);
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
boolean result = lki.canBeAttached(attachSourceLki, null);
//reset static abilities
c.getGame().getAction().checkStaticAbilities(false);
//reset static abilities
c.getGame().getAction().checkStaticAbilities(false);
return result;
}
return result;
});
final Card c = ComputerUtilCard.getBestCreatureAI(betterList);
@@ -864,42 +836,29 @@ public class AttachAi extends SpellAbilityAi {
if (totToughness < 0) {
// Kill a creature if we can
final int tgh = totToughness;
prefList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && (c.getLethalDamage() <= Math.abs(tgh))) {
return true;
}
return c.getNetToughness() <= Math.abs(tgh);
prefList = CardLists.filter(list, c -> {
if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && (c.getLethalDamage() <= Math.abs(tgh))) {
return true;
}
return c.getNetToughness() <= Math.abs(tgh);
});
}
Card c = null;
Card card = null;
if (prefList == null || prefList.isEmpty()) {
prefList = new ArrayList<>(list);
} else {
c = ComputerUtilCard.getBestAI(prefList);
if (c != null) {
return c;
card = ComputerUtilCard.getBestAI(prefList);
if (card != null) {
return card;
}
}
if (!keywords.isEmpty()) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return containsUsefulCurseKeyword(keywords, c, sa);
}
});
prefList = CardLists.filter(prefList, c -> containsUsefulCurseKeyword(keywords, c, sa));
} else if (totPower < 0) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
}
});
prefList = CardLists.filter(prefList, c -> c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c));
}
//some auras aren't useful in multiples
@@ -915,25 +874,22 @@ public class AttachAi extends SpellAbilityAi {
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo());
final int threshold = ai.isAI() ? ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD) : Integer.MAX_VALUE;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (!card.isCreature()) {
return false;
}
return ComputerUtilCard.evaluateCreature(card) >= oldEvalRating + threshold;
prefList = CardLists.filter(prefList, c -> {
if (!c.isCreature()) {
return false;
}
return ComputerUtilCard.evaluateCreature(c) >= oldEvalRating + threshold;
});
}
c = ComputerUtilCard.getBestAI(prefList);
card = ComputerUtilCard.getBestAI(prefList);
if (c == null) {
if (card == null) {
return chooseLessPreferred(mandatory, list);
}
return acceptableChoice(c, mandatory);
return acceptableChoice(card, mandatory);
}
/**
@@ -1029,41 +985,30 @@ public class AttachAi extends SpellAbilityAi {
*/
private static Card attachAIPumpPreference(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory, final Card attachSource) {
// AI For choosing a Card to Pump
Card c = null;
Card card = null;
List<Card> magnetList = null;
String stCheck = null;
if (attachSource.isAura() || sa.isBestow()) {
stCheck = "EnchantedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.isCreature()) {
return false;
}
String sVar = c.getSVar("EnchantMe");
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEnchanted());
magnetList = CardLists.filter(list, c -> {
if (!c.isCreature()) {
return false;
}
String sVar = c.getSVar("EnchantMe");
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEnchanted());
});
} else if (attachSource.isEquipment()) {
stCheck = "EquippedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.isCreature()) {
return false;
}
String sVar = c.getSVar("EquipMe");
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEquipped());
magnetList = CardLists.filter(list, c -> {
if (!c.isCreature()) {
return false;
}
String sVar = c.getSVar("EquipMe");
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEquipped());
});
} else if (attachSource.isFortification()) {
stCheck = "FortifiedBy";
magnetList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.isCreature() && !c.isFortified();
}
});
magnetList = CardLists.filter(list, c -> c.isCreature() && !c.isFortified());
}
// Look for triggers that will damage the creature and remove AI-owned creatures that will die
@@ -1109,23 +1054,13 @@ public class AttachAi extends SpellAbilityAi {
// Probably want to "weight" the list by amount of Enchantments and
// choose the "lightest"
List<Card> betterList = CardLists.filter(magnetList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, ai.getWeakestOpponent());
}
});
List<Card> betterList = CardLists.filter(magnetList, c -> CombatUtil.canAttack(c, ai.getWeakestOpponent()));
if (!betterList.isEmpty()) {
return ComputerUtilCard.getBestAI(betterList);
}
// Magnet List should not be attached when they are useless
betterList = CardLists.filter(magnetList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !ComputerUtilCard.isUselessCreature(ai, c);
}
});
betterList = CardLists.filter(magnetList, c -> !ComputerUtilCard.isUselessCreature(ai, c));
if (!betterList.isEmpty()) {
return ComputerUtilCard.getBestAI(betterList);
@@ -1179,38 +1114,30 @@ public class AttachAi extends SpellAbilityAi {
if (totToughness < 0) {
// Don't kill my own stuff with Negative toughness Auras
final int tgh = totToughness;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getLethalDamage() > Math.abs(tgh);
}
});
prefList = CardLists.filter(prefList, c -> c.getLethalDamage() > Math.abs(tgh));
}
//only add useful keywords unless P/T bonus is significant
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
final int pow = totPower;
final boolean extraBlock = grantingExtraBlock;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!keywords.isEmpty()) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
prefList = CardLists.filter(prefList, c -> {
if (!keywords.isEmpty()) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
}
if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
// consider +2 power a significant bonus on Infect creatures
return true;
}
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
return true;
}
return false;
}
if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
// consider +2 power a significant bonus on Infect creatures
return true;
}
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
return true;
}
return false;
});
}
@@ -1229,21 +1156,11 @@ public class AttachAi extends SpellAbilityAi {
if (!attachSource.getName().equals("Daybreak Coronet")) {
// TODO For Auras like Rancor, that aren't as likely to lead to
// card disadvantage, this check should be skipped
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF);
}
});
prefList = CardLists.filter(prefList, c -> !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF));
}
// should not attach Auras to creatures that does leave the play
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.hasSVar("EndOfTurnLeavePlay");
}
});
prefList = CardLists.filter(prefList, c -> !c.hasSVar("EndOfTurnLeavePlay"));
}
// Should not attach things to crewed vehicles that will stop being creatures soon
@@ -1258,12 +1175,7 @@ public class AttachAi extends SpellAbilityAi {
}
}
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.getTimesCrewedThisTurn() == 0 || (attachSource.isEquipment() && attachSource.getGame().getPhaseHandler().is(PhaseType.MAIN1, ai));
}
});
prefList = CardLists.filter(prefList, c -> c.getTimesCrewedThisTurn() == 0 || (attachSource.isEquipment() && attachSource.getGame().getPhaseHandler().is(PhaseType.MAIN1, ai)));
}
if (!grantingAbilities) {
@@ -1271,17 +1183,14 @@ public class AttachAi extends SpellAbilityAi {
// Filter out creatures that can't Attack or have Defender
if (keywords.isEmpty()) {
final int powerBonus = totPower;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.isCreature()) {
return true;
}
return powerBonus + c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
prefList = CardLists.filter(prefList, c -> {
if (!c.isCreature()) {
return true;
}
return powerBonus + c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
});
}
c = ComputerUtilCard.getBestAI(prefList);
card = ComputerUtilCard.getBestAI(prefList);
} else {
for (Card pref : prefList) {
if (pref.isLand() && pref.isUntapped()) {
@@ -1290,14 +1199,14 @@ public class AttachAi extends SpellAbilityAi {
}
// If we grant abilities, we may want to put it on something Weak?
// Possibly more defensive?
c = ComputerUtilCard.getWorstPermanentAI(prefList, false, false, false, false);
card = ComputerUtilCard.getWorstPermanentAI(prefList, false, false, false, false);
}
if (c == null) {
if (card == null) {
return chooseLessPreferred(mandatory, list);
}
return c;
return card;
}
/**
@@ -1338,22 +1247,13 @@ public class AttachAi extends SpellAbilityAi {
// Is a SA that moves target attachment
if ("MoveTgtAura".equals(sa.getParam("AILogic"))) {
CardCollection list = CardLists.filter(CardUtil.getValidCardsToTarget(sa), Predicates.or(CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()), new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return ComputerUtilCard.isUselessCreature(aiPlayer, card.getAttachedTo());
}
}));
CardCollection list = CardLists.filter(CardUtil.getValidCardsToTarget(sa), Predicates.or(CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()),
card -> ComputerUtilCard.isUselessCreature(aiPlayer, card.getAttachedTo())));
return !list.isEmpty() ? ComputerUtilCard.getBestAI(list) : null;
} else if ("Unenchanted".equals(sa.getParam("AILogic"))) {
List<Card> list = CardUtil.getValidCardsToTarget(sa);
CardCollection preferred = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return !card.hasCardAttachments();
}
});
CardCollection preferred = CardLists.filter(list, card -> !card.hasCardAttachments());
return preferred.isEmpty() ? Aggregates.random(list) : Aggregates.random(preferred);
}
@@ -1687,25 +1587,19 @@ public class AttachAi extends SpellAbilityAi {
public static Card doPumpOrCurseAILogic(final Player ai, final SpellAbility sa, final List<Card> list, final String type) {
Card chosen = null;
List<Card> aiType = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// Don't buff opponent's creatures of given type
if (!c.getController().equals(ai)) {
return false;
}
return c.isValid(type, ai, sa.getHostCard(), sa);
List<Card> aiType = CardLists.filter(list, c -> {
// Don't buff opponent's creatures of given type
if (!c.getController().equals(ai)) {
return false;
}
return c.isValid(type, ai, sa.getHostCard(), sa);
});
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// Don't debuff AI's own creatures not of given type
if (c.getController().equals(ai)) {
return false;
}
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
List<Card> oppNonType = CardLists.filter(list, c -> {
// Don't debuff AI's own creatures not of given type
if (c.getController().equals(ai)) {
return false;
}
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
});
if (!aiType.isEmpty() && !oppNonType.isEmpty()) {

View File

@@ -1,7 +1,6 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialAiLogic;
import forge.ai.SpecialCardAi;
@@ -35,12 +34,9 @@ public class BranchAi extends SpellAbilityAi {
}
final CardCollection attackers = combat.getAttackers();
final CardCollection attackingBattle = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
final GameEntity def = combat.getDefenderByAttacker(combat.getBandOfAttacker(card));
return def instanceof Card && ((Card)def).isBattle();
}
final CardCollection attackingBattle = CardLists.filter(attackers, card -> {
final GameEntity def = combat.getDefenderByAttacker(combat.getBandOfAttacker(card));
return def instanceof Card && ((Card)def).isBattle();
});
if (!attackingBattle.isEmpty()) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -378,14 +377,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (type != null && p == ai) {
// AI only "knows" about his information
list = CardLists.getValidCards(list, type, source.getController(), source, sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
}
return true;
list = CardLists.filter(list, c -> {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
}
return true;
});
}
// TODO: prevent ai searching its own library when Ob Nixilis, Unshackled is in play
@@ -906,12 +902,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, true);
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30;
}
});
list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30);
}
if (source.isInZone(ZoneType.Hand)) {
@@ -921,29 +912,23 @@ public class ChangeZoneAi extends SpellAbilityAi {
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
}
if (sa.hasParam("AttachedTo")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (card.isValid(sa.getParam("AttachedTo"), ai, c, sa)) {
return true;
}
list = CardLists.filter(list, c -> {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (card.isValid(sa.getParam("AttachedTo"), ai, c, sa)) {
return true;
}
return false;
}
return false;
});
}
if (sa.hasParam("AttachAfter")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (card.isValid(sa.getParam("AttachAfter"), ai, c, sa)) {
return true;
}
list = CardLists.filter(list, c -> {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (card.isValid(sa.getParam("AttachAfter"), ai, c, sa)) {
return true;
}
return false;
}
return false;
});
}
@@ -1039,12 +1024,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// blink logic: get my own permanents back or blink permanents with ETB effects
if (blink) {
CardCollection blinkTargets = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isToken() && c.getOwner().equals(ai) && (c.getController().isOpponentOf(ai) || c.hasETBTrigger(false));
}
});
CardCollection blinkTargets = CardLists.filter(list, c -> !c.isToken() && c.getOwner().equals(ai) && (c.getController().isOpponentOf(ai) || c.hasETBTrigger(false)));
if (!blinkTargets.isEmpty()) {
CardCollection opponentBlinkTargets = CardLists.filterControlledBy(blinkTargets, ai.getOpponents());
// prefer post-combat unless targeting opponent's stuff or part of another ability
@@ -1070,17 +1050,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
list = CardLists.filterControlledBy(list, ai.getOpponents());
if (!CardLists.getNotType(list, "Land").isEmpty()) {
// When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card aura : c.getEnchantedBy()) {
return aura.getController().isOpponentOf(ai);
}
if (blink) {
return c.isToken();
}
return c.isToken() || c.getCMC() > 0;
list = CardLists.filter(list, c -> {
for (Card aura : c.getEnchantedBy()) {
return aura.getController().isOpponentOf(ai);
}
if (blink) {
return c.isToken();
}
return c.isToken() || c.getCMC() > 0;
});
}
}
@@ -1102,16 +1079,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
// only retrieve cards from computer graveyard
list = CardLists.filterControlledBy(list, ai);
} else if (sa.hasParam("AttachedTo")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (SpellAbility attach : c.getSpellAbilities()) {
if ("Pump".equals(attach.getParam("AILogic"))) {
return true; //only use good auras
}
list = CardLists.filter(list, c -> {
for (SpellAbility attach : c.getSpellAbilities()) {
if ("Pump".equals(attach.getParam("AILogic"))) {
return true; //only use good auras
}
return false;
}
return false;
});
}
}
@@ -1135,16 +1109,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!sa.hasParam("AITgtOwnCards")) {
list = CardLists.filterControlledBy(list, ai.getOpponents());
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card aura : c.getEnchantedBy()) {
if (c.getOwner().isOpponentOf(ai) && aura.getController().equals(ai)) {
return false;
}
list = CardLists.filter(list, c -> {
for (Card aura : c.getEnchantedBy()) {
if (c.getOwner().isOpponentOf(ai) && aura.getController().equals(ai)) {
return false;
}
return true;
}
return true;
});
}
@@ -1613,22 +1584,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
// Save a card as a default, in case we can't find anything suitable.
Card first = fetchList.get(0);
if (ZoneType.Battlefield.equals(destination)) {
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !decider.isCardInPlay(c.getName());
}
return true;
fetchList = CardLists.filter(fetchList, c1 -> {
if (c1.getType().isLegendary()) {
return !decider.isCardInPlay(c1.getName());
}
return true;
});
if (player.isOpponentOf(decider) && sa.hasParam("GainControl") && activator.equals(decider)) {
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !ComputerUtilCard.isCardRemAIDeck(c) && !ComputerUtilCard.isCardRemRandomDeck(c);
}
});
fetchList = CardLists.filter(fetchList, c12 -> !ComputerUtilCard.isCardRemAIDeck(c12) && !ComputerUtilCard.isCardRemRandomDeck(c12));
}
}
if (ZoneType.Exile.equals(destination) || origin.contains(ZoneType.Battlefield)
@@ -1715,53 +1678,50 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
private static CardCollection prefilterOwnListForBounceAnyNum(CardCollection fetchList, Player decider) {
fetchList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
if (card.isToken()) {
return false;
fetchList = CardLists.filter(fetchList, card -> {
if (card.isToken()) {
return false;
}
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
return true;
}
if (card.isEquipped()) {
return false;
}
if (card.isEnchanted()) {
for (Card enc : card.getEnchantedBy()) {
if (enc.getOwner().isOpponentOf(decider)) {
return true;
}
}
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
return true;
}
if (card.isEquipped()) {
return false;
}
if (card.isEnchanted()) {
for (Card enc : card.getEnchantedBy()) {
if (enc.getOwner().isOpponentOf(decider)) {
return false;
}
if (card.hasCounters()) {
if (card.isPlaneswalker()) {
int maxLoyaltyToConsider = 2;
int loyaltyDiff = 2;
int chance = 30;
if (decider.getController().isAI()) {
AiController aic = ((PlayerControllerAi) decider.getController()).getAi();
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
}
if (MyRandom.percentTrue(chance)) {
int curLoyalty = card.getCounters(CounterEnumType.LOYALTY);
int freshLoyalty = Integer.parseInt(card.getCurrentState().getBaseLoyalty());
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
return true;
}
}
return false;
} else if (card.isCreature() && card.getCounters(CounterEnumType.M1M1) > 0) {
return true;
}
if (card.hasCounters()) {
if (card.isPlaneswalker()) {
int maxLoyaltyToConsider = 2;
int loyaltyDiff = 2;
int chance = 30;
if (decider.getController().isAI()) {
AiController aic = ((PlayerControllerAi) decider.getController()).getAi();
maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
}
if (MyRandom.percentTrue(chance)) {
int curLoyalty = card.getCounters(CounterEnumType.LOYALTY);
int freshLoyalty = Integer.parseInt(card.getCurrentState().getBaseLoyalty());
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
return true;
}
}
} else if (card.isCreature() && card.getCounters(CounterEnumType.M1M1) > 0) {
return true;
}
return false; // TODO: improve for other counters
} else if (card.isAura()) {
return false;
}
return true;
return false; // TODO: improve for other counters
} else if (card.isAura()) {
return false;
}
return true;
});
return fetchList;
@@ -1866,14 +1826,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
listGoal = CardLists.getValidCards(listGoal, curGoal + (curGoal.contains(".") ? "+" : ".") + "cmcGE" + goalCMC, source.getController(), source, sa);
}
listGoal = CardLists.filter(listGoal, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
}
return true;
listGoal = CardLists.filter(listGoal, c -> {
if (c.getType().isLegendary()) {
return !ai.isCardInPlay(c.getName());
}
return true;
});
if (!listGoal.isEmpty()) {
@@ -2014,17 +1971,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
exiledBy.add(exiled);
}
}
scanList = CardLists.filter(scanList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (exiledBy.isEmpty()) {
return true;
}
for (Card c : exiledBy) {
return !c.getType().sharesCardTypeWith(card.getType());
}
scanList = CardLists.filter(scanList, card -> {
if (exiledBy.isEmpty()) {
return true;
}
for (Card c : exiledBy) {
return !c.getType().sharesCardTypeWith(card.getType());
}
return true;
});
}
@@ -2061,12 +2015,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
final CardType.CoreType determinedMaxType = maxType;
CardCollection preferredList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.getType().hasType(determinedMaxType);
}
});
CardCollection preferredList = CardLists.filter(fetchList, card -> card.getType().hasType(determinedMaxType));
CardCollection preferredOppList = CardLists.filter(preferredList, CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()));
if (!preferredOppList.isEmpty()) {
@@ -2078,22 +2027,19 @@ public class ChangeZoneAi extends SpellAbilityAi {
return Aggregates.random(fetchList);
}
CardCollection preferredList = CardLists.filter(fetchList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean playerPref = true;
if (isCurse) {
playerPref = card.getController().isOpponentOf(aiPlayer);
} else if (isOwnOnly) {
playerPref = card.getController().equals(aiPlayer) || !card.getController().isOpponentOf(aiPlayer);
}
if (!playerPref) {
return false;
}
return card.isValid(valid, aiPlayer, host, sa); // for things like ExilePreference:Land.Basic
CardCollection preferredList = CardLists.filter(fetchList, card -> {
boolean playerPref = true;
if (isCurse) {
playerPref = card.getController().isOpponentOf(aiPlayer);
} else if (isOwnOnly) {
playerPref = card.getController().equals(aiPlayer) || !card.getController().isOpponentOf(aiPlayer);
}
if (!playerPref) {
return false;
}
return card.isValid(valid, aiPlayer, host, sa); // for things like ExilePreference:Land.Basic
});
if (!preferredList.isEmpty()) {

View File

@@ -4,7 +4,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -85,15 +84,12 @@ public class ChooseCardAi extends SpellAbilityAi {
return false;
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
choices = CardLists.filter(choices, c -> {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
});
return !choices.isEmpty();
} else if (aiLogic.equals("Ashiok")) {
@@ -201,15 +197,12 @@ public class ChooseCardAi extends SpellAbilityAi {
} else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
CardCollectionView better = CardLists.filter(options, c -> {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
});
if (!better.isEmpty()) {
choice = ComputerUtilCard.getBestAI(better);
@@ -235,26 +228,23 @@ public class ChooseCardAi extends SpellAbilityAi {
choice = creats.get(0);
}
} else if ("NegativePowerFirst".equals(logic)) {
Card lowest = Aggregates.itemWithMin(options, CardPredicates.Accessors.fnGetNetPower);
Card lowest = Aggregates.itemWithMin(options, Card::getNetPower);
if (lowest.getNetPower() <= 0) {
choice = lowest;
} else {
choice = ComputerUtilCard.getBestCreatureAI(options);
}
} else if ("TangleWire".equals(logic)) {
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
CardCollectionView betterList = CardLists.filter(options, c -> {
if (c.isCreature()) {
return false;
}
for (SpellAbility sa1 : c.getAllSpellAbilities()) {
if (sa1.getPayCosts().hasTapCost()) {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts().hasTapCost()) {
return false;
}
}
return true;
}
return true;
});
if (!betterList.isEmpty()) {
choice = betterList.get(0);

View File

@@ -3,9 +3,9 @@ package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.Direction;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -28,11 +28,11 @@ public class ChooseDirectionAi extends SpellAbilityAi {
CardCollection all = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.NONLAND_PERMANENTS);
CardCollection aiPermanent = CardLists.filterControlledBy(all, ai);
aiPermanent.remove(sa.getHostCard());
int aiValue = Aggregates.sum(aiPermanent, CardPredicates.Accessors.fnGetCmc);
int aiValue = Aggregates.sum(aiPermanent, Card::getCMC);
CardCollection left = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Left));
CardCollection right = CardLists.filterControlledBy(all, game.getNextPlayerAfter(ai, Direction.Right));
int leftValue = Aggregates.sum(left, CardPredicates.Accessors.fnGetCmc);
int rightValue = Aggregates.sum(right, CardPredicates.Accessors.fnGetCmc);
int leftValue = Aggregates.sum(left, Card::getCMC);
int rightValue = Aggregates.sum(right, Card::getCMC);
return aiValue <= leftValue && aiValue <= rightValue;
}
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.*;
@@ -85,12 +84,7 @@ public class ChooseGenericAi extends SpellAbilityAi {
} else if ("Random".equals(logic)) {
return Aggregates.random(spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility sp) {
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
}
}));
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, sp -> !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land")));
return Aggregates.random(filtered);
} else if ("PayUnlessCost".equals(logic)) {
for (final SpellAbility sp : spells) {

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -94,14 +93,11 @@ public class ChooseSourceAi extends SpellAbilityAi {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
}
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;
choices = CardLists.filter(choices, c -> {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
});
return !choices.isEmpty();
}
@@ -124,15 +120,12 @@ public class ChooseSourceAi extends SpellAbilityAi {
final Combat combat = game.getCombat();
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
List<Card> permanentSources = CardLists.filter(options, c -> {
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
});
// Try to choose the best creature for damage prevention.

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
@@ -32,12 +31,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
CardCollection list =
CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
}
});
list = CardLists.filter(list, c -> !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa));
object1 = ComputerUtilCard.getBestAI(list);
if (sa.hasParam("Defined")) {
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);

View File

@@ -20,7 +20,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -132,46 +131,43 @@ public class ControlGainAi extends SpellAbilityAi {
}
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!sa.canTarget(c)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
if (!c.canBeControlledBy(ai)) {
return false;
}
// do not take perm control on something that leaves the play end of turn
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (c.isCreature()) {
if (c.getNetCombatDamage() <= 0) {
return false;
}
// can not attack any opponent
boolean found = false;
for (final Player opp : opponents) {
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// do not take control on something it doesn't know how to use
return !ComputerUtilCard.isCardRemAIDeck(c);
list = CardLists.filter(list, c -> {
if (!sa.canTarget(c)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
if (!c.canBeControlledBy(ai)) {
return false;
}
// do not take perm control on something that leaves the play end of turn
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (c.isCreature()) {
if (c.getNetCombatDamage() <= 0) {
return false;
}
// can not attack any opponent
boolean found = false;
for (final Player opp : opponents) {
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// do not take control on something it doesn't know how to use
return !ComputerUtilCard.isCardRemAIDeck(c);
});
if (list.isEmpty()) {

View File

@@ -20,7 +20,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -49,12 +48,7 @@ public class ControlGainVariantAi extends SpellAbilityAi {
String logic = sa.getParam("AILogic");
if ("GainControlOwns".equals(logic)) {
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
}
});
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), crd -> crd.isCreature() && !crd.getController().equals(crd.getOwner()));
if (list.isEmpty()) {
return false;
}

View File

@@ -4,7 +4,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -176,12 +175,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (!c.getType().isLegendary() || canCopyLegendary) || !c.getController().equals(aiPlayer);
}
});
list = CardLists.filter(list, c -> (!c.getType().isLegendary() || canCopyLegendary) || !c.getController().equals(aiPlayer));
Card choice;
if (Iterables.any(list, Presets.CREATURES)) {
if (sa.hasParam("TargetingPlayer")) {

View File

@@ -19,7 +19,6 @@ package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
@@ -109,12 +108,7 @@ public abstract class CountersAi extends SpellAbilityAi {
choice = ComputerUtilCard.getBestLandToAnimate(list);
}
} else if (type.equals("DIVINITY")) {
final CardCollection boon = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getCounters(CounterEnumType.DIVINITY) == 0;
}
});
final CardCollection boon = CardLists.filter(list, c -> c.getCounters(CounterEnumType.DIVINITY) == 0);
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon);
} else if (CounterType.get(type).isKeywordCounter()) {
choice = ComputerUtilCard.getBestCreatureAI(CardLists.getNotKeyword(list, type));

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
@@ -261,26 +260,21 @@ public class CountersMoveAi extends SpellAbilityAi {
// prefered logic for this: try to steal counter
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// do not weak a useless creature if able
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
final Card srcCardCpy = CardCopyService.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die this way
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
}
return true;
List<Card> best = CardLists.filter(oppList, card -> {
// do not weak a useless creature if able
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
final Card srcCardCpy = CardCopyService.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die this way
if (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
}
return true;
});
// if no Prefered found, try normal list
@@ -303,33 +297,29 @@ public class CountersMoveAi extends SpellAbilityAi {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
// try to remove P1P1 from undying or evolve
if (cType != null && cType.is(CounterEnumType.P1P1)) {
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|| card.hasKeyword(Keyword.ADAPT)) {
return true;
}
}
if (cType != null && cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
return true;
}
return false;
List<Card> best = CardLists.filter(aiList, card -> {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
// try to remove P1P1 from undying or evolve
if (cType != null && cType.is(CounterEnumType.P1P1)) {
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|| card.hasKeyword(Keyword.ADAPT)) {
return true;
}
}
if (cType != null && cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
return true;
}
return false;
});
if (best.isEmpty()) {
@@ -379,34 +369,30 @@ public class CountersMoveAi extends SpellAbilityAi {
if (ComputerUtilCard.evaluateCreature(lkiWithCounters) > ComputerUtilCard.evaluateCreature(lkiWithoutCounters)) {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
return false;
}
if (cType.is(CounterEnumType.M1M1)) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return true;
List<Card> best = CardLists.filter(aiList, card -> {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
return false;
}
if (cType.is(CounterEnumType.M1M1)) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return true;
});
if (best.isEmpty()) {
@@ -432,22 +418,18 @@ public class CountersMoveAi extends SpellAbilityAi {
// try to move to something useless or something that would leave play
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
return false;
List<Card> best = CardLists.filter(oppList, card -> {
// gain from useless
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
return false;
});
if (best.isEmpty()) {

View File

@@ -4,7 +4,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -37,33 +36,28 @@ public class CountersMultiplyAi extends SpellAbilityAi {
// defined are mostly Self or Creatures you control
CardCollection list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
} else {
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
// has negative counter it would double
if (ComputerUtil.isNegativeCounter(e.getKey(), c)) {
return false;
}
}
}
return true;
list = CardLists.filter(list, c -> {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
} else {
for (Map.Entry<CounterType, Integer> e : c.getCounters().entrySet()) {
// has negative counter it would double
if (ComputerUtil.isNegativeCounter(e.getKey(), c)) {
return false;
}
}
}
return true;
});
if (list.isEmpty()) {
@@ -137,26 +131,21 @@ public class CountersMultiplyAi extends SpellAbilityAi {
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
// pre filter targetable cards with counters and can receive one of them
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
}
return true;
list = CardLists.filter(list, c -> {
if (!c.hasCounters()) {
return false;
}
if (counterType != null) {
if (c.getCounters(counterType) <= 0) {
return false;
}
if (!c.canReceiveCounters(counterType)) {
return false;
}
}
return true;
});
CardCollection aiList = CardLists.filterControlledBy(list, ai);

View File

@@ -4,7 +4,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -31,25 +30,22 @@ public class CountersProliferateAi extends SpellAbilityAi {
if (p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) {
allyExpOrEnergy = true;
}
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (!crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
return true;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), crd -> {
if (!crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
return true;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}));
}
@@ -58,25 +54,22 @@ public class CountersProliferateAi extends SpellAbilityAi {
for (final Player o : ai.getOpponents()) {
opponentPoison |= o.getPoisonCounters() > 0 && o.canReceiveCounters(CounterEnumType.POISON);
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (!crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), crd -> {
if (!crd.hasCounters()) {
return false;
}
if (crd.isPlaneswalker()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}));
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -171,13 +170,7 @@ public class CountersPutAi extends CountersAi {
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1));
}
});
oppCreatM1 = CardLists.filter(oppCreatM1, input -> input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1)));
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
if (best != null) {
@@ -188,17 +181,14 @@ public class CountersPutAi extends CountersAi {
CardCollection aiCreat = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
aiCreat = CardLists.filter(aiCreat, CardPredicates.hasCounters());
aiCreat = CardLists.filter(aiCreat, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
for (CounterType counterType : input.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(counterType, input)
&& input.canReceiveCounters(counterType)) {
return true;
}
aiCreat = CardLists.filter(aiCreat, input -> {
for (CounterType counterType : input.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(counterType, input)
&& input.canReceiveCounters(counterType)) {
return true;
}
return false;
}
return false;
});
// TODO check whole state which creature would be the best
@@ -251,8 +241,8 @@ public class CountersPutAi extends CountersAi {
} else if (ai.getGame().getCombat().isBlocking(source)) {
// when blocking, consider this if it's possible to save the blocker and/or kill at least one attacker
CardCollection blocked = ai.getGame().getCombat().getAttackersBlockedBy(source);
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
int totBlkPower = Aggregates.sum(blocked, Card::getNetPower);
int totBlkToughness = Aggregates.min(blocked, Card::getNetToughness);
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
if (source.getNetToughness() + numActivations > totBlkPower
@@ -327,7 +317,7 @@ public class CountersPutAi extends CountersAi {
if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetNetToughness);
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, Card::getNetToughness);
if (leastToughness.isEmpty()) {
return false;
}
@@ -389,7 +379,7 @@ public class CountersPutAi extends CountersAi {
sa.setXManaCostPaid(amount);
} else if ("ExiledCreatureFromGraveCMC".equals(logic)) {
// e.g. Necropolis
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc);
amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), Card::getCMC);
if (amount > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) {
return true;
}
@@ -473,24 +463,21 @@ public class CountersPutAi extends CountersAi {
list = ComputerUtil.getSafeTargets(ai, sa, ai.getCardsIn(ZoneType.Battlefield));
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// don't put the counter on the dead creature
if (sacSelf && c.equals(source)) {
return false;
} else if (hasSacCost && !ComputerUtil.shouldSacrificeThreatenedCard(ai, c, sa)) {
return false;
}
if ("NoCounterOfType".equals(sa.getParam("AILogic"))) {
for (String ctrType : types) {
if (c.getCounters(CounterType.getType(ctrType)) > 0) {
return false;
}
list = CardLists.filter(list, c -> {
// don't put the counter on the dead creature
if (sacSelf && c.equals(source)) {
return false;
} else if (hasSacCost && !ComputerUtil.shouldSacrificeThreatenedCard(ai, c, sa)) {
return false;
}
if ("NoCounterOfType".equals(sa.getParam("AILogic"))) {
for (String ctrType : types) {
if (c.getCounters(CounterType.getType(ctrType)) > 0) {
return false;
}
}
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
}
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
});
// Filter AI-specific targets if provided
@@ -928,13 +915,9 @@ public class CountersPutAi extends CountersAi {
final int tributeAmount = AbilityUtils.calculateAmount(source, amountStr, sa);
final boolean isHaste = source.hasKeyword(Keyword.HASTE);
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c, !isHaste)
&& (c.getNetToughness() > source.getNetPower() + tributeAmount || c.hasKeyword(Keyword.DEATHTOUCH));
}
});
List<Card> threatening = CardLists.filter(creats, c -> CombatUtil.canBlock(source, c, !isHaste)
&& (c.getNetToughness() > source.getNetPower() + tributeAmount || c.hasKeyword(Keyword.DEATHTOUCH))
);
if (!threatening.isEmpty()) {
return true;
}
@@ -946,12 +929,9 @@ public class CountersPutAi extends CountersAi {
return false;
} else if (logic.equals("CanBlockThisTurn")) {
// pump haste
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c) && (c.getNetToughness() > source.getNetPower() || c.hasKeyword(Keyword.DEATHTOUCH));
}
});
List<Card> canBlock = CardLists.filter(creats, c -> CombatUtil.canBlock(source, c)
&& (c.getNetToughness() > source.getNetPower() || c.hasKeyword(Keyword.DEATHTOUCH))
);
if (!canBlock.isEmpty()) {
return false;
}
@@ -1003,22 +983,19 @@ public class CountersPutAi extends CountersAi {
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
if (!opponents.isEmpty()) {
final CardCollection negative = CardLists.filter(opponents, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (input.hasSVar("EndOfTurnLeavePlay"))
return false;
if (ComputerUtilCard.isUselessCreature(ai, input))
return false;
for (CounterType type : types) {
if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
return true;
if (ComputerUtil.isNegativeCounter(type, input)) {
return true;
}
}
final CardCollection negative = CardLists.filter(opponents, input -> {
if (input.hasSVar("EndOfTurnLeavePlay"))
return false;
if (ComputerUtilCard.isUselessCreature(ai, input))
return false;
for (CounterType type : types) {
if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
return true;
if (ComputerUtil.isNegativeCounter(type, input)) {
return true;
}
}
return false;
});
if (!negative.isEmpty()) {
return ComputerUtilCard.getBestAI(negative);
@@ -1053,13 +1030,10 @@ public class CountersPutAi extends CountersAi {
if (doNotHaveKeyword.size() > 0)
filtered = doNotHaveKeyword;
final CardCollection notUseless = CardLists.filter(filtered, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (input.hasSVar("EndOfTurnLeavePlay"))
return false;
return !ComputerUtilCard.isUselessCreature(ai, input);
}
final CardCollection notUseless = CardLists.filter(filtered, input -> {
if (input.hasSVar("EndOfTurnLeavePlay"))
return false;
return !ComputerUtilCard.isUselessCreature(ai, input);
});
if (!notUseless.isEmpty()) {
@@ -1069,26 +1043,20 @@ public class CountersPutAi extends CountersAi {
// some special logic to reload Persist/Undying
for (CounterType type : types) {
if (p1p1.equals(type)) {
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (!input.hasKeyword(Keyword.PERSIST))
return false;
return input.getCounters(m1m1) <= amount;
}
final CardCollection persist = CardLists.filter(filtered, input -> {
if (!input.hasKeyword(Keyword.PERSIST))
return false;
return input.getCounters(m1m1) <= amount;
});
if (!persist.isEmpty()) {
filtered = persist;
}
} else if (m1m1.equals(type)) {
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (!input.hasKeyword(Keyword.UNDYING))
return false;
return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
}
final CardCollection undying = CardLists.filter(filtered, input -> {
if (!input.hasKeyword(Keyword.UNDYING))
return false;
return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
});
if (!undying.isEmpty()) {
@@ -1161,15 +1129,12 @@ public class CountersPutAi extends CountersAi {
CardCollection targets = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
targets.remove(source);
targets = CardLists.filter(targets, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean tgtThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|| (combat != null && ((combat.isBlocked(card) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, card, combat))
|| (combat.isBlocking(card) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, card, combat))));
// when threatened, any non-threatened target is good to preserve the counter
return !tgtThreatened && (threatened || ComputerUtilCard.evaluateCreature(card, false, false) > ComputerUtilCard.evaluateCreature(source, false, false) + creatDiff);
}
targets = CardLists.filter(targets, card -> {
boolean tgtThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|| (combat != null && ((combat.isBlocked(card) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, card, combat))
|| (combat.isBlocking(card) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, card, combat))));
// when threatened, any non-threatened target is good to preserve the counter
return !tgtThreatened && (threatened || ComputerUtilCard.evaluateCreature(card, false, false) > ComputerUtilCard.evaluateCreature(source, false, false) + creatDiff);
});
Card bestTgt = ComputerUtilCard.getBestCreatureAI(targets);
@@ -1194,7 +1159,7 @@ public class CountersPutAi extends CountersAi {
}
}
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
int totBlkPower = Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
if (source.getNetToughness() <= totBlkPower
&& source.getNetToughness() + amount > totBlkPower) {
return true;
@@ -1208,7 +1173,7 @@ public class CountersPutAi extends CountersAi {
}
}
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), Card::getNetPower);
if (source.getNetToughness() <= totAtkPower
&& source.getNetToughness() + amount > totAtkPower) {
return true;
@@ -1229,7 +1194,7 @@ public class CountersPutAi extends CountersAi {
Card source = sa.getHostCard();
CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Creature"));
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
int maxCMC = Aggregates.max(ownLib, CardPredicates.Accessors.fnGetCmc);
int maxCMC = Aggregates.max(ownLib, Card::getCMC);
int optimalCMC = 0;
int curAmount = 0;
// Assume the AI knows its deck list and realizes what it has left in its library. Could be improved to make this less cheat-y.
@@ -1247,7 +1212,7 @@ public class CountersPutAi extends CountersAi {
Card source = sa.getHostCard();
CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.NONLAND_PERMANENTS);
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
int maxCMC = Aggregates.max(oppInPlay, CardPredicates.Accessors.fnGetCmc);
int maxCMC = Aggregates.max(oppInPlay, Card::getCMC);
int optimalCMC = 0;
int curAmount = 0;
for (int cmc = numCtrs; cmc <= maxCMC; cmc++) {

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCost;
@@ -95,12 +94,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
if (curse) {
if (type.equals("M1M1")) {
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetToughness() <= amount;
}
});
final List<Card> killable = CardLists.filter(hList, c -> c.getNetToughness() <= amount);
if (!(killable.size() > 2)) {
return false;
}

View File

@@ -107,12 +107,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
if (!countersList.isEmpty()) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
if (maritEmpty) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
@@ -237,12 +232,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
if (maritEmpty) {
return CounterType.get(CounterEnumType.ICE);
@@ -288,12 +278,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
} else {
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
if (maritEmpty) {
return false;

View File

@@ -118,12 +118,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
list = ComputerUtil.filterAITgts(sa, ai, list, false);
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, (Predicate<Card>) Card::ignoreLegendRule);
if (type.matches("All")) {
// Logic Part for Vampire Hexmage

View File

@@ -253,12 +253,7 @@ public class DamageAllAi extends SpellAbilityAi {
CardCollection list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC, source.getController(), source, sa);
final Predicate<Card> filterKillable = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c, false);
}
};
final Predicate<Card> filterKillable = c -> ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c, false);
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
list = CardLists.filter(list, filterKillable);

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -171,7 +170,7 @@ public class DamageDealAi extends DamageAiBase {
} else if ("WildHunt".equals(logic)) {
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source, sa);
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
dmg = Aggregates.sum(wolves, Card::getNetPower);
} else if ("Triskelion".equals(logic)) {
final int n = source.getCounters(CounterEnumType.P1P1);
if (n > 0) {
@@ -337,16 +336,11 @@ public class DamageDealAi extends DamageAiBase {
// Filter MustTarget requirements
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c)
&& !c.hasSVar("SacMe")
&& !ComputerUtilCard.hasActiveUndyingOrPersist(c);
}
});
CardCollection killables = CardLists.filter(hPlay, c -> c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c)
&& !c.hasSVar("SacMe")
&& !ComputerUtilCard.hasActiveUndyingOrPersist(c));
// Filter AI-specific targets if provided
killables = ComputerUtil.filterAITgts(sa, ai, killables, true);
@@ -416,15 +410,10 @@ public class DamageDealAi extends DamageAiBase {
final Game game = source.getGame();
List<Card> hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS);
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c)
&& !c.hasSVar("SacMe");
}
});
CardCollection killables = CardLists.filter(hPlay, c -> c.getSVar("Targeting").equals("Dies")
|| (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d)
&& !ComputerUtil.canRegenerate(ai, c)
&& !c.hasSVar("SacMe"));
// Filter AI-specific targets if provided
killables = ComputerUtil.filterAITgts(sa, ai, killables, true);

View File

@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -69,18 +68,15 @@ public class DebuffAi extends SpellAbilityAi {
List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
return Iterables.any(cards, c -> {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false;
}
// don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false;
}
// don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
});
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
@@ -172,11 +168,8 @@ public class DebuffAi extends SpellAbilityAi {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
}
list = CardLists.filter(list, c -> {
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
});
}
return list;

View File

@@ -1,10 +1,8 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.card.mana.ManaCost;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
@@ -60,38 +58,35 @@ public class DelayedTriggerAi extends SpellAbilityAi {
// fetch Instant or Sorcery and AI has reason to play this turn
// does not try to get itself
final ManaCost costSa = sa.getPayCosts().getTotalMana();
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!(c.isInstant() || c.isSorcery()) || c.equals(sa.getHostCard())) {
return false;
}
for (SpellAbility ab : c.getSpellAbilities()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|| ab.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
} else if ("SpellCopy".equals(ab.getParam("AILogic")) && ab.getApi() == ApiType.DelayedTrigger) {
// don't copy another copy spell, too complex for the AI
continue;
}
if (!ab.canPlay()) {
continue;
}
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
// see if we can pay both for this spell and for the Effect spell we're considering
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
ManaCost costAb = ab.getPayCosts().getTotalMana();
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0, true)) {
return true;
}
}
}
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), c -> {
if (!(c.isInstant() || c.isSorcery()) || c.equals(sa.getHostCard())) {
return false;
}
for (SpellAbility ab : c.getSpellAbilities()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|| ab.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
} else if ("SpellCopy".equals(ab.getParam("AILogic")) && ab.getApi() == ApiType.DelayedTrigger) {
// don't copy another copy spell, too complex for the AI
continue;
}
if (!ab.canPlay()) {
continue;
}
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
// see if we can pay both for this spell and for the Effect spell we're considering
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
ManaCost costAb = ab.getPayCosts().getTotalMana();
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0, true)) {
return true;
}
}
}
return false;
});
if (count == 0) {
@@ -106,30 +101,27 @@ public class DelayedTriggerAi extends SpellAbilityAi {
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
// only need count, not the list
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!(c.isInstant() || c.isSorcery()) || c.hasKeyword(Keyword.REBOUND)) {
return false;
}
for (SpellAbility ab : c.getSpellAbilities()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|| ab.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
}
if (!ab.canPlay()) {
continue;
}
AiPlayDecision decision = ((PlayerControllerAi) ai.getController()).getAi().canPlaySa(ab);
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
if (ComputerUtilMana.canPayManaCost(ab, ai, 0, true)) {
return true;
}
}
}
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), c -> {
if (!(c.isInstant() || c.isSorcery()) || c.hasKeyword(Keyword.REBOUND)) {
return false;
}
for (SpellAbility ab : c.getSpellAbilities()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(ComputerUtilAbility.getAbilitySourceName(ab))
|| ab.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
}
if (!ab.canPlay()) {
continue;
}
AiPlayDecision decision = ((PlayerControllerAi) ai.getController()).getAi().canPlaySa(ab);
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
if (ComputerUtilMana.canPayManaCost(ab, ai, 0, true)) {
return true;
}
}
}
return false;
});
if (count == 0) {
@@ -140,15 +132,12 @@ public class DelayedTriggerAi extends SpellAbilityAi {
} else if (logic.equals("SaveCreature")) {
CardCollection ownCreatures = ai.getCreaturesInPlay();
ownCreatures = CardLists.filter(ownCreatures, new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
return ComputerUtil.predictCreatureWillDieThisTurn(ai, card, sa);
ownCreatures = CardLists.filter(ownCreatures, card -> {
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
return ComputerUtil.predictCreatureWillDieThisTurn(ai, card, sa);
});
if (!ownCreatures.isEmpty()) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
@@ -170,30 +169,27 @@ public class DestroyAi extends SpellAbilityAi {
if (!playReusable(ai, sa)) {
list = CardLists.filter(list, Predicates.not(CardPredicates.hasCounter(CounterEnumType.SHIELD, 1)));
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
list = CardLists.filter(list, c -> {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
}
}
if (c.hasSVar("SacMe")) {
return false;
}
//Check for undying
return !c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0;
}
if (c.hasSVar("SacMe")) {
return false;
}
//Check for undying
return !c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0;
});
}
@@ -201,12 +197,7 @@ public class DestroyAi extends SpellAbilityAi {
// regeneration shield
if (!noRegen) {
// TODO filter out things that might be tougher?
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c);
}
});
list = CardLists.filter(list, c -> c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
}
// Try to avoid targeting creatures that are dead on board
@@ -340,12 +331,7 @@ public class DestroyAi extends SpellAbilityAi {
if (!noRegen) {
// TODO filter out things that could regenerate in response?
// might be tougher?
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShieldCount() == 0;
}
});
preferred = CardLists.filter(preferred, c -> c.getShieldCount() == 0);
}
// Filter AI-specific targets if provided

View File

@@ -19,12 +19,7 @@ import forge.game.zone.ZoneType;
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getCounters(CounterEnumType.SHIELD) > 0 || c.hasSVar("SacMe"));
}
};
private static final Predicate<Card> predicate = c -> !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getCounters(CounterEnumType.SHIELD) > 0 || c.hasSVar("SacMe"));
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.*;
@@ -29,7 +28,6 @@ import forge.game.zone.MagicStack;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -173,22 +171,12 @@ public class EffectAi extends SpellAbilityAi {
List<Card> human = opp.getCreaturesInPlay();
// only count creatures that can attack or block
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
comp = CardLists.filter(comp, c -> CombatUtil.canAttack(c, opp));
if (comp.size() < 2) {
continue;
}
final List<Card> attackers = comp;
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canBlockAtLeastOne(c, attackers);
}
});
human = CardLists.filter(human, c -> CombatUtil.canBlockAtLeastOne(c, attackers));
if (human.isEmpty()) {
continue;
}
@@ -345,24 +333,20 @@ public class EffectAi extends SpellAbilityAi {
} else if (logic.equals("CantRegenerate")) {
if (sa.usesTargeting()) {
CardCollection list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.filter(list, CardPredicates.Presets.CAN_BE_DESTROYED, new Predicate<Card>() {
@Override
public boolean apply(@Nullable Card input) {
Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(input);
runParams.put(AbilityKey.Regeneration, true);
List<ReplacementEffect> repDestoryList = game.getReplacementHandler().getReplacementList(ReplacementType.Destroy, runParams, ReplacementLayer.Other);
// no Destroy Replacement, or one non-Regeneration one like Totem-Armor
if (repDestoryList.isEmpty() || Iterables.any(repDestoryList, Predicates.not(CardTraitPredicates.hasParam("Regeneration")))) {
return false;
}
if (cantRegenerateCheckCombat(input) || cantRegenerateCheckStack(input)) {
return true;
}
list = CardLists.filter(list, CardPredicates.Presets.CAN_BE_DESTROYED, input -> {
Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(input);
runParams.put(AbilityKey.Regeneration, true);
List<ReplacementEffect> repDestoryList = game.getReplacementHandler().getReplacementList(ReplacementType.Destroy, runParams, ReplacementLayer.Other);
// no Destroy Replacement, or one non-Regeneration one like Totem-Armor
if (repDestoryList.isEmpty() || Iterables.any(repDestoryList, Predicates.not(CardTraitPredicates.hasParam("Regeneration")))) {
return false;
}
if (cantRegenerateCheckCombat(input) || cantRegenerateCheckStack(input)) {
return true;
}
return false;
});
if (list.isEmpty()) {

View File

@@ -20,8 +20,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
@@ -91,24 +89,16 @@ public final class EncodeAi extends SpellAbilityAi {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean canAttackOpponent = false;
for (Player opp : ai.getOpponents()) {
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, null, opp)) {
canAttackOpponent = true;
break;
}
final List<Card> attackers = CardLists.filter(list, ComputerUtilCombat::canAttackNextTurn);
final List<Card> unblockables = CardLists.filter(attackers, c -> {
boolean canAttackOpponent = false;
for (Player opp : ai.getOpponents()) {
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, null, opp)) {
canAttackOpponent = true;
break;
}
return canAttackOpponent;
}
return canAttackOpponent;
});
if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables);

View File

@@ -2,9 +2,7 @@ package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.phase.PhaseHandler;
@@ -26,12 +24,7 @@ public class FlipOntoBattlefieldAi extends SpellAbilityAi {
if ("DamageCreatures".equals(logic)) {
int maxToughness = Integer.parseInt(sa.getSubAbility().getParam("NumDmg"));
CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.getNetToughness() <= maxToughness && card.canBeDestroyed();
}
});
CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), card -> card.getNetToughness() <= maxToughness && card.canBeDestroyed());
return !rightToughness.isEmpty();
}

View File

@@ -2,8 +2,6 @@ package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
@@ -32,25 +30,22 @@ public class GoadAi extends SpellAbilityAi {
if (game.getPlayers().size() > 2) {
// use this part only in multiplayer
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
// filter only creatures which can attack
if (ComputerUtilCard.isUselessCreature(ai, c)) {
return false;
}
// useless
if (c.isGoadedBy(ai)) {
return false;
}
// select creatures which can attack an Opponent other than ai
for (Player o : ai.getOpponents()) {
if (ComputerUtilCombat.canAttackNextTurn(c, o)) {
return true;
}
}
CardCollection betterList = CardLists.filter(list, c -> {
// filter only creatures which can attack
if (ComputerUtilCard.isUselessCreature(ai, c)) {
return false;
}
// useless
if (c.isGoadedBy(ai)) {
return false;
}
// select creatures which can attack an Opponent other than ai
for (Player o : ai.getOpponents()) {
if (ComputerUtilCombat.canAttackNextTurn(c, o)) {
return true;
}
}
return false;
});
// if better list is not empty, use that one instead
@@ -61,20 +56,17 @@ public class GoadAi extends SpellAbilityAi {
}
} else {
// single Player, goaded creature would attack ai
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
// filter only creatures which can attack
if (ComputerUtilCard.isUselessCreature(ai, c)) {
return false;
}
// useless
if (c.isGoadedBy(ai)) {
return false;
}
// select only creatures AI can block
return ComputerUtilCard.canBeBlockedProfitably(ai, c, false);
CardCollection betterList = CardLists.filter(list, c -> {
// filter only creatures which can attack
if (ComputerUtilCard.isUselessCreature(ai, c)) {
return false;
}
// useless
if (c.isGoadedBy(ai)) {
return false;
}
// select only creatures AI can block
return ComputerUtilCard.canBeBlockedProfitably(ai, c, false);
});
// if better list is not empty, use that one instead

View File

@@ -245,7 +245,7 @@ public class ManaAi extends SpellAbilityAi {
if (logic.startsWith("ManaRitualBattery")) {
// Don't remove more counters than would be needed to cast the more expensive thing we want to cast,
// otherwise the AI grabs too many counters at once.
int maxCtrs = Aggregates.max(castableSpells, CardPredicates.Accessors.fnGetCmc) - manaSurplus;
int maxCtrs = Aggregates.max(castableSpells, Card::getCMC) - manaSurplus;
sa.setXManaCostPaid(Math.min(numCounters, maxCtrs));
}

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
@@ -138,12 +137,7 @@ public abstract class ManifestBaseAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
if (Iterables.size(options) > 1 || isOptional) {
CardCollection filtered = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return shouldApply(input, ai, sa);
}
});
CardCollection filtered = CardLists.filter(options, input -> shouldApply(input, ai, sa));
if (!filtered.isEmpty()) {
return ComputerUtilCard.getBestAI(filtered);
}

View File

@@ -1,7 +1,6 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -159,13 +158,7 @@ public class MillAi extends SpellAbilityAi {
}
// select Player which would cause the most damage
// JAVA 1.8 use Map.Entry.comparingByValue()
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), new Comparator<Map.Entry<Player,Integer>>(){
@Override
public int compare(Map.Entry<Player, Integer> o1, Map.Entry<Player, Integer> o2) {
return o1.getValue() - o2.getValue();
}
});
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), Map.Entry.comparingByValue());
sa.getTargets().add(max.getKey());
}

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.AiCardMemory;
@@ -122,28 +121,24 @@ public class MustBlockAi extends SpellAbilityAi {
private List<Card> determineBlockerFromList(final Card attacker, final Player ai, Iterable<Card> options, SpellAbility sa,
final boolean onlyLethal, final boolean testTapped) {
List<Card> list = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean tapped = c.isTapped();
if (testTapped) {
c.setTapped(false);
}
if (!CombatUtil.canBlock(attacker, c)) {
return false;
}
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, c, null, false)) {
return false;
}
if (onlyLethal && !ComputerUtilCombat.canDestroyBlocker(ai, c, attacker, null, false)) {
return false;
}
if (testTapped) {
c.setTapped(tapped);
}
return true;
List<Card> list = CardLists.filter(options, c -> {
boolean tapped = c.isTapped();
if (testTapped) {
c.setTapped(false);
}
if (!CombatUtil.canBlock(attacker, c)) {
return false;
}
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, c, null, false)) {
return false;
}
if (onlyLethal && !ComputerUtilCombat.canDestroyBlocker(ai, c, attacker, null, false)) {
return false;
}
if (testTapped) {
c.setTapped(tapped);
}
return true;
});
return list;

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
@@ -29,12 +28,7 @@ public class MutateAi extends SpellAbilityAi {
CardPredicates.hasKeyword(Keyword.DEFENDER),
CardPredicates.hasKeyword("CARDNAME can't attack."),
CardPredicates.hasKeyword("CARDNAME can't block."),
new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return ComputerUtilCard.isUselessCreature(aiPlayer, card);
}
}
card -> ComputerUtilCard.isUselessCreature(aiPlayer, card)
)));
if (mutateTgts.isEmpty()) {

View File

@@ -3,8 +3,6 @@ package forge.ai.ability;
import forge.game.card.CardCopyService;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import forge.ai.AiController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
@@ -146,12 +144,9 @@ public class PermanentCreatureAi extends PermanentAi {
if (combat != null && combat.getDefendingPlayers().contains(ai)) {
// Currently we use a rather simplistic assumption that if we're behind on creature count on board,
// a flashed in creature might prove to be good as an additional defender
int numUntappedPotentialBlockers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return card.isUntapped() && !ComputerUtilCard.isUselessCreature(ai, card);
}
}).size();
int numUntappedPotentialBlockers = CardLists.filter(ai.getCreaturesInPlay(),
card1 -> card1.isUntapped() && !ComputerUtilCard.isUselessCreature(ai, card1)
).size();
if (combat.getAttackersOf(ai).size() > numUntappedPotentialBlockers) {
valuableBlocker = true;

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.card.CardStateName;
@@ -148,57 +147,54 @@ public class PlayAi extends SpellAbilityAi {
state = CardStateName.Original;
}
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
// of which spell was the reason for the choice can be used there
for (SpellAbility s : AbilityUtils.getSpellsFromPlayEffect(c, ai, state, false)) {
if (!sa.matchesValidParam("ValidSA", s)) {
continue;
}
if (s instanceof LandAbility) {
// might want to run some checks here but it's rare anyway
return true;
}
Spell spell = (Spell) s;
if (params != null && params.containsKey("CMCLimit")) {
Integer cmcLimit = (Integer) params.get("CMCLimit");
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
continue;
}
if (sa.hasParam("WithoutManaCost")) {
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
if (!(spell instanceof SpellPermanent)) {
if (spell.costHasManaX()) {
continue;
}
}
spell = (Spell) spell.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) {
Cost abCost;
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
abCost = new Cost(c.getManaCost(), false);
} else {
abCost = new Cost(sa.getParam("PlayCost"), false);
}
spell = (Spell) spell.copyWithManaCostReplaced(spell.getActivatingPlayer(), abCost);
}
if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !(isOptional || sa.hasParam("Optional")), true)) {
// Before accepting, see if the spell has a valid number of targets (it should at this point).
// Proceeding past this point if the spell is not correctly targeted will result
// in "Failed to add to stack" error and the card disappearing from the game completely.
if (!spell.isTargetNumberValid() || !ComputerUtilCost.canPayCost(spell, ai, true)) {
// if we won't be able to pay the cost, don't choose the card
return false;
}
return true;
}
List<Card> tgtCards = CardLists.filter(options, c -> {
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
// of which spell was the reason for the choice can be used there
for (SpellAbility s : AbilityUtils.getSpellsFromPlayEffect(c, ai, state, false)) {
if (!sa.matchesValidParam("ValidSA", s)) {
continue;
}
if (s instanceof LandAbility) {
// might want to run some checks here but it's rare anyway
return true;
}
Spell spell = (Spell) s;
if (params != null && params.containsKey("CMCLimit")) {
Integer cmcLimit = (Integer) params.get("CMCLimit");
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
continue;
}
if (sa.hasParam("WithoutManaCost")) {
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
if (!(spell instanceof SpellPermanent)) {
if (spell.costHasManaX()) {
continue;
}
}
spell = (Spell) spell.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) {
Cost abCost;
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
abCost = new Cost(c.getManaCost(), false);
} else {
abCost = new Cost(sa.getParam("PlayCost"), false);
}
spell = (Spell) spell.copyWithManaCostReplaced(spell.getActivatingPlayer(), abCost);
}
if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !(isOptional || sa.hasParam("Optional")), true)) {
// Before accepting, see if the spell has a valid number of targets (it should at this point).
// Proceeding past this point if the spell is not correctly targeted will result
// in "Failed to add to stack" error and the card disappearing from the game completely.
if (!spell.isTargetNumberValid() || !ComputerUtilCost.canPayCost(spell, ai, true)) {
// if we won't be able to pay the cost, don't choose the card
return false;
}
return true;
}
return false;
}
return false;
});
if (sa.hasParam("CastTransformed")) {

View File

@@ -1,7 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -86,17 +84,13 @@ public class PoisonAi extends SpellAbilityAi {
PlayerCollection tgts = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
if (!tgts.isEmpty()) {
// try to select a opponent that can lose through poison counters
PlayerCollection betterTgts = tgts.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return false;
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
return false;
}
return true;
PlayerCollection betterTgts = tgts.filter(input -> {
if (input.cantLose()) {
return false;
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
return false;
}
return true;
});
if (!betterTgts.isEmpty()) {
@@ -120,15 +114,11 @@ public class PoisonAi extends SpellAbilityAi {
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (!allies.isEmpty()) {
// some ally would be unaffected
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return true;
}
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
PlayerCollection betterAllies = allies.filter(input -> {
if (input.cantLose()) {
return true;
}
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
});
if (!betterAllies.isEmpty()) {
allies = betterAllies;

View File

@@ -2,8 +2,6 @@ package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -30,12 +28,7 @@ public class PowerExchangeAi extends SpellAbilityAi {
List<Card> list =
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeTargetedBy(sa) && c.getController() != ai;
}
});
list = CardLists.filter(list, c -> c.canBeTargetedBy(sa) && c.getController() != ai);
CardLists.sortByPowerDesc(list);
c1 = list.isEmpty() ? null : list.get(0);
if (sa.hasParam("Defined")) {

View File

@@ -3,8 +3,6 @@ package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
@@ -107,54 +105,51 @@ public class ProtectAi extends SpellAbilityAi {
CardCollection list = ai.getCreaturesInPlay();
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
// Don't add duplicate protections
if (hasProtectionFromAll(c, gains)) {
return false;
}
if (threatenedObjects.contains(c)) {
return true;
}
if (combat != null) {
//creature is blocking and would be destroyed itself
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
List<Card> threats = combat.getAttackersBlockedBy(c);
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
//creature is attacking and would be destroyed itself
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
CardCollection threats = combat.getBlockers(c);
if (threats != null && !threats.isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(threats);
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
}
}
//make unblockable
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(ai, c);
String s = aiAtk.toProtectAttacker(sa);
if (s == null) {
return false;
}
Player opponent = ai.getWeakestOpponent();
Combat combat = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
float ratio = 1.0f * dmg / opponent.getLife();
return MyRandom.getRandom().nextFloat() < ratio;
}
list = CardLists.filter(list, c -> {
if (!c.canBeTargetedBy(sa)) {
return false;
}
// Don't add duplicate protections
if (hasProtectionFromAll(c, gains)) {
return false;
}
if (threatenedObjects.contains(c)) {
return true;
}
if (combat != null) {
//creature is blocking and would be destroyed itself
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
List<Card> threats = combat.getAttackersBlockedBy(c);
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
//creature is attacking and would be destroyed itself
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
CardCollection threats = combat.getBlockers(c);
if (threats != null && !threats.isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(threats);
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
}
}
//make unblockable
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(ai, c);
String s = aiAtk.toProtectAttacker(sa);
if (s == null) {
return false;
}
Player opponent = ai.getWeakestOpponent();
Combat combat1 = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat1, true);
float ratio = 1.0f * dmg / opponent.getLife();
return MyRandom.getRandom().nextFloat() < ratio;
}
return false;
});
return list;
}
@@ -266,19 +261,9 @@ public class ProtectAi extends SpellAbilityAi {
}
CardCollection pref = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
}
});
pref = CardLists.filter(pref, c -> !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa)));
final CardCollection pref2 = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
}
});
pref = CardLists.filter(pref, c -> !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa)));
final List<Card> forced = CardLists.filterControlledBy(list, ai);
while (sa.canAddMoreTarget()) {

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -164,9 +163,52 @@ public class PumpAi extends PumpAiBase {
if (attr.isEmpty()) {
return false;
}
CardCollection best = CardLists.filter(attr, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
CardCollection best = CardLists.filter(attr, card -> {
int amount = 0;
if (StringUtils.isNumeric(amountStr)) {
amount = AbilityUtils.calculateAmount(source, amountStr, moveSA);
} else if (source.hasSVar(amountStr)) {
if ("Count$ChosenNumber".equals(source.getSVar(amountStr))) {
amount = card.getCounters(cType);
}
}
int i = card.getCounters(cType);
if (i < amount) {
return false;
}
final Card srcCardCpy = CardCopyService.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
if (cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken();
}
return false;
});
if (best.isEmpty()) {
best = attr;
}
final Card card = ComputerUtilCard.getBestCreatureAI(best);
sa.getTargets().add(card);
return true;
}
} else {
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (cType != null) {
list = CardLists.filter(list, CardPredicates.hasCounter(cType));
if (list.isEmpty()) {
return false;
}
List<Card> oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty() && !sameCtrl) {
List<Card> best = CardLists.filter(oppList, card -> {
int amount = 0;
if (StringUtils.isNumeric(amountStr)) {
amount = AbilityUtils.calculateAmount(source, amountStr, moveSA);
@@ -189,57 +231,7 @@ public class PumpAi extends PumpAiBase {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken();
}
return false;
}
});
if (best.isEmpty()) {
best = attr;
}
final Card card = ComputerUtilCard.getBestCreatureAI(best);
sa.getTargets().add(card);
return true;
}
} else {
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (cType != null) {
list = CardLists.filter(list, CardPredicates.hasCounter(cType));
if (list.isEmpty()) {
return false;
}
List<Card> oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty() && !sameCtrl) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
int amount = 0;
if (StringUtils.isNumeric(amountStr)) {
amount = AbilityUtils.calculateAmount(source, amountStr, moveSA);
} else if (source.hasSVar(amountStr)) {
if ("Count$ChosenNumber".equals(source.getSVar(amountStr))) {
amount = card.getCounters(cType);
}
}
int i = card.getCounters(cType);
if (i < amount) {
return false;
}
final Card srcCardCpy = CardCopyService.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
if (cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken();
}
return true;
}
return true;
});
if (best.isEmpty()) {
@@ -520,16 +512,13 @@ public class PumpAi extends PumpAiBase {
// Detain target nonland permanent: don't target noncreature permanents that don't have
// any activated abilities.
if ("DetainNonLand".equals(sa.getParam("AILogic"))) {
list = CardLists.filter(list, Predicates.or(CardPredicates.Presets.CREATURES, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.isActivatedAbility()) {
return true;
}
list = CardLists.filter(list, Predicates.or(CardPredicates.Presets.CREATURES, card -> {
for (SpellAbility sa1 : card.getSpellAbilities()) {
if (sa1.isActivatedAbility()) {
return true;
}
return false;
}
return false;
}));
}
@@ -548,12 +537,7 @@ public class PumpAi extends PumpAiBase {
if ("BetterCreatureThanSource".equals(sa.getParam("AILogic"))) {
// Don't target cards that are not better in value than the targeting card
final int sourceValue = ComputerUtilCard.evaluateCreature(source);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.isCreature() && ComputerUtilCard.evaluateCreature(card) > sourceValue + 30;
}
});
list = CardLists.filter(list, card -> card.isCreature() && ComputerUtilCard.evaluateCreature(card) > sourceValue + 30);
}
if ("ReplaySpell".equals(sa.getParam("AILogic"))) {
@@ -819,23 +803,11 @@ public class PumpAi extends PumpAiBase {
}
values.keySet().removeAll(toRemove);
// JAVA 1.8 use Map.Entry.comparingByValue()
data.put(opp, Collections.max(values.entrySet(), new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue() - o2.getValue();
}
}));
data.put(opp, Collections.max(values.entrySet(), Map.Entry.comparingByValue()));
}
if (!data.isEmpty()) {
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
@Override
public int compare(Map.Entry<Player, Map.Entry<String, Integer>> o1, Map.Entry<Player, Map.Entry<String, Integer>> o2) {
return o1.getValue().getValue() - o2.getValue().getValue();
}
});
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), Comparator.comparingInt(o -> o.getValue().getValue()));
// filter list again by the opponent and a creature of the wanted name that can be targeted
list = CardLists.filter(CardLists.filterControlledBy(list, max.getKey()),

View File

@@ -122,15 +122,12 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
return (combat != null && combat.isAttacking(c)) || CombatUtil.canAttack(c, card.getController());
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), c -> {
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
return (combat != null && combat.isAttacking(c)) || CombatUtil.canAttack(c, card.getController());
});
return CombatUtil.canBlockAtLeastOne(card, attackers);
}
@@ -140,17 +137,14 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
// the cards controller needs to be the one attacked
return (combat != null && combat.isAttacking(c) && card.getController().equals(combat.getDefenderPlayerByAttacker(c))) ||
CombatUtil.canAttack(c, card.getController());
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), c -> {
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
// the cards controller needs to be the one attacked
return (combat != null && combat.isAttacking(c) && card.getController().equals(combat.getDefenderPlayerByAttacker(c))) ||
CombatUtil.canAttack(c, card.getController());
});
return CombatUtil.canBlockAtLeastOne(card, attackers);
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
@@ -416,12 +410,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
protected CardCollection getPumpCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack,
final List<String> keywords, final boolean immediately) {
CardCollection list = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, attack, keywords, immediately);
}
});
list = CardLists.filter(list, c -> ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, attack, keywords, immediately));
return list;
}
@@ -449,14 +438,11 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
if (defense < 0) { // with spells that give -X/-X, compi will try to destroy a creature
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getSVar("Targeting").equals("Dies") || c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
list = CardLists.filter(list, c -> {
if (c.getSVar("Targeting").equals("Dies") || c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
}); // leaves all creatures that will be destroyed
} // -X/-X end
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
@@ -472,18 +458,15 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} else {
// Human active, only curse attacking creatures
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c)) {
return false;
}
if (c.getNetPower() > 0 && ai.getLife() < 5) {
return true;
}
//Don't waste a -7/-0 spell on a 1/1 creature
return c.getNetPower() + attack > -2 || c.getNetPower() > 3;
list = CardLists.filter(list, c -> {
if (combat == null || !combat.isAttacking(c)) {
return false;
}
if (c.getNetPower() > 0 && ai.getLife() < 5) {
return true;
}
//Don't waste a -7/-0 spell on a 1/1 creature
return c.getNetPower() + attack > -2 || c.getNetPower() > 3;
});
} else {
list = new CardCollection();
@@ -501,12 +484,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return containsUsefulKeyword(ai, keywords, c, sa, attack);
}
});
list = CardLists.filter(list, c -> containsUsefulKeyword(ai, keywords, c, sa, attack));
} else if (sa.hasParam("NumAtt") || sa.hasParam("NumDef")) {
// X is zero
list = new CardCollection();

View File

@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
@@ -85,23 +84,17 @@ public class PumpAllAi extends PumpAiBase {
if (sa.isCurse()) {
if (defense < 0) { // try to destroy creatures
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
comp = CardLists.filter(comp, c -> {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
}); // leaves all creatures that will be destroyed
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
human = CardLists.filter(human, c -> {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return ComputerUtilCombat.getDamageToKill(c, false) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE);
}); // leaves all creatures that will be destroyed
} // -X/-X end
else if (power < 0) { // -X/-0

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.card.Card;
@@ -56,12 +55,7 @@ public class RepeatAi extends SpellAbilityAi {
Card best = null;
Iterable<Card> targetableAi = Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa));
if (!logic.endsWith("IgnoreLegendary")) {
best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.ignoreLegendRule();
}
}));
best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, Card::ignoreLegendRule));
} else {
best = ComputerUtilCard.getBestAI(targetableAi);
}

View File

@@ -3,8 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
@@ -97,12 +95,7 @@ public class RepeatEachAi extends SpellAbilityAi {
return hitOpp;
} else if ("EquipAll".equals(logic)) {
if (aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
final CardCollection unequipped = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.isEquipment() && card.getAttachedTo() != sa.getHostCard();
}
});
final CardCollection unequipped = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), card -> card.isEquipment() && card.getAttachedTo() != sa.getHostCard());
return !unequipped.isEmpty();
}

View File

@@ -3,8 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -75,12 +73,7 @@ public class SetStateAi extends SpellAbilityAi {
sa.resetTargets();
// select only the ones that can transform
CardCollection list = CardLists.filter(CardUtil.getValidCardsToTarget(sa), CardPredicates.Presets.CREATURES, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return c.canTransform(sa);
}
});
CardCollection list = CardLists.filter(CardUtil.getValidCardsToTarget(sa), CardPredicates.Presets.CREATURES, c -> c.canTransform(sa));
if (list.isEmpty()) {
return false;

View File

@@ -109,39 +109,33 @@ public abstract class TapAiBase extends SpellAbilityAi {
final Game game = ai.getGame();
CardCollection tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.filter(tapList, Presets.CAN_TAP);
tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
tapList = CardLists.filter(tapList, c -> {
if (c.isCreature()) {
return true;
}
for (final SpellAbility sa1 : c.getSpellAbilities()) {
if (sa1.isAbility() && sa1.getPayCosts().hasTapCost()) {
return true;
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
return false;
}
return false;
});
//use broader approach when the cost is a positive thing
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
tapList = CardLists.filter(tapList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
tapList = CardLists.filter(tapList, c -> {
if (c.isCreature()) {
return true;
}
for (final SpellAbility sa12 : c.getSpellAbilities()) {
if (sa12.isAbility() && sa12.getPayCosts().hasTapCost()) {
return true;
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
return false;
}
return false;
});
}
@@ -187,12 +181,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
//Combat has already started
attackers = game.getCombat().getAttackers();
} else {
attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
attackers = CardLists.filter(ai.getCreaturesInPlay(), c -> CombatUtil.canAttack(c, opp));
attackers.remove(source);
}
Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers);
@@ -209,12 +198,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
&& phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
// Tap creatures possible blockers before combat during AI's turn.
if (Iterables.any(tapList, CardPredicates.Presets.CREATURES)) {
List<Card> creatureList = CardLists.filter(tapList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.isCreature() && CombatUtil.canAttack(c, opp);
}
});
List<Card> creatureList = CardLists.filter(tapList, c -> c.isCreature() && CombatUtil.canAttack(c, opp));
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
} else { // no creatures available
choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList);

View File

@@ -2,7 +2,6 @@ package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCombat;
@@ -75,12 +74,7 @@ public class TapAllAi extends SpellAbilityAi {
// in AI's turn, check if there are possible attackers, before tapping blockers
if (game.getPhaseHandler().isPlayerTurn(ai)) {
validTappables = ai.getCardsIn(ZoneType.Battlefield);
final boolean any = Iterables.any(validTappables, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c) && ComputerUtilCombat.canAttackNextTurn(c);
}
});
final boolean any = Iterables.any(validTappables, c -> CombatUtil.canAttack(c) && ComputerUtilCombat.canAttackNextTurn(c));
return any;
}
return true;

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.AiController;
@@ -182,12 +181,7 @@ public class TokenAi extends SpellAbilityAi {
} else {
// Flash Foliage
CardCollection list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return c.getLethalDamage() == 1;
}
});
CardCollection betterList = CardLists.filter(list, c -> c.getLethalDamage() == 1);
if (!betterList.isEmpty()) {
list = betterList;
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.*;
@@ -408,18 +407,15 @@ public class UntapAi extends SpellAbilityAi {
// (it may actually be possible to enable this for sorceries, but that'll need some canPlay shenanigans)
CardCollection playable = CardLists.filter(inHand, Presets.PERMANENTS);
CardCollection untappingCards = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
boolean hasUntapLandLogic = false;
for (SpellAbility sa : card.getSpellAbilities()) {
if ("PoolExtraMana".equals(sa.getParam("AILogic"))) {
hasUntapLandLogic = true;
break;
}
CardCollection untappingCards = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), card -> {
boolean hasUntapLandLogic = false;
for (SpellAbility sa1 : card.getSpellAbilities()) {
if ("PoolExtraMana".equals(sa1.getParam("AILogic"))) {
hasUntapLandLogic = true;
break;
}
return hasUntapLandLogic && card.isUntapped();
}
return hasUntapLandLogic && card.isUntapped();
});
// TODO: currently limited to Main 2, somehow improve to let the AI use this SA at other time?

View File

@@ -197,12 +197,7 @@ public class GameCopier {
// TODO update thisTurnCast
if (advanceToPhase != null) {
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, new Runnable() {
@Override
public void run() {
GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent());
}
});
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, () -> GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent()));
}
return newGame;

View File

@@ -252,32 +252,29 @@ public class GameSimulator {
// TODO: This needs to set an AI controller for all opponents, in case of multiplayer.
PlayerControllerAi sim = new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer());
sim.setUseSimulation(true);
opponent.runWithController(new Runnable() {
@Override
public void run() {
final Set<Card> allAffectedCards = new HashSet<>();
opponent.runWithController(() -> {
final Set<Card> allAffectedCards = new HashSet<>();
game.getAction().checkStateEffects(false, allAffectedCards);
game.getStack().addAllTriggeredAbilitiesToStack();
while (!game.getStack().isEmpty() && !game.isGameOver()) {
debugPrint("Resolving:" + game.getStack().peekAbility());
// Resolve the top effect on the stack.
game.getStack().resolveStack();
// Evaluate state based effects as a result of resolving stack.
// Note: Needs to happen after resolve stack rather than at the
// top of the loop to ensure state effects are evaluated after the
// last resolved effect
game.getAction().checkStateEffects(false, allAffectedCards);
// Add any triggers additional triggers as a result of the above.
// Must be below state effects, since legendary rule is evaluated
// as part of state effects and trigger come afterward. (e.g. to
// correctly handle two Dark Depths - one having no counters).
game.getStack().addAllTriggeredAbilitiesToStack();
while (!game.getStack().isEmpty() && !game.isGameOver()) {
debugPrint("Resolving:" + game.getStack().peekAbility());
// Resolve the top effect on the stack.
game.getStack().resolveStack();
// Evaluate state based effects as a result of resolving stack.
// Note: Needs to happen after resolve stack rather than at the
// top of the loop to ensure state effects are evaluated after the
// last resolved effect
game.getAction().checkStateEffects(false, allAffectedCards);
// Add any triggers additional triggers as a result of the above.
// Must be below state effects, since legendary rule is evaluated
// as part of state effects and trigger come afterward. (e.g. to
// correctly handle two Dark Depths - one having no counters).
game.getStack().addAllTriggeredAbilitiesToStack();
// Continue until stack is empty.
}
// Continue until stack is empty.
}
}, sim);
}

View File

@@ -59,12 +59,7 @@ public class GameStateEvaluator {
gameCopy = copier.makeCopy(null, aiPlayer);
}
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, new Runnable() {
@Override
public void run() {
GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent());
}
});
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, () -> GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent()));
CombatSimResult result = new CombatSimResult();
result.copier = copier;
result.gameCopy = gameCopy;