mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
ComputerUtil: add logic for Council's dilemma votings
add helper lifegainPositive & lifegainNegative for looking at lifegain replacement effects
This commit is contained in:
@@ -31,11 +31,14 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import forge.ai.ability.ProtectAi;
|
import forge.ai.ability.ProtectAi;
|
||||||
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
|
import forge.game.CardTraitPredicates;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
@@ -61,6 +64,8 @@ import forge.game.cost.CostSacrifice;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -2061,48 +2066,196 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
|
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Player controller = source.getController();
|
||||||
|
final Game game = controller.getGame();
|
||||||
|
|
||||||
|
boolean opponent = controller.isOpponentOf(ai);
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return Aggregates.random(options);
|
return Aggregates.random(options);
|
||||||
} else {
|
}
|
||||||
String logic = sa.getParam("AILogic");
|
|
||||||
switch (logic) {
|
String logic = sa.getParam("AILogic");
|
||||||
case "Torture":
|
switch (logic) {
|
||||||
return "Torture";
|
case "Torture":
|
||||||
case "GraceOrCondemnation":
|
return "Torture";
|
||||||
return ai.getCreaturesInPlay().size() > ai.getOpponent().getCreaturesInPlay().size() ? "Grace" : "Condemnation";
|
case "GraceOrCondemnation":
|
||||||
case "CarnageOrHomage":
|
return ai.getCreaturesInPlay().size() > ai.getOpponent().getCreaturesInPlay().size() ? "Grace"
|
||||||
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
: "Condemnation";
|
||||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
case "CarnageOrHomage":
|
||||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
CardCollection cardsInPlay = CardLists
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||||
case "Judgment":
|
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
||||||
if (votes.isEmpty()) {
|
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
||||||
CardCollection list = new CardCollection();
|
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||||
for (Object o : options) {
|
case "Judgment":
|
||||||
if (o instanceof Card) {
|
if (votes.isEmpty()) {
|
||||||
list.add((Card) o);
|
CardCollection list = new CardCollection();
|
||||||
}
|
for (Object o : options) {
|
||||||
|
if (o instanceof Card) {
|
||||||
|
list.add((Card) o);
|
||||||
}
|
}
|
||||||
return ComputerUtilCard.getBestAI(list);
|
|
||||||
} else {
|
|
||||||
return Iterables.getFirst(votes.keySet(), null);
|
|
||||||
}
|
}
|
||||||
case "Protection":
|
return ComputerUtilCard.getBestAI(list);
|
||||||
if (votes.isEmpty()) {
|
} else {
|
||||||
List<String> restrictedToColors = new ArrayList<String>();
|
return Iterables.getFirst(votes.keySet(), null);
|
||||||
for (Object o : options) {
|
|
||||||
if (o instanceof String) {
|
|
||||||
restrictedToColors.add((String) o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CardCollection lists = CardLists.filterControlledBy(ai.getGame().getCardsInGame(), ai.getOpponents());
|
|
||||||
return StringUtils.capitalize(ComputerUtilCard.getMostProminentColor(lists, restrictedToColors));
|
|
||||||
} else {
|
|
||||||
return Iterables.getFirst(votes.keySet(), null);
|
|
||||||
}
|
|
||||||
default: return Iterables.getFirst(options, null);
|
|
||||||
}
|
}
|
||||||
|
case "Protection":
|
||||||
|
if (votes.isEmpty()) {
|
||||||
|
List<String> restrictedToColors = Lists.newArrayList();
|
||||||
|
for (Object o : options) {
|
||||||
|
if (o instanceof String) {
|
||||||
|
restrictedToColors.add((String) o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CardCollection lists = CardLists.filterControlledBy(ai.getGame().getCardsInGame(), ai.getOpponents());
|
||||||
|
return StringUtils.capitalize(ComputerUtilCard.getMostProminentColor(lists, restrictedToColors));
|
||||||
|
} else {
|
||||||
|
return Iterables.getFirst(votes.keySet(), null);
|
||||||
|
}
|
||||||
|
case "FeatherOrQuill":
|
||||||
|
|
||||||
|
// try to mill opponent with Quill vote
|
||||||
|
if (opponent && !controller.cantLose()) {
|
||||||
|
int numQuill = votes.get("Quill").size();
|
||||||
|
if (numQuill + 1 >= controller.getCardsIn(ZoneType.Library).size()) {
|
||||||
|
return controller.isCardInPlay("Laboratory Maniac") ? "Feather" : "Quill";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
|
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||||
|
return opponent ? "Feather" : "Quill";
|
||||||
|
}
|
||||||
|
// if source is not on the battlefield anymore, choose +1/+1
|
||||||
|
// ones
|
||||||
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
|
return opponent ? "Feather" : "Quill";
|
||||||
|
}
|
||||||
|
// if no hand cards, try to mill opponent
|
||||||
|
if (controller.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
|
return opponent ? "Quill" : "Feather";
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI has something to discard
|
||||||
|
if (ai.equals(controller)) {
|
||||||
|
CardCollectionView aiCardsInHand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
if (CardLists.count(aiCardsInHand, CardPredicates.hasSVar("DiscardMe")) >= 1) {
|
||||||
|
return "Quill";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default card draw and discard are better than +1/+1 counter
|
||||||
|
return opponent ? "Feather" : "Quill";
|
||||||
|
case "StrengthOrNumbers":
|
||||||
|
// similar to fabricate choose +1/+1 or Token
|
||||||
|
final SpellAbility saToken = sa.findSubAbilityByType(ApiType.Token);
|
||||||
|
int numStrength = votes.get("Strength").size();
|
||||||
|
int numNumbers = votes.get("Numbers").size();
|
||||||
|
|
||||||
|
Card token = TokenAi.spawnToken(controller, saToken);
|
||||||
|
|
||||||
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
|
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||||
|
return opponent ? "Strength" : "Numbers";
|
||||||
|
}
|
||||||
|
|
||||||
|
// if source is not on the battlefield anymore
|
||||||
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
|
return opponent ? "Strength" : "Numbers";
|
||||||
|
}
|
||||||
|
|
||||||
|
// token would not survive
|
||||||
|
if (token == null) {
|
||||||
|
return opponent ? "Numbers" : "Strength";
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check for ETB to +1/+1 counters
|
||||||
|
// or over another trigger like lifegain
|
||||||
|
|
||||||
|
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
||||||
|
|
||||||
|
// score check similar to Fabricate
|
||||||
|
Card sourceNumbers = CardUtil.getLKICopy(source);
|
||||||
|
Card sourceStrength = CardUtil.getLKICopy(source);
|
||||||
|
|
||||||
|
sourceNumbers.setCounters(CounterType.P1P1, sourceNumbers.getCounters(CounterType.P1P1) + numStrength);
|
||||||
|
sourceNumbers.setZone(source.getZone());
|
||||||
|
|
||||||
|
sourceStrength.setCounters(CounterType.P1P1,
|
||||||
|
sourceStrength.getCounters(CounterType.P1P1) + numStrength + 1);
|
||||||
|
sourceStrength.setZone(source.getZone());
|
||||||
|
|
||||||
|
int scoreStrength = ComputerUtilCard.evaluateCreature(sourceStrength) + tokenScore * numNumbers;
|
||||||
|
int scoreNumbers = ComputerUtilCard.evaluateCreature(sourceNumbers) + tokenScore * (numNumbers + 1);
|
||||||
|
|
||||||
|
return (scoreNumbers >= scoreStrength) != opponent ? "Numbers" : "Strength";
|
||||||
|
|
||||||
|
case "SproutOrHarvest":
|
||||||
|
|
||||||
|
// lifegain would hurt or has no effect
|
||||||
|
if (opponent) {
|
||||||
|
if (lifegainNegative(controller, source)) {
|
||||||
|
return "Harvest";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lifegainNegative(controller, source)) {
|
||||||
|
return "Sprout";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
|
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||||
|
return opponent ? "Sprout" : "Harvest";
|
||||||
|
}
|
||||||
|
|
||||||
|
// if source is not on the battlefield anymore
|
||||||
|
if (!game.getCardState(source).getZone().is(ZoneType.Battlefield)) {
|
||||||
|
return opponent ? "Sprout" : "Harvest";
|
||||||
|
}
|
||||||
|
// TODO add Lifegain to +1/+1 counters trigger
|
||||||
|
|
||||||
|
// for now +1/+1 counters are better
|
||||||
|
return opponent ? "Harvest" : "Sprout";
|
||||||
|
case "DeathOrTaxes":
|
||||||
|
int numDeath = votes.get("Death").size();
|
||||||
|
int numTaxes = votes.get("Taxes").size();
|
||||||
|
|
||||||
|
if (opponent) {
|
||||||
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
|
CardCollectionView aiCardsInHand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
// would need to sacrifice more creatures than AI has
|
||||||
|
// sacrifice even more
|
||||||
|
if (aiCreatures.size() <= numDeath) {
|
||||||
|
return "Death";
|
||||||
|
}
|
||||||
|
// would need to discard more cards than it has
|
||||||
|
if (aiCardsInHand.size() <= numTaxes) {
|
||||||
|
return "Taxes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// has cards with SacMe or Token
|
||||||
|
if (CardLists.count(aiCreatures,
|
||||||
|
Predicates.or(CardPredicates.hasSVar("SacMe"), CardPredicates.Presets.TOKEN)) >= numDeath) {
|
||||||
|
return "Death";
|
||||||
|
}
|
||||||
|
|
||||||
|
// has cards with DiscardMe
|
||||||
|
if (CardLists.count(aiCardsInHand, CardPredicates.hasSVar("DiscardMe")) >= numTaxes) {
|
||||||
|
return "Taxes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard is probably less worse than sacrifice
|
||||||
|
return "Taxes";
|
||||||
|
} else {
|
||||||
|
// ai is first voter or ally of controller
|
||||||
|
// both are not affected, but if opponents controll creatures,
|
||||||
|
// sacrifice is worse
|
||||||
|
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2417,4 +2570,65 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean lifegainPositive(final Player player, final Card source) {
|
||||||
|
|
||||||
|
if (!player.canGainLife()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run any applicable replacement effects.
|
||||||
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
|
repParams.put("Event", "GainLife");
|
||||||
|
repParams.put("Affected", player);
|
||||||
|
repParams.put("LifeGained", 1);
|
||||||
|
repParams.put("Source", source);
|
||||||
|
|
||||||
|
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||||
|
ReplacementLayer.None);
|
||||||
|
|
||||||
|
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||||
|
return false;
|
||||||
|
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LoseLife"))) {
|
||||||
|
return false;
|
||||||
|
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LichDraw"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean lifegainNegative(final Player player, final Card source) {
|
||||||
|
return lifegainNegative(player, source, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
||||||
|
|
||||||
|
if (!player.canGainLife()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run any applicable replacement effects.
|
||||||
|
final Map<String, Object> repParams = Maps.newHashMap();
|
||||||
|
repParams.put("Event", "GainLife");
|
||||||
|
repParams.put("Affected", player);
|
||||||
|
repParams.put("LifeGained", n);
|
||||||
|
repParams.put("Source", source);
|
||||||
|
|
||||||
|
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||||
|
ReplacementLayer.None);
|
||||||
|
|
||||||
|
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||||
|
// no life gain is not negative
|
||||||
|
return false;
|
||||||
|
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LoseLife"))) {
|
||||||
|
// lose life is only negagive is the player can lose life
|
||||||
|
return player.canLoseLife();
|
||||||
|
} else if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "LichDraw"))) {
|
||||||
|
// if it would draw more cards than player has, then its negative
|
||||||
|
return player.getCardsIn(ZoneType.Library).size() <= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user