From 76f95bd0a9613c04e248499e3223d28801b07282 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 29 May 2022 17:35:14 +0200 Subject: [PATCH] AiHints upgrade --- .../main/java/forge/ai/AiBlockController.java | 73 ++++++++++--------- .../java/forge/card/CardRulesPredicates.java | 10 +++ .../src/main/java/forge/card/DeckHints.java | 37 ++++++++-- 3 files changed, 77 insertions(+), 43 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index dd489c3f8c3..de5ac94e04b 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -945,6 +945,39 @@ public class AiBlockController { } } + private void makeRequiredBlocks(Combat combat) { + // assign blockers that have to block + final CardCollection chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."); + // if an attacker with lure attacks - all that can block + for (final Card blocker : blockersLeft) { + if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) { + chumpBlockers.add(blocker); + } + } + if (!chumpBlockers.isEmpty()) { + for (final Card attacker : attackers) { + List blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false); + for (final Card blocker : blockers) { + if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) + && (CombatUtil.mustBlockAnAttacker(blocker, combat, null) + || blocker.hasKeyword("CARDNAME blocks each combat if able."))) { + combat.addBlocker(attacker, blocker); + if (!blocker.getMustBlockCards().isEmpty()) { + int mustBlockAmt = blocker.getMustBlockCards().size(); + final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker); + boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); + if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) { + blockersLeft.remove(blocker); + } + } else { + blockersLeft.remove(blocker); + } + } + } + } + } + } + private void clearBlockers(final Combat combat, final List possibleBlockers) { for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) { // don't touch other player's blockers @@ -1008,9 +1041,6 @@ public class AiBlockController { clearBlockers(combat, possibleBlockers); - List blockers; - List chumpBlockers; - diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) { diff = 0; @@ -1106,37 +1136,9 @@ public class AiBlockController { } } - // assign blockers that have to block - chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."); - // if an attacker with lure attacks - all that can block - for (final Card blocker : blockersLeft) { - if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) { - chumpBlockers.add(blocker); - } - } - if (!chumpBlockers.isEmpty()) { - CardLists.shuffle(attackers); - for (final Card attacker : attackers) { - blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false); - for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) - && (CombatUtil.mustBlockAnAttacker(blocker, combat, null) - || blocker.hasKeyword("CARDNAME blocks each combat if able."))) { - combat.addBlocker(attacker, blocker); - if (!blocker.getMustBlockCards().isEmpty()) { - int mustBlockAmt = blocker.getMustBlockCards().size(); - final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker); - boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar); - if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) { - blockersLeft.remove(blocker); - } - } else { - blockersLeft.remove(blocker); - } - } - } - } - } + // block requirements + // TODO because this isn't done earlier, sometimes a good block will enforce a restriction that prevents another for the requirement + makeRequiredBlocks(combat); // check to see if it's possible to defend a Planeswalker under attack with a chump block, // unless life is low enough to be more worried about saving preserving the life total @@ -1186,8 +1188,7 @@ public class AiBlockController { * Orders a blocker that put onto the battlefield blocking. Depends heavily * on the implementation of orderBlockers(). */ - public static CardCollection orderBlocker(final Card attacker, final Card blocker, - final CardCollection oldBlockers) { + public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) { // add blocker to existing ordering // sort by evaluate, then insert it appropriately // relies on current implementation of orderBlockers() diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index 16b5ed5b974..ea772e340df 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -209,6 +209,16 @@ public final class CardRulesPredicates { }; } + public static Predicate deckHasExactly(final DeckHints.Type type, final String has[]) { + return new Predicate() { + @Override + public boolean apply(final CardRules card) { + DeckHints deckHas = card.getAiHints().getDeckHas(); + return deckHas != null && deckHas.isValid() && deckHas.is(type, has); + } + }; + } + /** * Core type. * diff --git a/forge-core/src/main/java/forge/card/DeckHints.java b/forge-core/src/main/java/forge/card/DeckHints.java index 4ede03d9b88..a55426cfd95 100644 --- a/forge-core/src/main/java/forge/card/DeckHints.java +++ b/forge-core/src/main/java/forge/card/DeckHints.java @@ -14,6 +14,7 @@ import com.google.common.collect.Iterables; import forge.item.PaperCard; import forge.util.PredicateString.StringOp; +import forge.util.collect.FCollection; /** * DeckHints provides the ability for a Card to "want" another Card or type of @@ -73,12 +74,26 @@ public class DeckHints { return false; } for (Pair filter : filters) { - if (filter.getLeft() == type && filter.getRight().equals(hint)) { + if (filter.getLeft() == type && filter.getRight().contains(hint)) { return true; } } return false; } + public boolean is(Type type, String hints[]) { + if (filters == null) { + return false; + } + for (String hint : hints) { + for (Pair filter : filters) { + if (filter.getLeft() == type && filter.getRight().equals(hint)) { + continue; + } + } + return false; + } + return true; + } /** * Returns a Map of Cards by Type from the given Iterable that match this @@ -95,6 +110,10 @@ public class DeckHints { String param = pair.getRight(); Iterable cards = getCardsForFilter(cardList, type, param); if (cards != null) { + // if a type is used more than once intersect respective matches + if (ret.get(type) != null) { + Iterables.retainAll(cards, new FCollection<>(ret.get(type))); + } ret.put(type, cards); } } @@ -143,13 +162,16 @@ public class DeckHints { private Iterable getCardsForFilter(Iterable cardList, Type type, String param) { List cards = new ArrayList<>(); + + // this is case ABILITY, but other types can also use this when the implicit parsing would miss + String[] abilities = param.split("\\|"); + for (String ability : abilities) { + Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(type, ability), PaperCard.FN_GET_RULES)); + } + // bonus if a DeckHas can satisfy the type with multiple ones + Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHasExactly(type, abilities), PaperCard.FN_GET_RULES)); + switch (type) { - case ABILITY: - String[] abilities = param.split("\\|"); - for (String ability : abilities) { - Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(Type.ABILITY, ability), PaperCard.FN_GET_RULES)); - } - break; case COLOR: String[] colors = param.split("\\|"); for (String color : colors) { @@ -187,6 +209,7 @@ public class DeckHints { } break; case NONE: + case ABILITY: // already done above break; } return cards;