AI: purged whole targeting code and replaced with simple and shared logic (part of #13638) (#13766)

- refactor: removed outdated/unused code from AI, battlefield score, targeting, etc;
- ai: removed all targeting and filters related logic from ComputerPlayer;
- ai: improved targeting with shared and simple logic due effect's outcome and possible targets priority;
- ai: improved get amount selection (now AI will not choose big and useless values);
- ai: improved spell selection on free cast (now AI will try to cast spells with valid targets only);
- tests: improved result table with first bad dialog info for fast access (part of #13638);
This commit is contained in:
Oleg Agafonov 2025-06-18 20:03:58 +03:00 committed by GitHub
parent 1fe0d92c86
commit ffe902d25e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 777 additions and 1615 deletions

View file

@ -0,0 +1,156 @@
package mage.player.ai;
import mage.MageItem;
import mage.MageObject;
import mage.cards.Card;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.player.ai.score.GameStateEvaluator2;
import mage.players.PlayableObjectsList;
import mage.players.Player;
import java.util.Comparator;
import java.util.UUID;
/**
* AI related code - compare and sort possible targets due target/effect type
*
* @author JayDi85
*/
public class PossibleTargetsComparator {
UUID abilityControllerId;
Game game;
PlayableObjectsList playableItems = new PlayableObjectsList();
public PossibleTargetsComparator(UUID abilityControllerId, Game game) {
this.abilityControllerId = abilityControllerId;
this.game = game;
}
public void findPlayableItems() {
this.playableItems = this.game.getPlayer(this.abilityControllerId).getPlayableObjects(this.game, Zone.ALL);
}
private int getScoreFromBattlefield(MageItem item) {
if (item instanceof Permanent) {
// use battlefield score instead simple life
return GameStateEvaluator2.evaluatePermanent((Permanent) item, game, false);
} else {
return getScoreFromLife(item);
}
}
private String getName(MageItem item) {
if (item instanceof Player) {
return ((Player) item).getName();
} else if (item instanceof MageObject) {
return ((MageObject) item).getName();
} else {
return "unknown";
}
}
private int getScoreFromLife(MageItem item) {
// TODO: replace permanent/card life by battlefield score?
int res = 0;
if (item instanceof Player) {
res = ((Player) item).getLife();
} else if (item instanceof Card) {
Card card = (Card) item;
if (card.isPlaneswalker(game)) {
res = card.getCounters(game).getCount(CounterType.LOYALTY);
} else if (card.isBattle(game)) {
res = card.getCounters(game).getCount(CounterType.DEFENSE);
} else {
int damage = 0;
if (card instanceof Permanent) {
damage = ((Permanent) card).getDamage();
}
res = Math.max(0, card.getToughness().getValue() - damage);
}
// instant
if (res == 0) {
res = card.getManaValue();
}
}
return res;
}
private boolean isMyItem(MageItem item) {
return PossibleTargetsSelector.isMyItem(this.abilityControllerId, item);
}
// sort by name-id at the end, so AI will use same choices in all simulations
private final Comparator<MageItem> BY_NAME = (o1, o2) -> getName(o2).compareTo(getName(o1));
private final Comparator<MageItem> BY_ID = Comparator.comparing(MageItem::getId);
private final Comparator<MageItem> BY_ME = (o1, o2) -> Boolean.compare(
isMyItem(o2),
isMyItem(o1)
);
private final Comparator<MageItem> BY_BIGGER_SCORE = (o1, o2) -> Integer.compare(
getScoreFromBattlefield(o2),
getScoreFromBattlefield(o1)
);
private final Comparator<MageItem> BY_PLAYABLE = (o1, o2) -> Boolean.compare(
this.playableItems.containsObject(o2.getId()),
this.playableItems.containsObject(o1.getId())
);
private final Comparator<MageItem> BY_LAND = (o1, o2) -> {
boolean isLand1 = o1 instanceof MageObject && ((MageObject) o1).isLand(game);
boolean isLand2 = o2 instanceof MageObject && ((MageObject) o2).isLand(game);
return Boolean.compare(isLand2, isLand1);
};
private final Comparator<MageItem> BY_TYPE_PLAYER = (o1, o2) -> Boolean.compare(
o2 instanceof Player,
o1 instanceof Player
);
private final Comparator<MageItem> BY_TYPE_PLANESWALKER = (o1, o2) -> {
boolean isPlaneswalker1 = o1 instanceof MageObject && ((MageObject) o1).isPlaneswalker(game);
boolean isPlaneswalker2 = o2 instanceof MageObject && ((MageObject) o2).isPlaneswalker(game);
return Boolean.compare(isPlaneswalker2, isPlaneswalker1);
};
private final Comparator<MageItem> BY_TYPE_BATTLE = (o1, o2) -> {
boolean isBattle1 = o1 instanceof MageObject && ((MageObject) o1).isBattle(game);
boolean isBattle2 = o2 instanceof MageObject && ((MageObject) o2).isBattle(game);
return Boolean.compare(isBattle2, isBattle1);
};
private final Comparator<MageItem> BY_TYPES = BY_TYPE_PLANESWALKER
.thenComparing(BY_TYPE_BATTLE)
.thenComparing(BY_TYPE_PLAYER);
/**
* Default sorting for good effects - put the biggest items to the top
*/
public final Comparator<MageItem> ANY_MOST_VALUABLE_FIRST = BY_TYPES
.thenComparing(BY_BIGGER_SCORE)
.thenComparing(BY_NAME)
.thenComparing(BY_ID);
public final Comparator<MageItem> ANY_MOST_VALUABLE_LAST = ANY_MOST_VALUABLE_FIRST.reversed();
/**
* Default sorting for good effects - put own and biggest items to the top
*/
public final Comparator<MageItem> MY_MOST_VALUABLE_FIRST = BY_ME
.thenComparing(ANY_MOST_VALUABLE_FIRST);
public final Comparator<MageItem> MY_MOST_VALUABLE_LAST = MY_MOST_VALUABLE_FIRST.reversed();
/**
* Sorting for discard effects - put the biggest unplayable at the top, lands at the end anyway
*/
public final Comparator<MageItem> ANY_UNPLAYABLE_AND_USELESS = BY_LAND.reversed()
.thenComparing(BY_PLAYABLE.reversed())
.thenComparing(ANY_MOST_VALUABLE_FIRST);
}

View file

@ -0,0 +1,184 @@
package mage.player.ai;
import mage.MageItem;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ControllableOrOwnerable;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyardBattlefieldOrStack;
import mage.target.common.TargetDiscard;
import java.util.*;
import java.util.stream.Collectors;
/**
* AI related code - find possible targets and sort it due priority
*
* @author JayDi85
*/
public class PossibleTargetsSelector {
Outcome outcome;
Target target;
UUID abilityControllerId;
Ability source;
Game game;
PossibleTargetsComparator comparators;
// possible targets lists
List<MageItem> me = new ArrayList<>();
List<MageItem> opponents = new ArrayList<>();
List<MageItem> any = new ArrayList<>(); // for outcomes with any target like copy
public PossibleTargetsSelector(Outcome outcome, Target target, UUID abilityControllerId, Ability source, Game game) {
this.outcome = outcome;
this.target = target;
this.abilityControllerId = abilityControllerId;
this.source = source;
this.game = game;
this.comparators = new PossibleTargetsComparator(abilityControllerId, game);
}
public void findNewTargets(Set<UUID> fromTargetsList) {
// collect new valid targets
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game).stream()
.filter(id -> !target.contains(id))
.filter(id -> fromTargetsList == null || fromTargetsList.contains(id))
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
.map(id -> {
Player player = game.getPlayer(id);
if (player != null) {
return player;
} else {
return game.getObject(id);
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// split targets between me and opponents
found.forEach(item -> {
if (isMyItem(abilityControllerId, item)) {
this.me.add(item);
} else {
this.opponents.add(item);
}
this.any.add(item);
});
if (target instanceof TargetDiscard) {
// sort due unplayable
sortByUnplayableAndUseless();
} else {
// sort due good/bad outcome
sortByMostValuableTargets();
}
}
/**
* Sorting for any good/bad effects
*/
private void sortByMostValuableTargets() {
if (isGoodEffect()) {
// for good effect must choose the biggest objects
this.me.sort(comparators.MY_MOST_VALUABLE_FIRST);
this.opponents.sort(comparators.MY_MOST_VALUABLE_LAST);
this.any.sort(comparators.ANY_MOST_VALUABLE_FIRST);
} else {
// for bad effect must choose the smallest objects
this.me.sort(comparators.MY_MOST_VALUABLE_LAST);
this.opponents.sort(comparators.MY_MOST_VALUABLE_FIRST);
this.any.sort(comparators.ANY_MOST_VALUABLE_LAST);
}
}
/**
* Sorting for discard
*/
private void sortByUnplayableAndUseless() {
// used
// no good or bad effect - you must choose
comparators.findPlayableItems();
this.me.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
this.opponents.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
this.any.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
}
/**
* Priority targets. Try to use as much as possible.
*/
public List<MageItem> getGoodTargets() {
if (isAnyEffect()) {
return this.any;
}
if (isGoodEffect()) {
return this.me;
} else {
return this.opponents;
}
}
/**
* Optional targets. Try to ignore bad targets (e.g. opponent's creatures for your good effect).
*/
public List<MageItem> getBadTargets() {
if (isAnyEffect()) {
return Collections.emptyList();
}
if (isGoodEffect()) {
return this.opponents;
} else {
return this.me;
}
}
public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
if (item instanceof Player) {
return item.getId().equals(abilityControllerId);
} else if (item instanceof ControllableOrOwnerable) {
return ((ControllableOrOwnerable) item).getControllerOrOwnerId().equals(abilityControllerId);
}
return false;
}
private boolean isAnyEffect() {
boolean isAnyEffect = outcome.anyTargetHasSameValue();
if (hasGoodExile()) {
isAnyEffect = true;
}
return isAnyEffect;
}
private boolean isGoodEffect() {
boolean isGoodEffect = outcome.isGood();
if (hasGoodExile()) {
isGoodEffect = true;
}
return isGoodEffect;
}
private boolean hasGoodExile() {
// exile workaround: exile is bad, but exile from library or graveyard in most cases is good
// (more exiled -- more good things you get, e.g. delve's pay or search cards with same name)
if (outcome == Outcome.Exile) {
if (Zone.GRAVEYARD.match(target.getZone())
|| Zone.LIBRARY.match(target.getZone())) {
// TargetCardInGraveyardBattlefieldOrStack - used for additional payment like Craft, so do not allow big cards for it
if (!(target instanceof TargetCardInGraveyardBattlefieldOrStack)) {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,173 @@
package mage.player.ai.score;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author ubeefx, nantuko
*/
public final class ArtificialScoringSystem {
public static final int WIN_GAME_SCORE = 100000000;
public static final int LOSE_GAME_SCORE = -WIN_GAME_SCORE;
private static final int[] LIFE_SCORES = {0, 1000, 2000, 3000, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7400, 7800, 8200, 8600, 9000, 9200, 9400, 9600, 9800, 10000};
private static final int MAX_LIFE = LIFE_SCORES.length - 1;
private static final int UNKNOWN_CARD_SCORE = 300;
private static final int PERMANENT_SCORE = 300;
private static final int LIFE_ABOVE_MULTIPLIER = 100;
public static int getCardDefinitionScore(final Game game, final Card card) {
int value = 3; //TODO: add new rating system card value
if (card.isLand(game)) {
int score = (int) ((value / 2.0f) * 50);
//TODO: check this for "any color" lands
//TODO: check this for dual and filter lands
/*for (Mana mana : card.getMana()) {
score += 50;
}*/
score += card.getMana().size() * 50;
return score;
}
final int score = value * 100 - card.getManaCost().manaValue() * 20;
if (card.getCardType(game).contains(CardType.CREATURE)) {
return score + (card.getPower().getValue() + card.getToughness().getValue()) * 10;
} else {
return score + (/*card.getRemoval()*50*/+(card.getRarity() == null ? 0 : card.getRarity().getRating() * 30));
}
}
public static int getFixedPermanentScore(final Game game, final Permanent permanent) {
//TODO: cache it inside Card
int score = getCardDefinitionScore(game, permanent);
score += PERMANENT_SCORE;
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
// TODO: implement in the mage core
//score + =cardDefinition.getActivations().size()*50;
//score += cardDefinition.getManaActivations().size()*80;
} else {
if (permanent.hasSubtype(SubType.EQUIPMENT, game)) {
score += 100;
}
}
return score;
}
public static int getDynamicPermanentScore(final Game game, final Permanent permanent) {
int score = permanent.getCounters(game).getCount(CounterType.CHARGE) * 30;
score += permanent.getCounters(game).getCount(CounterType.LEVEL) * 30;
score -= permanent.getDamage() * 2;
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
final int power = permanent.getPower().getValue();
final int toughness = permanent.getToughness().getValue();
int abilityScore = 0;
for (Ability ability : permanent.getAbilities(game)) {
abilityScore += MagicAbility.getAbilityScore(ability);
}
score += power * 300 + getPositive(toughness) * 200 + abilityScore * (getPositive(power) + 1) / 2;
int enchantments = 0;
int equipments = 0;
for (UUID uuid : permanent.getAttachments()) {
MageObject object = game.getObject(uuid);
if (object instanceof Card) {
Card card = (Card) object;
// TODO: implement getOutcomeTotal for permanents and cards too (not only attachments)
int outcomeScore = card.getAbilities(game).getOutcomeTotal();
if (card.getCardType(game).contains(CardType.ENCHANTMENT)) {
enchantments = enchantments + outcomeScore * 100;
} else {
equipments = equipments + outcomeScore * 50;
}
}
}
score += equipments + enchantments;
}
return score;
}
public static int getCombatPermanentScore(final Game game, final Permanent permanent) {
int score = 0;
if (!canTap(game, permanent)) {
score += getTappedScore(game, permanent);
}
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
if (!permanent.canAttack(null, game)) {
score -= 100;
}
if (!permanent.canBlockAny(game)) {
score -= 30;
}
}
return score;
}
private static boolean canTap(Game game, Permanent permanent) {
return !permanent.isTapped()
&& (!permanent.hasSummoningSickness()
|| !permanent.getCardType(game).contains(CardType.CREATURE)
|| permanent.getAbilities(game).contains(HasteAbility.getInstance()));
}
private static int getPositive(int value) {
return Math.max(0, value);
}
public static int getTappedScore(Game game, final Permanent permanent) {
if (permanent.isCreature(game)) {
return -100;
} else if (permanent.isLand(game)) {
return -20; // means probably no mana available (should be greater than passivity penalty
} else {
return -2;
}
}
public static int getLifeScore(final int life) {
if (life > MAX_LIFE) {
return LIFE_SCORES[MAX_LIFE] + (life - MAX_LIFE) * LIFE_ABOVE_MULTIPLIER;
} else if (life >= 0) {
return LIFE_SCORES[life];
} else {
return 0;
}
}
public static int getManaScore(final int amount) {
return -amount;
}
public static int getAttackerScore(final Permanent attacker) {
//TODO: implement this
/*int score = attacker.getPower().getValue() * 5 + attacker.lethalDamage * 2 - attacker.candidateBlockers.length;
for (final MagicCombatCreature blocker : attacker.candidateBlockers) {
score -= blocker.power;
}
// Dedicated attacker.
if (attacker.hasAbility(MagicAbility.AttacksEachTurnIfAble) || attacker.hasAbility(MagicAbility.CannotBlock)) {
score += 10;
}
// Abilities for attacking.
if (attacker.hasAbility(MagicAbility.Trample) || attacker.hasAbility(MagicAbility.Vigilance)) {
score += 8;
}
// Dangerous to block.
if (!attacker.normalDamage || attacker.hasAbility(MagicAbility.FirstStrike) || attacker.hasAbility(MagicAbility.Indestructible)) {
score += 7;
}
*/
int score = 0;
return score;
}
}

View file

@ -0,0 +1,238 @@
package mage.player.ai.score;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import org.apache.log4j.Logger;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.constants.Outcome;
/**
* @author nantuko
* <p>
* This evaluator is only good for two player games
*/
public final class GameStateEvaluator2 {
private static final Logger logger = Logger.getLogger(GameStateEvaluator2.class);
public static final int WIN_GAME_SCORE = 100000000;
public static final int LOSE_GAME_SCORE = -WIN_GAME_SCORE;
public static final int HAND_CARD_SCORE = 5;
public static PlayerEvaluateScore evaluate(UUID playerId, Game game) {
return evaluate(playerId, game, true);
}
public static PlayerEvaluateScore evaluate(UUID playerId, Game game, boolean useCombatPermanentScore) {
// TODO: add multi opponents support, so AI can take better actions
Player player = game.getPlayer(playerId);
// must find all leaved opponents
Player opponent = game.getPlayer(game.getOpponents(playerId, false).stream().findFirst().orElse(null));
if (opponent == null) {
return new PlayerEvaluateScore(playerId, WIN_GAME_SCORE);
}
if (game.checkIfGameIsOver()) {
if (player.hasLost()
|| opponent.hasWon()) {
return new PlayerEvaluateScore(playerId, LOSE_GAME_SCORE);
}
if (opponent.hasLost()
|| player.hasWon()) {
return new PlayerEvaluateScore(playerId, WIN_GAME_SCORE);
}
}
int playerLifeScore = 0;
int opponentLifeScore = 0;
if (player.getLife() <= 0) { // we don't want a tie
playerLifeScore = ArtificialScoringSystem.LOSE_GAME_SCORE;
} else if (opponent.getLife() <= 0) {
playerLifeScore = ArtificialScoringSystem.WIN_GAME_SCORE;
} else {
playerLifeScore = ArtificialScoringSystem.getLifeScore(player.getLife());
opponentLifeScore = ArtificialScoringSystem.getLifeScore(opponent.getLife()); // TODO: minus
}
int playerPermanentsScore = 0;
int opponentPermanentsScore = 0;
try {
StringBuilder sbPlayer = new StringBuilder();
StringBuilder sbOpponent = new StringBuilder();
// add values of player
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
int onePermScore = evaluatePermanent(permanent, game, useCombatPermanentScore);
playerPermanentsScore += onePermScore;
if (logger.isDebugEnabled()) {
sbPlayer.append(permanent.getName()).append('[').append(onePermScore).append("] ");
}
}
if (logger.isDebugEnabled()) {
sbPlayer.insert(0, playerPermanentsScore + " - ");
sbPlayer.insert(0, "Player..: ");
logger.debug(sbPlayer);
}
// add values of opponent
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) {
int onePermScore = evaluatePermanent(permanent, game, useCombatPermanentScore);
opponentPermanentsScore += onePermScore;
if (logger.isDebugEnabled()) {
sbOpponent.append(permanent.getName()).append('[').append(onePermScore).append("] ");
}
}
if (logger.isDebugEnabled()) {
sbOpponent.insert(0, opponentPermanentsScore + " - ");
sbOpponent.insert(0, "Opponent: ");
logger.debug(sbOpponent);
}
} catch (Throwable t) {
}
// TODO: add card evaluator like permanent evaluator
// - same card on battlefield must score x2 compared to hand, so AI will want to play it;
// - other zones must score cards same way, example: battlefield = x, hand = x * 0.1, graveyard = x * 0.5, exile = x * 0.3
// - possible bug in wrong score: instant and sorcery on hand will be more valuable compared to other zones,
// so AI will keep it in hand. Possible fix: look at card type and apply zones multipliers due special
// table like:
// * battlefield needs in creatures and enchantments/auras;
// * hand needs in instants and sorceries
// * graveyard needs in anything after battlefield and hand;
// * exile needs in nothing;
// * commander zone needs in nothing;
// - additional improve: use revealed data to score opponent's hand:
// * known card by card evaluator;
// * unknown card by max value (so AI will use reveal to make opponent's total score lower -- is it helps???)
int playerHandScore = player.getHand().size() * HAND_CARD_SCORE;
int opponentHandScore = opponent.getHand().size() * HAND_CARD_SCORE;
int score = (playerLifeScore - opponentLifeScore)
+ (playerPermanentsScore - opponentPermanentsScore)
+ (playerHandScore - opponentHandScore);
logger.debug(score
+ " total Score (life:" + (playerLifeScore - opponentLifeScore)
+ " permanents:" + (playerPermanentsScore - opponentPermanentsScore)
+ " hand:" + (playerHandScore - opponentHandScore) + ')');
return new PlayerEvaluateScore(
playerId,
playerLifeScore, playerHandScore, playerPermanentsScore,
opponentLifeScore, opponentHandScore, opponentPermanentsScore);
}
public static int evaluatePermanent(Permanent permanent, Game game, boolean useCombatPermanentScore) {
// prevent AI from attaching bad auras to its own permanents ex: Brainwash and Demonic Torment (no immediate penalty on the battlefield)
int value = 0;
if (!permanent.getAttachments().isEmpty()) {
for (UUID attachmentId : permanent.getAttachments()) {
Permanent attachment = game.getPermanent(attachmentId);
for (Ability a : attachment.getAbilities(game)) {
for (Effect e : a.getEffects()) {
if (e.getOutcome().equals(Outcome.Detriment)
&& attachment.getControllerId().equals(permanent.getControllerId())) {
value -= 1000; // seems to work well ; -300 is not effective enough
}
}
}
}
}
value += ArtificialScoringSystem.getFixedPermanentScore(game, permanent);
value += ArtificialScoringSystem.getDynamicPermanentScore(game, permanent);
if (useCombatPermanentScore) {
value += ArtificialScoringSystem.getCombatPermanentScore(game, permanent);
}
return value;
}
public static class PlayerEvaluateScore {
private UUID playerId;
private int playerLifeScore = 0;
private int playerHandScore = 0;
private int playerPermanentsScore = 0;
private int opponentLifeScore = 0;
private int opponentHandScore = 0;
private int opponentPermanentsScore = 0;
private int specialScore = 0; // special score (ignore all others, e.g. for win/lose game states)
public PlayerEvaluateScore(UUID playerId, int specialScore) {
this.playerId = playerId;
this.specialScore = specialScore;
}
public PlayerEvaluateScore(UUID playerId,
int playerLifeScore, int playerHandScore, int playerPermanentsScore,
int opponentLifeScore, int opponentHandScore, int opponentPermanentsScore) {
this.playerId = playerId;
this.playerLifeScore = playerLifeScore;
this.playerHandScore = playerHandScore;
this.playerPermanentsScore = playerPermanentsScore;
this.opponentLifeScore = opponentLifeScore;
this.opponentHandScore = opponentHandScore;
this.opponentPermanentsScore = opponentPermanentsScore;
}
public UUID getPlayerId() {
return this.playerId;
}
public int getPlayerScore() {
return playerLifeScore + playerHandScore + playerPermanentsScore;
}
public int getOpponentScore() {
return opponentLifeScore + opponentHandScore + opponentPermanentsScore;
}
public int getTotalScore() {
if (specialScore != 0) {
return specialScore;
} else {
return getPlayerScore() - getOpponentScore();
}
}
public int getPlayerLifeScore() {
return playerLifeScore;
}
public int getPlayerHandScore() {
return playerHandScore;
}
public int getPlayerPermanentsScore() {
return playerPermanentsScore;
}
public String getPlayerInfoFull() {
return "Life:" + playerLifeScore
+ ", Hand:" + playerHandScore
+ ", Perm:" + playerPermanentsScore;
}
public String getPlayerInfoShort() {
return "L:" + playerLifeScore
+ ",H:" + playerHandScore
+ ",P:" + playerPermanentsScore;
}
public String getOpponentInfoFull() {
return "Life:" + opponentLifeScore
+ ", Hand:" + opponentHandScore
+ ", Perm:" + opponentPermanentsScore;
}
public String getOpponentInfoShort() {
return "L:" + opponentLifeScore
+ ",H:" + opponentHandScore
+ ",P:" + opponentPermanentsScore;
}
}
}

View file

@ -0,0 +1,54 @@
package mage.player.ai.score;
import mage.abilities.Ability;
import mage.abilities.keyword.*;
import java.util.HashMap;
import java.util.Map;
/**
* TODO: outdated, replace by edh or commander brackets ability score
* @author nantuko
*/
public final class MagicAbility {
private static Map<String, Integer> scores = new HashMap<String, Integer>() {{
put(DeathtouchAbility.getInstance().getRule(), 60);
put(DefenderAbility.getInstance().getRule(), -100);
put(DoubleStrikeAbility.getInstance().getRule(), 100);
put(DoubleStrikeAbility.getInstance().getRule(), 100);
put(new ExaltedAbility().getRule(), 10);
put(FirstStrikeAbility.getInstance().getRule(), 50);
put(FlashAbility.getInstance().getRule(), 0);
put(FlyingAbility.getInstance().getRule(), 50);
put(new ForestwalkAbility().getRule(), 10);
put(HasteAbility.getInstance().getRule(), 0);
put(IndestructibleAbility.getInstance().getRule(), 150);
put(InfectAbility.getInstance().getRule(), 60);
put(IntimidateAbility.getInstance().getRule(), 50);
put(new IslandwalkAbility().getRule(), 10);
put(new MountainwalkAbility().getRule(), 10);
put(new PlainswalkAbility().getRule(), 10);
put(ReachAbility.getInstance().getRule(), 20);
put(ShroudAbility.getInstance().getRule(), 60);
put(new SwampwalkAbility().getRule(), 10);
put(TrampleAbility.getInstance().getRule(), 30);
put(new CantBeBlockedSourceAbility().getRule(), 100);
put(VigilanceAbility.getInstance().getRule(), 20);
put(WitherAbility.getInstance().getRule(), 30);
// gatecrash
put(new EvolveAbility().getRule(), 50);
put(new ExtortAbility().getRule(), 30);
}};
public static int getAbilityScore(Ability ability) {
if (!scores.containsKey(ability.getRule())) {
//System.err.println("Couldn't find ability score: " + ability.getClass().getSimpleName() + " - " + ability.toString());
//TODO: add handling protection from ..., levelup, kicker, etc. abilities
return 0;
}
return scores.get(ability.getRule());
}
}

View file

@ -1,65 +0,0 @@
package mage.player.ai.simulators;
import mage.abilities.ActivatedAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.player.ai.ComputerPlayer;
import mage.player.ai.PermanentEvaluator;
import mage.players.Player;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class ActionSimulator {
private ComputerPlayer player;
private List<Card> playableInstants = new ArrayList<>();
private List<ActivatedAbility> playableAbilities = new ArrayList<>();
private Game game;
public ActionSimulator(ComputerPlayer player) {
this.player = player;
}
public void simulate(Game game) {
}
public int evaluateState() {
// must find all leaved opponents
Player opponent = game.getPlayer(game.getOpponents(player.getId(), false).stream().findFirst().orElse(null));
if (opponent == null) {
return Integer.MAX_VALUE;
}
if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) {
return Integer.MIN_VALUE;
}
if (opponent.hasLost() || player.hasWon()) {
return Integer.MAX_VALUE;
}
}
int value = player.getLife();
value -= opponent.getLife();
PermanentEvaluator evaluator = new PermanentEvaluator();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
value += evaluator.evaluate(permanent, game);
}
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
value -= evaluator.evaluate(permanent, game);
}
value += player.getHand().size();
value -= opponent.getHand().size();
return value;
}
}