AiHints upgrade

This commit is contained in:
tool4EvEr
2022-05-29 17:35:14 +02:00
parent 149f9f083c
commit 76f95bd0a9
3 changed files with 77 additions and 43 deletions

View File

@@ -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<Card> 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<Card> possibleBlockers) { private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) { for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) {
// don't touch other player's blockers // don't touch other player's blockers
@@ -1008,9 +1041,6 @@ public class AiBlockController {
clearBlockers(combat, possibleBlockers); clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> chumpBlockers;
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade 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)) { if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
diff = 0; diff = 0;
@@ -1106,37 +1136,9 @@ public class AiBlockController {
} }
} }
// assign blockers that have to block // block requirements
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."); // TODO because this isn't done earlier, sometimes a good block will enforce a restriction that prevents another for the requirement
// if an attacker with lure attacks - all that can block makeRequiredBlocks(combat);
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);
}
}
}
}
}
// check to see if it's possible to defend a Planeswalker under attack with a chump block, // 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 // 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 * Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers(). * on the implementation of orderBlockers().
*/ */
public static CardCollection orderBlocker(final Card attacker, final Card blocker, public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
final CardCollection oldBlockers) {
// add blocker to existing ordering // add blocker to existing ordering
// sort by evaluate, then insert it appropriately // sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers() // relies on current implementation of orderBlockers()

View File

@@ -209,6 +209,16 @@ public final class CardRulesPredicates {
}; };
} }
public static Predicate<CardRules> deckHasExactly(final DeckHints.Type type, final String has[]) {
return new Predicate<CardRules>() {
@Override
public boolean apply(final CardRules card) {
DeckHints deckHas = card.getAiHints().getDeckHas();
return deckHas != null && deckHas.isValid() && deckHas.is(type, has);
}
};
}
/** /**
* Core type. * Core type.
* *

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.Iterables;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.PredicateString.StringOp; import forge.util.PredicateString.StringOp;
import forge.util.collect.FCollection;
/** /**
* DeckHints provides the ability for a Card to "want" another Card or type of * DeckHints provides the ability for a Card to "want" another Card or type of
@@ -73,12 +74,26 @@ public class DeckHints {
return false; return false;
} }
for (Pair<Type, String> filter : filters) { for (Pair<Type, String> filter : filters) {
if (filter.getLeft() == type && filter.getRight().equals(hint)) { if (filter.getLeft() == type && filter.getRight().contains(hint)) {
return true; return true;
} }
} }
return false; return false;
} }
public boolean is(Type type, String hints[]) {
if (filters == null) {
return false;
}
for (String hint : hints) {
for (Pair<Type, String> 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<PaperCard> that match this * Returns a Map of Cards by Type from the given Iterable<PaperCard> that match this
@@ -95,6 +110,10 @@ public class DeckHints {
String param = pair.getRight(); String param = pair.getRight();
Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param); Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param);
if (cards != null) { 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); ret.put(type, cards);
} }
} }
@@ -143,13 +162,16 @@ public class DeckHints {
private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) { private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) {
List<PaperCard> cards = new ArrayList<>(); List<PaperCard> 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) { 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: case COLOR:
String[] colors = param.split("\\|"); String[] colors = param.split("\\|");
for (String color : colors) { for (String color : colors) {
@@ -187,6 +209,7 @@ public class DeckHints {
} }
break; break;
case NONE: case NONE:
case ABILITY: // already done above
break; break;
} }
return cards; return cards;