CountersMoveAi: total refacor of AI class, now it has better logic for Tgt -> Defined and Source -> Tgt and for without Tgt

This commit is contained in:
Hanmac
2016-12-18 06:33:40 +00:00
parent 90ffab69de
commit 0000700320

View File

@@ -1,129 +1,449 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
import forge.util.collect.FCollection;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Random r = MyRandom.getRandom();
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
return false;
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
int amount = calcAmount(sa, cType);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (ph.getPlayerTurn().isOpponentOf(ai)) {
// opponent Creature with +1/+1 counter does attack
// try to steal counter from it to kill it
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
for (final Card c : srcCards) {
// source is not controlled by current player
if (!ph.isPlayerTurn(c.getController())) {
continue;
}
int a = c.getCounters(cType);
if (a < amount) {
continue;
}
if (ph.getCombat().isAttacking(c)) {
// get copy of creature with removed Counter
final Card cpy = CardUtil.getLKICopy(c);
// cant use substract on Copy
cpy.setCounters(cType, a - amount);
// a removed counter would kill it
if (cpy.getNetToughness() <= cpy.getDamage()) {
return true;
}
// something you can't block, try to reduce its
// attack
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true;
}
}
}
return false;
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// something like Cyptoplast Root-kin
if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
if (!moveTgtAI(ai, sa) && !mandatory) {
return false;
}
if (!sa.isTargetNumberValid() && mandatory) {
final Game game = ai.getGame();
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (tgtCards.isEmpty()) {
return false;
}
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
sa.getTargets().add(card);
}
return true;
} else {
// no target Probably something like Graft
if (mandatory) {
return true;
}
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (srcCards.isEmpty() || destCards.isEmpty()) {
return false;
}
final Card src = srcCards.get(0);
final Card dest = destCards.get(0);
// for such Trigger, do not move counter to another players creature
if (!dest.getController().equals(ai)) {
return false;
} else if (isUselessCreature(ai, dest)) {
return false;
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (!dest.canReceiveCounters(cType)) {
return false;
}
final int amount = calcAmount(sa, cType);
int a = src.getCounters(cType);
if (a < amount) {
return false;
}
final Card srcCopy = CardUtil.getLKICopy(src);
// cant use substract on Copy
srcCopy.setCounters(cType, a - amount);
final Card destCopy = CardUtil.getLKICopy(dest);
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
if (newEval < oldEval) {
return false;
}
}
// no target
return true;
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
return true;
}
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
final Card host = sa.getHostCard();
final String amountStr = sa.getParam("CounterNum");
// TODO handle proper calculation of X values based on Cost
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// don't use it if no counters to add
if (amount <= 0) {
return false;
if (amountStr.equals("All")) {
// sa has Source, otherwise Source is the Target
if (sa.hasParam("Source")) {
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
for (final Card c : srcCards) {
amount += c.getCounters(cType);
}
}
} else {
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
}
return amount;
}
// prevent run-away activations - first time will always return true
boolean chance = false;
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
if (SpellAbilityAi.playReusable(ai, sa)) {
return chance;
}
return ((r.nextFloat() < .6667) && chance);
} // moveCounterCanPlayAI
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card host = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
int amount = 0;
if (!sa.getParam("CounterNum").equals("All")) {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
boolean chance = false;
boolean preferred = true;
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final CounterType cType = CounterType.valueOf(sa.getParam("CounterType"));
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if ((srcCards.size() > 0 && sa.getParam("CounterNum").equals("All"))) {
amount = srcCards.get(0).getCounters(cType);
}
if (abTgt == null) {
if ((srcCards.size() > 0)
&& cType.equals(CounterType.P1P1) // move +1/+1 counters away
// from
// permanents that cannot use
// them
&& (destCards.size() > 0) && destCards.get(0).getController() == ai
&& (!srcCards.get(0).isCreature() || srcCards.get(0).hasStartOfKeyword("CARDNAME can't attack"))) {
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
chance = true;
}
} else { // targeted
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
CardCollectionView list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), host.getController(), host, sa);
if (list.isEmpty() && mandatory) {
// If there isn't any prefered cards to target, gotta choose
// non-preferred ones
list = CardLists.getTargetableCards(player.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), host.getController(), host, sa);
preferred = false;
}
// Not mandatory, or the the list was regenerated and is still
// empty,
// so return false since there are no targets
if (list.isEmpty()) {
if (sa.hasParam("Defined")) {
final int amount = calcAmount(sa, cType);
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
// SA uses target for Source
// Target => Defined
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (destCards.isEmpty()) {
// something went wrong
return false;
}
Card choice = null;
final Card dest = destCards.get(0);
// Choose targets here:
if (sa.isCurse()) {
if (preferred) {
choice = CountersAi.chooseCursedTarget(list, type, amount);
}
else if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
}
else {
choice = Aggregates.random(list);
}
// remove dest from targets, because move doesn't work that way
tgtCards.remove(dest);
if (cType != null && !dest.canReceiveCounters(cType)) {
return false;
}
else {
if (preferred) {
choice = CountersAi.chooseBoonTarget(list, type);
// 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 (isUselessCreature(ai, card)) {
return false;
}
final Card srcCardCpy = CardUtil.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 (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
return true;
}
return false;
}
return true;
}
});
// if no Prefered found, try normal list
if (best.isEmpty()) {
best = oppList;
}
else if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
else {
choice = Aggregates.random(list);
}
// from your creature, try to take from the weakest
FCollection<Player> ally = ai.getAllies();
ally.add(ai);
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 (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 (CounterType.P1P1.equals(cType)) {
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
return true;
}
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getWorstCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// TODO - I think choice can be null here. Is that ok for
// addTarget()?
sa.getTargets().add(choice);
return false;
} else {
// SA uses target for Defined
// Source => Targeted
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (srcCards.isEmpty()) {
// something went wrong
return false;
}
final Card src = srcCards.get(0);
if (cType != null) {
if (src.getCounters(cType) <= 0) {
return false;
}
}
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 (isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
return false;
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// move counter to opponents creature but only if you can not steal
// them
// 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 (!isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
return false;
}
return chance;
}
}