diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java
index 1e30e995570..31b9bee41ec 100644
--- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java
+++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java
@@ -112,8 +112,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
protected void calculateActions(Game game) {
if (!getNextAction(game)) {
- //logger.info("--- calculating possible actions for " + this.getName() + " on " + game.toString());
- Date startTime = new Date();
currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
Game sim = createSimulation(game);
SimulationNode2.resetCount();
@@ -146,15 +144,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
} else {
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
}
- Date endTime = new Date();
- this.setLastThinkTime((endTime.getTime() - startTime.getTime()));
-
- /*
- logger.warn("Last think time: " + this.getLastThinkTime()
- + "; actions: " + actions.size()
- + "; hand: " + this.getHand().size()
- + "; permanents: " + game.getBattlefield().getAllPermanents().size());
- */
} else {
logger.debug("Next Action exists!");
}
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java
index 282d424bbd5..b9c6f3ff1b8 100644
--- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java
+++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java
@@ -3,10 +3,6 @@ package mage.player.ai;
import mage.*;
import mage.abilities.*;
import mage.abilities.costs.mana.*;
-import mage.abilities.effects.Effect;
-import mage.abilities.effects.common.DamageTargetEffect;
-import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect;
-import mage.abilities.keyword.*;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.abilities.mana.ManaOptions;
import mage.cards.Card;
@@ -20,21 +16,12 @@ import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.choices.Choice;
import mage.constants.*;
-import mage.filter.FilterPermanent;
-import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.common.FilterLandCard;
-import mage.filter.common.FilterNonlandCard;
-import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
-import mage.game.combat.CombatGroup;
import mage.game.draft.Draft;
-import mage.game.events.GameEvent;
import mage.game.match.Match;
import mage.game.permanent.Permanent;
import mage.game.tournament.Tournament;
-import mage.player.ai.simulators.CombatGroupSimulator;
-import mage.player.ai.simulators.CombatSimulator;
-import mage.player.ai.simulators.CreatureSimulator;
import mage.players.ManaPoolItem;
import mage.players.Player;
import mage.players.PlayerImpl;
@@ -51,14 +38,16 @@ import java.util.*;
import java.util.Map.Entry;
/**
- * AI: basic server side bot with simple actions support (game, draft, construction/sideboarding)
+ * AI: basic server side bot with simple actions support (game, draft, construction/sideboarding).
+ * Full and minimum implementation of all choose dialogs to allow AI to start and finish a real game.
+ * Used as parent class for any AI implementations.
*
*
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public class ComputerPlayer extends PlayerImpl {
- private static final Logger log = Logger.getLogger(ComputerPlayer.class);
+ private static final Logger logger = Logger.getLogger(ComputerPlayer.class);
protected static final int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
@@ -78,11 +67,7 @@ public class ComputerPlayer extends PlayerImpl {
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5;
- // TODO: delete after target rework
- private final transient Map unplayable = new TreeMap<>();
- private final transient List playableNonInstant = new ArrayList<>();
- private final transient List playableInstant = new ArrayList<>();
- private final transient List playableAbilities = new ArrayList<>();
+ // remember picked cards for better draft choices
private final transient List pickedCards = new ArrayList<>();
private final transient List chosenColors = new ArrayList<>();
@@ -94,8 +79,6 @@ public class ComputerPlayer extends PlayerImpl {
// For stopping infinite loops when trying to pay Phyrexian mana when the player can't spend life and no other sources are available
private transient boolean alreadyTryingToPayPhyrexian;
- private transient long lastThinkTime = 0; // time in ms for last AI actions calc
-
public ComputerPlayer(String name, RangeOfInfluence range) {
super(name, range);
human = false;
@@ -228,36 +211,6 @@ public class ComputerPlayer extends PlayerImpl {
return makeChoice(outcome, target, source, game, null);
}
- @Deprecated // TODO: replace by source only version
- protected Card selectCard(UUID abilityControllerId, List cards, Outcome outcome, Target target, Game game) {
- return selectCardInner(abilityControllerId, cards, outcome, target, null, game);
- }
-
- /**
- * @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
- */
- protected Card selectCardInner(UUID abilityControllerId, List cards, Outcome outcome, Target target, Ability targetingSource, Game game) {
- Card card;
- while (!cards.isEmpty()) {
- if (outcome.isGood()) {
- card = selectBestCardInner(cards, Collections.emptyList(), target, targetingSource, game);
- } else {
- card = selectWorstCardInner(cards, Collections.emptyList(), target, targetingSource, game);
- }
- if (!target.getTargets().contains(card.getId())) {
- if (targetingSource != null) {
- if (target.canTarget(abilityControllerId, card.getId(), targetingSource, game)) {
- return card;
- }
- } else {
- return card;
- }
- }
- cards.remove(card); // TODO: research parent code - is it depends on original list? Can be bugged
- }
- return null;
- }
-
@Override
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
@@ -412,247 +365,9 @@ public class ComputerPlayer extends PlayerImpl {
@Override
public boolean priority(Game game) {
- game.resumeTimer(getTurnControlledBy());
- boolean result = priorityPlay(game);
- game.pauseTimer(getTurnControlledBy());
- return result;
- }
-
- private boolean priorityPlay(Game game) {
- // TODO: simplify and delete, never called in real game due ComputerPlayer7's simulations usage
- UUID opponentId = getRandomOpponent(game);
- if (game.isActivePlayer(playerId)) {
- if (game.isMainPhase() && game.getStack().isEmpty()) {
- playLand(game);
- }
- switch (game.getTurnStepType()) {
- case UPKEEP:
- // TODO: is it needs here? Need research (e.g. for better choose in upkeep triggers)?
- findPlayables(game);
- break;
- case DRAW:
- break;
- case PRECOMBAT_MAIN:
- findPlayables(game);
- if (!playableAbilities.isEmpty()) {
- for (ActivatedAbility ability : playableAbilities) {
- if (ability.canActivate(playerId, game).canActivate()) {
- if (ability.getEffects().hasOutcome(ability, Outcome.PutLandInPlay)) {
- if (this.activateAbility(ability, game)) {
- return true;
- }
- }
- if (ability.getEffects().hasOutcome(ability, Outcome.PutCreatureInPlay)) {
- if (getOpponentBlockers(opponentId, game).size() <= 1) {
- if (this.activateAbility(ability, game)) {
- return true;
- }
- }
- }
- }
- }
- }
- break;
- case DECLARE_BLOCKERS:
- findPlayables(game);
- playRemoval(game.getCombat().getBlockers(), game);
- playDamage(game.getCombat().getBlockers(), game);
- break;
- case END_COMBAT:
- findPlayables(game);
- playDamage(game.getCombat().getBlockers(), game);
- break;
- case POSTCOMBAT_MAIN:
- findPlayables(game);
- if (game.getStack().isEmpty()) {
- if (!playableNonInstant.isEmpty()) {
- for (Card card : playableNonInstant) {
- if (card.getSpellAbility().canActivate(playerId, game).canActivate()) {
- if (this.activateAbility(card.getSpellAbility(), game)) {
- return true;
- }
- }
- }
- }
- if (!playableAbilities.isEmpty()) {
- for (ActivatedAbility ability : playableAbilities) {
- if (ability.canActivate(playerId, game).canActivate()) {
- if (!(ability.getEffects().get(0) instanceof BecomesCreatureSourceEffect)) {
- if (this.activateAbility(ability, game)) {
- return true;
- }
- }
- }
- }
- }
- }
- break;
- }
- } else {
- //respond to opponent events
- switch (game.getTurnStepType()) {
- case UPKEEP:
- findPlayables(game);
- break;
- case DECLARE_ATTACKERS:
- findPlayables(game);
- playRemoval(game.getCombat().getAttackers(), game);
- playDamage(game.getCombat().getAttackers(), game);
- break;
- case END_COMBAT:
- findPlayables(game);
- playDamage(game.getCombat().getAttackers(), game);
- break;
- }
- }
+ // minimum implementation for do nothing
pass(game);
- return true;
- } // end priorityPlay method
-
- protected void playLand(Game game) {
- Set lands = new LinkedHashSet<>();
- for (Card landCard : hand.getCards(new FilterLandCard(), game)) {
- // remove lands that can not be played
- boolean canPlay = false;
- for (Ability ability : landCard.getAbilities(game)) {
- if (ability instanceof PlayLandAbility) {
- if (!game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, landCard.getId(), ability, playerId), null, game, true)) {
- canPlay = true;
- }
- }
- }
- if (canPlay) {
- lands.add(landCard);
- }
- }
- while (!lands.isEmpty() && this.canPlayLand()) {
- if (lands.size() == 1) {
- this.playLand(lands.iterator().next(), game, false);
- } else {
- playALand(lands, game);
- }
- }
- }
-
- protected void playALand(Set lands, Game game) {
- //play a land that will allow us to play an unplayable
- for (Mana mana : unplayable.keySet()) {
- for (Card card : lands) {
- for (ActivatedManaAbilityImpl ability : card.getAbilities(game).getActivatedManaAbilities(Zone.BATTLEFIELD)) {
- for (Mana netMana : ability.getNetMana(game)) {
- if (netMana.enough(mana)) {
- this.playLand(card, game, false);
- lands.remove(card);
- return;
- }
- }
- }
- }
- }
- //play a land that will get us closer to playing an unplayable
- for (Mana mana : unplayable.keySet()) {
- for (Card card : lands) {
- for (ActivatedManaAbilityImpl ability : card.getAbilities(game).getActivatedManaAbilities(Zone.BATTLEFIELD)) {
- for (Mana netMana : ability.getNetMana(game)) {
- if (mana.contains(netMana)) {
- this.playLand(card, game, false);
- lands.remove(card);
- return;
- }
- }
- }
- }
- }
- //play first available land
- this.playLand(lands.iterator().next(), game, false);
- lands.remove(lands.iterator().next());
- }
-
- @Deprecated // TODO: delete after target rework
- protected void findPlayables(Game game) {
- playableInstant.clear();
- playableNonInstant.clear();
- unplayable.clear();
- playableAbilities.clear();
- Set nonLands = hand.getCards(new FilterNonlandCard(), game);
- ManaOptions available = getManaAvailable(game);
-// available.addMana(manaPool.getMana());
-
- for (Card card : nonLands) {
- ManaOptions options = card.getManaCost().getOptions();
- if (!card.getManaCost().getVariableCosts().isEmpty()) {
- //don't use variable mana costs unless there is at least 3 extra mana for X
- for (Mana option : options) {
- option.add(Mana.GenericMana(3));
- }
- }
- for (Mana mana : options) {
- for (Mana avail : available) {
- if (mana.enough(avail)) {
- SpellAbility ability = card.getSpellAbility();
- GameEvent castEvent = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, ability.getId(), ability, playerId);
- castEvent.setZone(game.getState().getZone(card.getMainCard().getId()));
- if (ability != null && ability.canActivate(playerId, game).canActivate()
- && !game.getContinuousEffects().preventedByRuleModification(castEvent, ability, game, true)) {
- if (card.isInstant(game)
- || card.hasAbility(FlashAbility.getInstance(), game)) {
- playableInstant.add(card);
- } else {
- playableNonInstant.add(card);
- }
- }
- } else if (!playableInstant.contains(card) && !playableNonInstant.contains(card)) {
- unplayable.put(mana.needed(avail), card);
- }
- }
- }
- }
- // TODO: wtf?! change to player.getPlayable
- for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
- for (ActivatedAbility ability : permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
- if (!ability.isManaActivatedAbility() && ability.canActivate(playerId, game).canActivate()) {
- if (ability instanceof EquipAbility && permanent.getAttachedTo() != null) {
- continue;
- }
- ManaOptions abilityOptions = ability.getManaCosts().getOptions();
- if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
- //don't use variable mana costs unless there is at least 3 extra mana for X
- for (Mana option : abilityOptions) {
- option.add(Mana.GenericMana(3));
- }
- }
- if (abilityOptions.isEmpty()) {
- playableAbilities.add(ability);
- } else {
- for (Mana mana : abilityOptions) {
- for (Mana avail : available) {
- if (mana.enough(avail)) {
- playableAbilities.add(ability);
- }
- }
- }
- }
- }
- }
- }
- for (Card card : graveyard.getCards(game)) {
- for (ActivatedAbility ability : card.getAbilities(game).getActivatedAbilities(Zone.GRAVEYARD)) {
- if (ability.canActivate(playerId, game).canActivate()) {
- ManaOptions abilityOptions = ability.getManaCosts().getOptions();
- if (abilityOptions.isEmpty()) {
- playableAbilities.add(ability);
- } else {
- for (Mana mana : abilityOptions) {
- for (Mana avail : available) {
- if (mana.enough(avail)) {
- playableAbilities.add(ability);
- }
- }
- }
- }
- }
- }
- }
+ return false;
}
@Override
@@ -1053,7 +768,9 @@ public class ComputerPlayer extends PlayerImpl {
// choose creature type
// TODO: WTF?! Creature types dialog text can changes, need to replace that code
if (choice.getMessage() != null && (choice.getMessage().equals("Choose creature type") || choice.getMessage().equals("Choose a creature type"))) {
- chooseCreatureType(outcome, choice, game);
+ if (chooseCreatureType(outcome, choice, game)) {
+ return true;
+ }
}
// choose the correct color to pay a spell (use last unpaid ability for color hint)
@@ -1170,39 +887,12 @@ public class ComputerPlayer extends PlayerImpl {
@Override
public void selectAttackers(Game game, UUID attackingPlayerId) {
- UUID opponentId = game.getCombat().getDefenders().iterator().next();
- Attackers attackers = getPotentialAttackers(game);
- List blockers = getOpponentBlockers(opponentId, game);
- List actualAttackers = new ArrayList<>();
- if (blockers.isEmpty()) {
- actualAttackers = attackers.getAttackers();
- } else if (attackers.size() - blockers.size() >= game.getPlayer(opponentId).getLife()) {
- actualAttackers = attackers.getAttackers();
- } else {
- CombatSimulator combat = simulateAttack(attackers, blockers, opponentId, game);
- if (combat.rating > 2) {
- for (CombatGroupSimulator group : combat.groups) {
- this.declareAttacker(group.attackers.get(0).id, group.defenderId, game, false);
- }
- }
- }
- for (Permanent attacker : actualAttackers) {
- this.declareAttacker(attacker.getId(), opponentId, game, false);
- }
+ // do nothing, parent class must implement it
}
@Override
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
- List blockers = getAvailableBlockers(game);
-
- CombatSimulator sim = simulateBlock(CombatSimulator.load(game), blockers, game);
-
- List groups = game.getCombat().getGroups();
- for (int i = 0; i < groups.size(); i++) {
- for (CreatureSimulator creature : sim.groups.get(i).blockers) {
- groups.get(i).addBlocker(creature.id, playerId, game);
- }
- }
+ // do nothing, parent class must implement it
}
@Override
@@ -1431,25 +1121,11 @@ public class ComputerPlayer extends PlayerImpl {
tournament.submitDeck(playerId, deck);
}
- @Deprecated // TODO: replace by source only version
- public Card selectBestCard(List cards, List chosenColors) {
- return selectBestCardInner(cards, chosenColors, null, null, null);
- }
-
- /**
- * @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
- */
- public Card selectBestCardInner(List cards, List chosenColors, Target target, Ability targetingSource, Game game) {
+ public Card makePickCard(List cards, List chosenColors) {
if (cards.isEmpty()) {
return null;
}
- // sometimes a target selection can be made from a player that does not control the ability
- UUID abilityControllerId = this.getId();
- if (target != null) {
- abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
- }
-
Card bestCard = null;
int maxScore = 0;
for (Card card : cards) {
@@ -1458,13 +1134,7 @@ public class ComputerPlayer extends PlayerImpl {
if (bestCard == null) { // we need any card to prevent NPE in callers
betterCard = true;
} else if (score > maxScore) { // we need better card
- if (target != null && targetingSource != null && game != null) {
- // but also check it can be targeted
- betterCard = target.canTarget(abilityControllerId, card.getId(), targetingSource, game);
- } else {
- // target object wasn't provided, so accepting it anyway
- betterCard = true;
- }
+ betterCard = true;
}
// is it better than previous one?
if (betterCard) {
@@ -1475,59 +1145,21 @@ public class ComputerPlayer extends PlayerImpl {
return bestCard;
}
- /**
- * @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
- */
- public Card selectWorstCardInner(List cards, List chosenColors, Target target, Ability targetingSource, Game game) {
- if (cards.isEmpty()) {
- return null;
- }
-
- // sometimes a target selection can be made from a player that does not control the ability
- UUID abilityControllerId = this.getId();
- if (target != null) {
- abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
- }
-
- Card worstCard = null;
- int minScore = Integer.MAX_VALUE;
- for (Card card : cards) {
- int score = RateCard.rateCard(card, chosenColors);
- boolean worseCard = false;
- if (worstCard == null) { // we need any card to prevent NPE in callers
- worseCard = true;
- } else if (score < minScore) { // we need worse card
- if (target != null && targetingSource != null && game != null) {
- // but also check it can be targeted
- worseCard = target.canTarget(abilityControllerId, card.getId(), targetingSource, game);
- } else {
- // target object wasn't provided, so accepting it anyway
- worseCard = true;
- }
- }
- // is it worse than previous one?
- if (worseCard) {
- minScore = score;
- worstCard = card;
- }
- }
- return worstCard;
- }
-
@Override
public void pickCard(List cards, Deck deck, Draft draft) {
+ // method used by DRAFT bot too
if (cards.isEmpty()) {
throw new IllegalArgumentException("No cards to pick from.");
}
try {
- Card bestCard = selectBestCard(cards, chosenColors);
+ Card bestCard = makePickCard(cards, chosenColors);
int maxScore = RateCard.rateCard(bestCard, chosenColors);
int pickedCardRate = RateCard.getBaseCardScore(bestCard);
if (pickedCardRate <= 30) {
// if card is bad
// try to counter pick without any color restriction
- Card counterPick = selectBestCard(cards, Collections.emptyList());
+ Card counterPick = makePickCard(cards, Collections.emptyList());
int counterPickScore = RateCard.getBaseCardScore(counterPick);
// card is perfect
// take it!
@@ -1552,10 +1184,9 @@ public class ComputerPlayer extends PlayerImpl {
colors += symbol.toString();
}
}
- log.debug("[DEBUG] AI picked: " + bestCard.getName() + ", score=" + maxScore + ", deck colors=" + colors);
draft.addPick(playerId, bestCard.getId(), null);
} catch (Exception e) {
- log.debug("Exception during AI pick card for draft playerId= " + getId());
+ logger.error("Error during AI pick card for draft playerId = " + getId(), e);
draft.addPick(playerId, cards.get(0).getId(), null);
}
}
@@ -1629,100 +1260,6 @@ public class ComputerPlayer extends PlayerImpl {
}
}
- protected Attackers getPotentialAttackers(Game game) {
- Attackers attackers = new Attackers();
- List creatures = super.getAvailableAttackers(game);
- for (Permanent creature : creatures) {
- int potential = combatPotential(creature, game);
- if (potential > 0 && creature.getPower().getValue() > 0) {
- List l = attackers.get(potential);
- if (l == null) {
- attackers.put(potential, l = new ArrayList<>());
- }
- l.add(creature);
- }
- }
- return attackers;
- }
-
- protected int combatPotential(Permanent creature, Game game) {
- if (!creature.canAttack(null, game)) {
- return 0;
- }
- int potential = creature.getPower().getValue();
- potential += creature.getAbilities().getEvasionAbilities().size();
- potential += creature.getAbilities().getProtectionAbilities().size();
- potential += creature.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId()) ? 1 : 0;
- potential += creature.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId()) ? 2 : 0;
- potential += creature.getAbilities().containsKey(TrampleAbility.getInstance().getId()) ? 1 : 0;
- return potential;
- }
-
- protected List getOpponentBlockers(UUID opponentId, Game game) {
- FilterCreatureForCombatBlock blockFilter = new FilterCreatureForCombatBlock();
- return game.getBattlefield().getAllActivePermanents(blockFilter, opponentId, game);
- }
-
- protected CombatSimulator simulateAttack(Attackers attackers, List blockers, UUID opponentId, Game game) {
- List attackersList = attackers.getAttackers();
- CombatSimulator best = new CombatSimulator();
- int bestResult = 0;
- //use binary digits to calculate powerset of attackers
- int powerElements = (int) Math.pow(2, attackersList.size());
- for (int i = 1; i < powerElements; i++) {
- String binary = Integer.toBinaryString(i);
- while (binary.length() < attackersList.size()) {
- binary = '0' + binary;
- }
- List trialAttackers = new ArrayList<>();
- for (int j = 0; j < attackersList.size(); j++) {
- if (binary.charAt(j) == '1') {
- trialAttackers.add(attackersList.get(j));
- }
- }
- CombatSimulator combat = new CombatSimulator();
- for (Permanent permanent : trialAttackers) {
- combat.groups.add(new CombatGroupSimulator(opponentId, Arrays.asList(permanent.getId()), new ArrayList(), game));
- }
- CombatSimulator test = simulateBlock(combat, blockers, game);
- if (test.evaluate() > bestResult) {
- best = test;
- bestResult = test.evaluate();
- }
- }
-
- return best;
- }
-
- protected CombatSimulator simulateBlock(CombatSimulator combat, List blockers, Game game) {
- TreeNode simulations;
-
- simulations = new TreeNode<>(combat);
- addBlockSimulations(blockers, simulations, game);
- combat.simulate(game);
-
- return getWorstSimulation(simulations);
-
- }
-
- protected void addBlockSimulations(List blockers, TreeNode node, Game game) {
- int numGroups = node.getData().groups.size();
- Copier copier = new Copier<>();
- for (Permanent blocker : blockers) {
- List subList = remove(blockers, blocker);
- for (int i = 0; i < numGroups; i++) {
- if (node.getData().groups.get(i).canBlock(blocker, game)) {
- CombatSimulator combat = copier.copy(node.getData());
- combat.groups.get(i).blockers.add(new CreatureSimulator(blocker));
- TreeNode child = new TreeNode<>(combat);
- node.addChild(child);
- addBlockSimulations(subList, child, game);
- combat.simulate(game);
- }
- }
- }
- }
-
protected List remove(List source, Permanent element) {
List newList = new ArrayList<>();
for (Permanent permanent : source) {
@@ -1733,106 +1270,13 @@ public class ComputerPlayer extends PlayerImpl {
return newList;
}
- protected CombatSimulator getBestSimulation(TreeNode simulations) {
- CombatSimulator best = simulations.getData();
- int bestResult = best.evaluate();
- for (TreeNode node : simulations.getChildren()) {
- CombatSimulator bestSub = getBestSimulation(node);
- if (bestSub.evaluate() > bestResult) {
- best = node.getData();
- bestResult = best.evaluate();
- }
- }
- return best;
- }
-
- protected CombatSimulator getWorstSimulation(TreeNode simulations) {
- CombatSimulator worst = simulations.getData();
- int worstResult = worst.evaluate();
- for (TreeNode node : simulations.getChildren()) {
- CombatSimulator worstSub = getWorstSimulation(node);
- if (worstSub.evaluate() < worstResult) {
- worst = node.getData();
- worstResult = worst.evaluate();
- }
- }
- return worst;
- }
-
- protected List threats(UUID playerId, Ability source, FilterPermanent filter, Game game, List targets) {
- return threats(playerId, source, filter, game, targets, true);
- }
-
- protected List threats(UUID playerId, Ability source, FilterPermanent filter, Game game, List targets, boolean mostValuableGoFirst) {
- // most valuable/powerfully permanents goes at first
- List threats;
- if (playerId == null) {
- threats = game.getBattlefield().getActivePermanents(filter, this.getId(), source, game); // all permanents within the range of the player
- } else {
- FilterPermanent filterCopy = filter.copy();
- filterCopy.add(new ControllerIdPredicate(playerId));
- threats = game.getBattlefield().getActivePermanents(filter, this.getId(), source, game);
- }
- Iterator it = threats.iterator();
- while (it.hasNext()) { // remove permanents already targeted
- Permanent test = it.next();
- if (targets.contains(test.getId()) || (playerId != null && !test.getControllerId().equals(playerId))) {
- it.remove();
- }
- }
- Collections.sort(threats, new PermanentComparator(game));
- if (mostValuableGoFirst) {
- Collections.reverse(threats);
- }
- return threats;
- }
-
protected void logList(String message, List list) {
StringBuilder sb = new StringBuilder();
sb.append(message).append(": ");
for (MageObject object : list) {
sb.append(object.getName()).append(',');
}
- log.info(sb.toString());
- }
-
- private void playRemoval(Set creatures, Game game) {
- for (UUID creatureId : creatures) {
- for (Card card : this.playableInstant) {
- if (card.getSpellAbility().canActivate(playerId, game).canActivate()) {
- for (Effect effect : card.getSpellAbility().getEffects()) {
- if (effect.getOutcome() == Outcome.DestroyPermanent || effect.getOutcome() == Outcome.ReturnToHand) {
- if (card.getSpellAbility().getTargets().get(0).canTarget(creatureId, card.getSpellAbility(), game)) {
- if (this.activateAbility(card.getSpellAbility(), game)) {
- return;
- }
- }
- }
- }
- }
- }
- }
- }
-
- private void playDamage(Set creatures, Game game) {
- for (UUID creatureId : creatures) {
- Permanent creature = game.getPermanent(creatureId);
- for (Card card : this.playableInstant) {
- if (card.getSpellAbility().canActivate(playerId, game).canActivate()) {
- for (Effect effect : card.getSpellAbility().getEffects()) {
- if (effect instanceof DamageTargetEffect) {
- if (card.getSpellAbility().getTargets().get(0).canTarget(creatureId, card.getSpellAbility(), game)) {
- if (((DamageTargetEffect) effect).getAmount() > (creature.getPower().getValue() - creature.getDamage())) {
- if (this.activateAbility(card.getSpellAbility(), game)) {
- return;
- }
- }
- }
- }
- }
- }
- }
- }
+ logger.info(sb.toString());
}
@Override
@@ -1845,22 +1289,6 @@ public class ComputerPlayer extends PlayerImpl {
return new ComputerPlayer(this);
}
- private boolean tryAddTarget(Target target, UUID id, int amount, Ability source, Game game) {
- // workaround to check successfully targets add
- int before = target.getTargets().size();
- target.addTarget(id, amount, source, game);
- int after = target.getTargets().size();
- return before != after;
- }
-
- /**
- * Returns an opponent by random
- */
- @Deprecated // TODO: rework all usages and replace to all possible opponents instead single
- private UUID getRandomOpponent(Game game) {
- return RandomUtil.randomFromCollection(game.getOpponents(getId(), true));
- }
-
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
Map usable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), card, game.getState().getZone(card.getId()), noMana);
@@ -1905,12 +1333,4 @@ public class ComputerPlayer extends PlayerImpl {
// all human players converted to computer and analyse
this.human = false;
}
-
- public long getLastThinkTime() {
- return lastThinkTime;
- }
-
- public void setLastThinkTime(long lastThinkTime) {
- this.lastThinkTime = lastThinkTime;
- }
}
\ No newline at end of file
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatGroupSimulator.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatGroupSimulator.java
deleted file mode 100644
index ffa8bdee0e6..00000000000
--- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatGroupSimulator.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package mage.player.ai.simulators;
-
-import mage.game.Game;
-import mage.game.permanent.Permanent;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-/**
- *
- * @author BetaSteward_at_googlemail.com
- */
-public class CombatGroupSimulator implements Serializable {
- public List attackers = new ArrayList<>();
- public List blockers = new ArrayList<>();
- public UUID defenderId;
- public boolean defenderIsPlaneswalker;
- public int unblockedDamage;
- private CreatureSimulator attacker;
-
- public CombatGroupSimulator(UUID defenderId, List attackers, List blockers, Game game) {
- this.defenderId = defenderId;
- for (UUID attackerId: attackers) {
- Permanent permanent = game.getPermanent(attackerId);
- this.attackers.add(new CreatureSimulator(permanent));
- }
- for (UUID blockerId: blockers) {
- Permanent permanent = game.getPermanent(blockerId);
- this.blockers.add(new CreatureSimulator(permanent));
- }
- //NOTE: assumes no banding
- attacker = this.attackers.get(0);
- }
-
- private boolean hasFirstOrDoubleStrike() {
- for (CreatureSimulator creature: attackers) {
- if (creature.hasDoubleStrike || creature.hasFirstStrike)
- return true;
- }
- for (CreatureSimulator creature: blockers) {
- if (creature.hasDoubleStrike || creature.hasFirstStrike)
- return true;
- }
- return false;
- }
-
- public boolean canBlock(Permanent blocker, Game game) {
- return blocker.canBlock(attacker.id, game);
- }
-
- public void simulateCombat(Game game) {
- unblockedDamage = 0;
-
- if (hasFirstOrDoubleStrike())
- assignDamage(true, game);
- assignDamage(false, game);
- }
-
- private void assignDamage(boolean first, Game game) {
- if (blockers.isEmpty()) {
- if (canDamage(attacker, first))
- unblockedDamage += attacker.power;
- }
- else if (blockers.size() == 1) {
- CreatureSimulator blocker = blockers.get(0);
- if (canDamage(attacker, first)) {
- if (attacker.hasTrample) {
- int lethalDamage = blocker.getLethalDamage(game);
- if (attacker.power > lethalDamage) {
- blocker.damage += lethalDamage;
- unblockedDamage += attacker.power - lethalDamage;
- }
- else {
- blocker.damage += attacker.power;
- }
- }
- }
- if (canDamage(blocker, first)) {
- attacker.damage += blocker.power;
- }
- }
- else {
- int damage = attacker.power;
- for (CreatureSimulator blocker: blockers) {
- if (damage > 0 && canDamage(attacker, first)) {
- int lethalDamage = blocker.getLethalDamage(game);
- if (damage > lethalDamage) {
- blocker.damage += lethalDamage;
- damage -= lethalDamage;
- }
- else {
- blocker.damage += damage;
- damage = 0;
- }
- }
- if (canDamage(blocker, first)) {
- attacker.damage += blocker.power;
- }
- }
- if (damage > 0) {
- if (attacker.hasTrample) {
- unblockedDamage += damage;
- }
- else {
- blockers.get(0).damage += damage;
- }
- }
- }
- }
-
- private boolean canDamage(CreatureSimulator creature, boolean first) {
- if (first && (creature.hasFirstStrike || creature.hasDoubleStrike))
- return true;
- if (!first && (!creature.hasFirstStrike || creature.hasDoubleStrike))
- return true;
- return false;
- }
-
- /**
- * returns 3 attacker survives blockers destroyed
- * returns 2 both destroyed
- * returns 1 both survive
- * returns 0 attacker destroyed blockers survive
- *
- * @return int
- */
- public int evaluateCombat() {
- int survivingBlockers = 0;
- for (CreatureSimulator blocker: blockers) {
- if (blocker.damage < blocker.toughness)
- survivingBlockers++;
- }
- if (attacker.isDead()) {
- if (survivingBlockers > 0) {
- return 0;
- }
- return 2;
- }
- else {
- if (survivingBlockers > 0) {
- return 1;
- }
- return 3;
- }
- }
-}
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatSimulator.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatSimulator.java
deleted file mode 100644
index 7157962a522..00000000000
--- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CombatSimulator.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package mage.player.ai.simulators;
-
-import mage.counters.CounterType;
-import mage.game.Game;
-import mage.game.combat.CombatGroup;
-import mage.game.permanent.Permanent;
-import mage.players.Player;
-
-import java.io.Serializable;
-import java.util.*;
-import java.util.Map.Entry;
-
-/**
- *
- * @author BetaSteward_at_googlemail.com
- */
-public class CombatSimulator implements Serializable {
-
- public List groups = new ArrayList<>();
- public List defenders = new ArrayList<>();
- public Map playersLife = new HashMap<>();
- public Map planeswalkerLoyalty = new HashMap<>();
- public UUID attackerId;
- public int rating = 0;
-
- public static CombatSimulator load(Game game) {
- CombatSimulator simCombat = new CombatSimulator();
- for (CombatGroup group: game.getCombat().getGroups()) {
- simCombat.groups.add(new CombatGroupSimulator(group.getDefenderId(), group.getAttackers(), group.getBlockers(), game));
- }
- for (UUID defenderId: game.getCombat().getDefenders()) {
- simCombat.defenders.add(defenderId);
- Player player = game.getPlayer(defenderId);
- if (player != null) {
- simCombat.playersLife.put(defenderId, player.getLife());
- }
- else {
- Permanent permanent = game.getPermanent(defenderId);
- simCombat.planeswalkerLoyalty.put(defenderId, permanent.getCounters(game).getCount(CounterType.LOYALTY));
- }
- }
- return simCombat;
- }
-
- public CombatSimulator() {}
-
- public void clear() {
- groups.clear();
- defenders.clear();
- attackerId = null;
- }
-
- public void simulate(Game game) {
- for (CombatGroupSimulator group: groups) {
- group.simulateCombat(game);
- }
- }
-
- public int evaluate() {
- Map damage = new HashMap<>();
- int result = 0;
- for (CombatGroupSimulator group: groups) {
- if (!damage.containsKey(group.defenderId)) {
- damage.put(group.defenderId, group.unblockedDamage);
- }
- else {
- damage.put(group.defenderId, damage.get(group.defenderId) + group.unblockedDamage);
- }
- }
- //check for lethal damage to player
- for (Entry entry: playersLife.entrySet()) {
- if (damage.containsKey(entry.getKey()) && entry.getValue() <= damage.get(entry.getKey())) {
- //TODO: check for protection
- //NOTE: not applicable for mulitplayer games
- return Integer.MAX_VALUE;
- }
- }
-
- for (CombatGroupSimulator group: groups) {
- result += group.evaluateCombat();
- }
-
- rating = result;
- return result;
- }
-}
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CreatureSimulator.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CreatureSimulator.java
deleted file mode 100644
index 1a5e7fe7b85..00000000000
--- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/CreatureSimulator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package mage.player.ai.simulators;
-
-import mage.abilities.keyword.DoubleStrikeAbility;
-import mage.abilities.keyword.FirstStrikeAbility;
-import mage.abilities.keyword.TrampleAbility;
-import mage.filter.common.FilterCreaturePermanent;
-import mage.game.Game;
-import mage.game.permanent.Permanent;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.UUID;
-
-/**
- *
- * @author BetaSteward_at_googlemail.com
- */
-public class CreatureSimulator implements Serializable {
- public UUID id;
- public int damage;
- public int power;
- public int toughness;
- public boolean hasFirstStrike;
- public boolean hasDoubleStrike;
- public boolean hasTrample;
- public Permanent permanent;
-
- public CreatureSimulator(Permanent permanent) {
- this.id = permanent.getId();
- this.damage = permanent.getDamage();
- this.power = permanent.getPower().getValue();
- this.toughness = permanent.getToughness().getValue();
- this.hasDoubleStrike = permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
- this.hasFirstStrike = permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
- this.hasTrample = permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId());
- this.permanent = permanent;
- }
-
- public boolean isDead() {
- return damage >= toughness;
- }
-
- public int getLethalDamage(Game game) {
- List usePowerInsteadOfToughnessForDamageLethalityFilters = game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
- /*
- * for handling Zilortha, Strength Incarnate:
- * 2020-04-17
- * Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
- */
- boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
- .anyMatch(filter -> filter.match(permanent, game));
- int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
- // Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
- Math.max(power, 1) : toughness;
- return Math.max(lethalDamageThreshold - damage, 0);
- }
-}
diff --git a/Mage/src/main/java/mage/choices/Choice.java b/Mage/src/main/java/mage/choices/Choice.java
index c99d11df9e6..e4538b198e6 100644
--- a/Mage/src/main/java/mage/choices/Choice.java
+++ b/Mage/src/main/java/mage/choices/Choice.java
@@ -44,6 +44,9 @@ public interface Choice extends Serializable, Copyable {
ChoiceHintType getHintType();
+ /**
+ * For AI, mana auto-payment from choose color dialogs
+ */
boolean isManaColorChoice();
Choice setManaColorChoice(boolean manaColorChoice);
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index 1028d914a0c..eb4fe3f69c0 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -816,6 +816,9 @@ public interface Player extends MageItem, Copyable {
void construct(Tournament tournament, Deck deck);
+ /**
+ * Draft related: pick next card from a booster
+ */
void pickCard(List cards, Deck deck, Draft draft);
// TODO: add result, process it in AI code (if something put creature to attack then it can broke current AI logic)
diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java
index dbac98f943e..b95d18c8550 100644
--- a/Mage/src/main/java/mage/target/Target.java
+++ b/Mage/src/main/java/mage/target/Target.java
@@ -188,11 +188,15 @@ public interface Target extends Copyable, Serializable {
// some targets are chosen from players that are not the controller of the ability (e.g. Pandemonium)
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
+ // replace by shared method like setAbilityAndTargetControllers()
+ @Deprecated
void setTargetController(UUID playerId);
UUID getTargetController();
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
+ // replace by shared method like setAbilityAndTargetControllers()
+ @Deprecated
void setAbilityController(UUID playerId);
UUID getAbilityController();