mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
AI, refactor: removed outdated and unused code like simple priority and combat implementation, simple combat damage calculator, etc (part of #13638, #13766)
This commit is contained in:
parent
64bfa21b0c
commit
135c594de1
8 changed files with 29 additions and 901 deletions
|
|
@ -112,8 +112,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
|
|
||||||
protected void calculateActions(Game game) {
|
protected void calculateActions(Game game) {
|
||||||
if (!getNextAction(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();
|
currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||||
Game sim = createSimulation(game);
|
Game sim = createSimulation(game);
|
||||||
SimulationNode2.resetCount();
|
SimulationNode2.resetCount();
|
||||||
|
|
@ -146,15 +144,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
} else {
|
} else {
|
||||||
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
|
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 {
|
} else {
|
||||||
logger.debug("Next Action exists!");
|
logger.debug("Next Action exists!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@ package mage.player.ai;
|
||||||
import mage.*;
|
import mage.*;
|
||||||
import mage.abilities.*;
|
import mage.abilities.*;
|
||||||
import mage.abilities.costs.mana.*;
|
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.ActivatedManaAbilityImpl;
|
||||||
import mage.abilities.mana.ManaOptions;
|
import mage.abilities.mana.ManaOptions;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
|
@ -20,21 +16,12 @@ import mage.cards.repository.CardInfo;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
import mage.choices.Choice;
|
import mage.choices.Choice;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.filter.FilterPermanent;
|
|
||||||
import mage.filter.common.FilterCreatureForCombatBlock;
|
|
||||||
import mage.filter.common.FilterLandCard;
|
import mage.filter.common.FilterLandCard;
|
||||||
import mage.filter.common.FilterNonlandCard;
|
|
||||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.combat.CombatGroup;
|
|
||||||
import mage.game.draft.Draft;
|
import mage.game.draft.Draft;
|
||||||
import mage.game.events.GameEvent;
|
|
||||||
import mage.game.match.Match;
|
import mage.game.match.Match;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.tournament.Tournament;
|
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.ManaPoolItem;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.players.PlayerImpl;
|
import mage.players.PlayerImpl;
|
||||||
|
|
@ -51,14 +38,16 @@ import java.util.*;
|
||||||
import java.util.Map.Entry;
|
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.
|
||||||
* <p>
|
* <p>
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class ComputerPlayer extends PlayerImpl {
|
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
|
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;
|
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5;
|
||||||
|
|
||||||
|
|
||||||
// TODO: delete after target rework
|
// remember picked cards for better draft choices
|
||||||
private final transient Map<Mana, Card> unplayable = new TreeMap<>();
|
|
||||||
private final transient List<Card> playableNonInstant = new ArrayList<>();
|
|
||||||
private final transient List<Card> playableInstant = new ArrayList<>();
|
|
||||||
private final transient List<ActivatedAbility> playableAbilities = new ArrayList<>();
|
|
||||||
private final transient List<PickedCard> pickedCards = new ArrayList<>();
|
private final transient List<PickedCard> pickedCards = new ArrayList<>();
|
||||||
private final transient List<ColoredManaSymbol> chosenColors = new ArrayList<>();
|
private final transient List<ColoredManaSymbol> 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
|
// 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 boolean alreadyTryingToPayPhyrexian;
|
||||||
|
|
||||||
private transient long lastThinkTime = 0; // time in ms for last AI actions calc
|
|
||||||
|
|
||||||
public ComputerPlayer(String name, RangeOfInfluence range) {
|
public ComputerPlayer(String name, RangeOfInfluence range) {
|
||||||
super(name, range);
|
super(name, range);
|
||||||
human = false;
|
human = false;
|
||||||
|
|
@ -228,36 +211,6 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return makeChoice(outcome, target, source, game, null);
|
return makeChoice(outcome, target, source, game, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated // TODO: replace by source only version
|
|
||||||
protected Card selectCard(UUID abilityControllerId, List<Card> 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<Card> 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
|
@Override
|
||||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||||
|
|
||||||
|
|
@ -412,247 +365,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean priority(Game game) {
|
public boolean priority(Game game) {
|
||||||
game.resumeTimer(getTurnControlledBy());
|
// minimum implementation for do nothing
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pass(game);
|
pass(game);
|
||||||
return true;
|
return false;
|
||||||
} // end priorityPlay method
|
|
||||||
|
|
||||||
protected void playLand(Game game) {
|
|
||||||
Set<Card> 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<Card> 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<Card> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1053,7 +768,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
// choose creature type
|
// choose creature type
|
||||||
// TODO: WTF?! Creature types dialog text can changes, need to replace that code
|
// 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"))) {
|
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)
|
// 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
|
@Override
|
||||||
public void selectAttackers(Game game, UUID attackingPlayerId) {
|
public void selectAttackers(Game game, UUID attackingPlayerId) {
|
||||||
UUID opponentId = game.getCombat().getDefenders().iterator().next();
|
// do nothing, parent class must implement it
|
||||||
Attackers attackers = getPotentialAttackers(game);
|
|
||||||
List<Permanent> blockers = getOpponentBlockers(opponentId, game);
|
|
||||||
List<Permanent> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
|
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
|
||||||
List<Permanent> blockers = getAvailableBlockers(game);
|
// do nothing, parent class must implement it
|
||||||
|
|
||||||
CombatSimulator sim = simulateBlock(CombatSimulator.load(game), blockers, game);
|
|
||||||
|
|
||||||
List<CombatGroup> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1431,25 +1121,11 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
tournament.submitDeck(playerId, deck);
|
tournament.submitDeck(playerId, deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated // TODO: replace by source only version
|
public Card makePickCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) {
|
||||||
public Card selectBestCard(List<Card> cards, List<ColoredManaSymbol> 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<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability targetingSource, Game game) {
|
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return null;
|
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;
|
Card bestCard = null;
|
||||||
int maxScore = 0;
|
int maxScore = 0;
|
||||||
for (Card card : cards) {
|
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
|
if (bestCard == null) { // we need any card to prevent NPE in callers
|
||||||
betterCard = true;
|
betterCard = true;
|
||||||
} else if (score > maxScore) { // we need better card
|
} else if (score > maxScore) { // we need better card
|
||||||
if (target != null && targetingSource != null && game != null) {
|
betterCard = true;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// is it better than previous one?
|
// is it better than previous one?
|
||||||
if (betterCard) {
|
if (betterCard) {
|
||||||
|
|
@ -1475,59 +1145,21 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return bestCard;
|
return bestCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
|
|
||||||
*/
|
|
||||||
public Card selectWorstCardInner(List<Card> cards, List<ColoredManaSymbol> 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
|
@Override
|
||||||
public void pickCard(List<Card> cards, Deck deck, Draft draft) {
|
public void pickCard(List<Card> cards, Deck deck, Draft draft) {
|
||||||
|
// method used by DRAFT bot too
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
throw new IllegalArgumentException("No cards to pick from.");
|
throw new IllegalArgumentException("No cards to pick from.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Card bestCard = selectBestCard(cards, chosenColors);
|
Card bestCard = makePickCard(cards, chosenColors);
|
||||||
int maxScore = RateCard.rateCard(bestCard, chosenColors);
|
int maxScore = RateCard.rateCard(bestCard, chosenColors);
|
||||||
int pickedCardRate = RateCard.getBaseCardScore(bestCard);
|
int pickedCardRate = RateCard.getBaseCardScore(bestCard);
|
||||||
|
|
||||||
if (pickedCardRate <= 30) {
|
if (pickedCardRate <= 30) {
|
||||||
// if card is bad
|
// if card is bad
|
||||||
// try to counter pick without any color restriction
|
// 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);
|
int counterPickScore = RateCard.getBaseCardScore(counterPick);
|
||||||
// card is perfect
|
// card is perfect
|
||||||
// take it!
|
// take it!
|
||||||
|
|
@ -1552,10 +1184,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
colors += symbol.toString();
|
colors += symbol.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("[DEBUG] AI picked: " + bestCard.getName() + ", score=" + maxScore + ", deck colors=" + colors);
|
|
||||||
draft.addPick(playerId, bestCard.getId(), null);
|
draft.addPick(playerId, bestCard.getId(), null);
|
||||||
} catch (Exception e) {
|
} 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);
|
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<Permanent> creatures = super.getAvailableAttackers(game);
|
|
||||||
for (Permanent creature : creatures) {
|
|
||||||
int potential = combatPotential(creature, game);
|
|
||||||
if (potential > 0 && creature.getPower().getValue() > 0) {
|
|
||||||
List<Permanent> 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<Permanent> getOpponentBlockers(UUID opponentId, Game game) {
|
|
||||||
FilterCreatureForCombatBlock blockFilter = new FilterCreatureForCombatBlock();
|
|
||||||
return game.getBattlefield().getAllActivePermanents(blockFilter, opponentId, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CombatSimulator simulateAttack(Attackers attackers, List<Permanent> blockers, UUID opponentId, Game game) {
|
|
||||||
List<Permanent> 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<Permanent> 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<UUID>(), game));
|
|
||||||
}
|
|
||||||
CombatSimulator test = simulateBlock(combat, blockers, game);
|
|
||||||
if (test.evaluate() > bestResult) {
|
|
||||||
best = test;
|
|
||||||
bestResult = test.evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CombatSimulator simulateBlock(CombatSimulator combat, List<Permanent> blockers, Game game) {
|
|
||||||
TreeNode<CombatSimulator> simulations;
|
|
||||||
|
|
||||||
simulations = new TreeNode<>(combat);
|
|
||||||
addBlockSimulations(blockers, simulations, game);
|
|
||||||
combat.simulate(game);
|
|
||||||
|
|
||||||
return getWorstSimulation(simulations);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addBlockSimulations(List<Permanent> blockers, TreeNode<CombatSimulator> node, Game game) {
|
|
||||||
int numGroups = node.getData().groups.size();
|
|
||||||
Copier<CombatSimulator> copier = new Copier<>();
|
|
||||||
for (Permanent blocker : blockers) {
|
|
||||||
List<Permanent> 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<CombatSimulator> child = new TreeNode<>(combat);
|
|
||||||
node.addChild(child);
|
|
||||||
addBlockSimulations(subList, child, game);
|
|
||||||
combat.simulate(game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Permanent> remove(List<Permanent> source, Permanent element) {
|
protected List<Permanent> remove(List<Permanent> source, Permanent element) {
|
||||||
List<Permanent> newList = new ArrayList<>();
|
List<Permanent> newList = new ArrayList<>();
|
||||||
for (Permanent permanent : source) {
|
for (Permanent permanent : source) {
|
||||||
|
|
@ -1733,106 +1270,13 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return newList;
|
return newList;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CombatSimulator getBestSimulation(TreeNode<CombatSimulator> simulations) {
|
|
||||||
CombatSimulator best = simulations.getData();
|
|
||||||
int bestResult = best.evaluate();
|
|
||||||
for (TreeNode<CombatSimulator> node : simulations.getChildren()) {
|
|
||||||
CombatSimulator bestSub = getBestSimulation(node);
|
|
||||||
if (bestSub.evaluate() > bestResult) {
|
|
||||||
best = node.getData();
|
|
||||||
bestResult = best.evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CombatSimulator getWorstSimulation(TreeNode<CombatSimulator> simulations) {
|
|
||||||
CombatSimulator worst = simulations.getData();
|
|
||||||
int worstResult = worst.evaluate();
|
|
||||||
for (TreeNode<CombatSimulator> node : simulations.getChildren()) {
|
|
||||||
CombatSimulator worstSub = getWorstSimulation(node);
|
|
||||||
if (worstSub.evaluate() < worstResult) {
|
|
||||||
worst = node.getData();
|
|
||||||
worstResult = worst.evaluate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return worst;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Permanent> threats(UUID playerId, Ability source, FilterPermanent filter, Game game, List<UUID> targets) {
|
|
||||||
return threats(playerId, source, filter, game, targets, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Permanent> threats(UUID playerId, Ability source, FilterPermanent filter, Game game, List<UUID> targets, boolean mostValuableGoFirst) {
|
|
||||||
// most valuable/powerfully permanents goes at first
|
|
||||||
List<Permanent> 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<Permanent> 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<MageObject> list) {
|
protected void logList(String message, List<MageObject> list) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(message).append(": ");
|
sb.append(message).append(": ");
|
||||||
for (MageObject object : list) {
|
for (MageObject object : list) {
|
||||||
sb.append(object.getName()).append(',');
|
sb.append(object.getName()).append(',');
|
||||||
}
|
}
|
||||||
log.info(sb.toString());
|
logger.info(sb.toString());
|
||||||
}
|
|
||||||
|
|
||||||
private void playRemoval(Set<UUID> 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<UUID> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1845,22 +1289,6 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return new ComputerPlayer(this);
|
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
|
@Override
|
||||||
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
|
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
|
||||||
Map<UUID, SpellAbility> usable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), card, game.getState().getZone(card.getId()), noMana);
|
Map<UUID, SpellAbility> 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
|
// all human players converted to computer and analyse
|
||||||
this.human = false;
|
this.human = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLastThinkTime() {
|
|
||||||
return lastThinkTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastThinkTime(long lastThinkTime) {
|
|
||||||
this.lastThinkTime = lastThinkTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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<CreatureSimulator> attackers = new ArrayList<>();
|
|
||||||
public List<CreatureSimulator> blockers = new ArrayList<>();
|
|
||||||
public UUID defenderId;
|
|
||||||
public boolean defenderIsPlaneswalker;
|
|
||||||
public int unblockedDamage;
|
|
||||||
private CreatureSimulator attacker;
|
|
||||||
|
|
||||||
public CombatGroupSimulator(UUID defenderId, List<UUID> attackers, List<UUID> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<CombatGroupSimulator> groups = new ArrayList<>();
|
|
||||||
public List<UUID> defenders = new ArrayList<>();
|
|
||||||
public Map<UUID, Integer> playersLife = new HashMap<>();
|
|
||||||
public Map<UUID, Integer> 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<UUID, Integer> 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<UUID, Integer> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<FilterCreaturePermanent> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -44,6 +44,9 @@ public interface Choice extends Serializable, Copyable<Choice> {
|
||||||
|
|
||||||
ChoiceHintType getHintType();
|
ChoiceHintType getHintType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For AI, mana auto-payment from choose color dialogs
|
||||||
|
*/
|
||||||
boolean isManaColorChoice();
|
boolean isManaColorChoice();
|
||||||
|
|
||||||
Choice setManaColorChoice(boolean manaColorChoice);
|
Choice setManaColorChoice(boolean manaColorChoice);
|
||||||
|
|
|
||||||
|
|
@ -816,6 +816,9 @@ public interface Player extends MageItem, Copyable<Player> {
|
||||||
|
|
||||||
void construct(Tournament tournament, Deck deck);
|
void construct(Tournament tournament, Deck deck);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draft related: pick next card from a booster
|
||||||
|
*/
|
||||||
void pickCard(List<Card> cards, Deck deck, Draft draft);
|
void pickCard(List<Card> 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)
|
// TODO: add result, process it in AI code (if something put creature to attack then it can broke current AI logic)
|
||||||
|
|
|
||||||
|
|
@ -188,11 +188,15 @@ public interface Target extends Copyable<Target>, Serializable {
|
||||||
|
|
||||||
// some targets are chosen from players that are not the controller of the ability (e.g. Pandemonium)
|
// 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
|
// 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);
|
void setTargetController(UUID playerId);
|
||||||
|
|
||||||
UUID getTargetController();
|
UUID getTargetController();
|
||||||
|
|
||||||
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
|
// 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);
|
void setAbilityController(UUID playerId);
|
||||||
|
|
||||||
UUID getAbilityController();
|
UUID getAbilityController();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue