From 6cbf94bad6f3d3e9afbe172a490107932af897a1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 29 Jan 2020 07:38:08 +0400 Subject: [PATCH] AI: improved usage of attachments: * AI can play equipment/aura cards more frequent (computer can see and analyse all attached effects now); * AI can attach permanents with bad effects correctly (bad for opponents, good for itself); --- .../src/mage/player/ai/ComputerPlayer6.java | 32 ++-- .../src/mage/player/ai/ComputerPlayer7.java | 2 +- .../mage/player/ai/GameStateEvaluator2.java | 143 ++++++++++++++---- .../player/ai/ma/ArtificialScoringSystem.java | 30 ++-- .../src/mage/player/ai/ma/MagicAbility.java | 4 +- Mage.Sets/src/mage/cards/m/MiresGrasp.java | 2 +- 6 files changed, 149 insertions(+), 64 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 50cc2a4ba2..77f7ecfdf9 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -118,7 +118,12 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } Player player = game.getPlayer(playerId); - logger.info(new StringBuilder("[").append(game.getPlayer(playerId).getName()).append("], life = ").append(player.getLife()).toString()); + GameStateEvaluator2.PlayerEvaluateScore score = GameStateEvaluator2.evaluate(playerId, game); + logger.info(new StringBuilder("[").append(game.getPlayer(playerId).getName()).append("]") + .append(", life = ").append(player.getLife()) + .append(", score = ").append(score.getTotalScore()) + .append(" (").append(score.getPlayerInfoFull()).append(")") + .toString()); StringBuilder sb = new StringBuilder("-> Hand: ["); for (Card card : player.getHand().getCards(game)) { sb.append(card.getName()).append(';'); @@ -135,6 +140,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { if (permanent.isAttacking()) { sb.append("(attacking)"); } + sb.append(':' + String.valueOf(GameStateEvaluator2.evaluatePermanent(permanent, game))); sb.append(';'); } } @@ -200,13 +206,13 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); - return GameStateEvaluator2.evaluate(playerId, game); + return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } // Condition to stop deeper simulation if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) { - val = GameStateEvaluator2.evaluate(playerId, game); + val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>'); SimulationNode2 logNode = node; @@ -240,20 +246,20 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } if (game.checkIfGameIsOver()) { - val = GameStateEvaluator2.evaluate(playerId, game); + val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } else if (stepFinished) { logger.debug("Step finished"); - int testScore = GameStateEvaluator2.evaluate(playerId, game); + int testScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); if (game.isActivePlayer(playerId)) { if (testScore < currentScore) { // if score at end of step is worse than original score don't check further //logger.debug("Add Action -- abandoning check, no immediate benefit"); val = testScore; } else { - val = GameStateEvaluator2.evaluate(playerId, game); + val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } } else { - val = GameStateEvaluator2.evaluate(playerId, game); + val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } } else if (!node.getChildren().isEmpty()) { if (logger.isDebugEnabled()) { @@ -445,7 +451,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.info("interrupted"); - return GameStateEvaluator2.evaluate(playerId, game); + return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } node.setGameValue(game.getState().getValue(true).hashCode()); SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get()); @@ -490,7 +496,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { int val; if (action instanceof PassAbility && sim.getStack().isEmpty()) { // Stop to simulate deeper if PassAbility and stack is empty - val = GameStateEvaluator2.evaluate(this.getId(), sim); + val = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore(); } else { val = addActions(newNode, depth - 1, alpha, beta); } @@ -533,7 +539,9 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } if (depth == maxDepth) { - logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + "> " + bestNode.getAbilities().toString()); + GameStateEvaluator2.PlayerEvaluateScore score = GameStateEvaluator2.evaluate(this.getId(), bestNode.game); + String scoreInfo = " [" + score.getPlayerInfoShort() + "-" + score.getOpponentInfoShort() + "]"; + logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + bestNode.getAbilities().toString()); node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); @@ -933,7 +941,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { if (action instanceof PassAbility || action instanceof SpellAbility || action.getAbilityType() == AbilityType.MANA) { return false; } - int newVal = GameStateEvaluator2.evaluate(playerId, sim); + int newVal = GameStateEvaluator2.evaluate(playerId, sim).getTotalScore(); SimulationNode2 test = node.getParent(); while (test != null) { if (test.getPlayerId().equals(playerId)) { @@ -942,7 +950,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { if (test.getParent() != null) { Game prevGame = node.getGame(); if (prevGame != null) { - int oldVal = GameStateEvaluator2.evaluate(playerId, prevGame); + int oldVal = GameStateEvaluator2.evaluate(playerId, prevGame).getTotalScore(); if (oldVal >= newVal) { return true; } 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 7d2ea07761..84dfc55a9e 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 @@ -108,7 +108,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { protected void calculateActions(Game game) { if (!getNextAction(game)) { Date startTime = new Date(); - currentScore = GameStateEvaluator2.evaluate(playerId, game); + currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); Game sim = createSimulation(game); SimulationNode2.resetCount(); root = new SimulationNode2(null, sim, maxDepth, playerId); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java index 77a0705001..0997159e82 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java @@ -4,19 +4,18 @@ */ package mage.player.ai; -import java.util.UUID; import mage.game.Game; import mage.game.permanent.Permanent; import mage.player.ai.ma.ArtificialScoringSystem; import mage.players.Player; import org.apache.log4j.Logger; +import java.util.UUID; + /** - * * @author nantuko - * + *

* This evaluator is only good for two player games - * */ public final class GameStateEvaluator2 { @@ -25,43 +24,47 @@ public final class GameStateEvaluator2 { public static final int WIN_GAME_SCORE = 100000000; public static final int LOSE_GAME_SCORE = -WIN_GAME_SCORE; - public static int evaluate(UUID playerId, Game game) { + public static PlayerEvaluateScore evaluate(UUID playerId, Game game) { Player player = game.getPlayer(playerId); - Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); + Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); // TODO: add multi opponent support? if (game.checkIfGameIsOver()) { - if (player.hasLost() + if (player.hasLost() || opponent.hasWon()) { - return LOSE_GAME_SCORE; + return new PlayerEvaluateScore(LOSE_GAME_SCORE); } - if (opponent.hasLost() + if (opponent.hasLost() || player.hasWon()) { - return WIN_GAME_SCORE; + return new PlayerEvaluateScore(WIN_GAME_SCORE); } } - int lifeScore = 0; + + int playerLifeScore = 0; + int opponentLifeScore = 0; if (player.getLife() <= 0) { // we don't want a tie - lifeScore = ArtificialScoringSystem.LOSE_GAME_SCORE; + playerLifeScore = ArtificialScoringSystem.LOSE_GAME_SCORE; } else if (opponent.getLife() <= 0) { - lifeScore = ArtificialScoringSystem.WIN_GAME_SCORE; + playerLifeScore = ArtificialScoringSystem.WIN_GAME_SCORE; } else { - lifeScore = ArtificialScoringSystem.getLifeScore(player.getLife()) - ArtificialScoringSystem.getLifeScore(opponent.getLife()); + playerLifeScore = ArtificialScoringSystem.getLifeScore(player.getLife()); + opponentLifeScore = ArtificialScoringSystem.getLifeScore(opponent.getLife()); // TODO: minus } - int permanentScore = 0; - int playerScore = 0; - int opponentScore = 0; + + 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); - playerScore += onePermScore; + playerPermanentsScore += onePermScore; if (logger.isDebugEnabled()) { sbPlayer.append(permanent.getName()).append('[').append(onePermScore).append("] "); } } if (logger.isDebugEnabled()) { - sbPlayer.insert(0, playerScore + " - "); + sbPlayer.insert(0, playerPermanentsScore + " - "); sbPlayer.insert(0, "Player..: "); logger.debug(sbPlayer); } @@ -69,27 +72,32 @@ public final class GameStateEvaluator2 { // add values of opponent for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) { int onePermScore = evaluatePermanent(permanent, game); - opponentScore += onePermScore; + opponentPermanentsScore += onePermScore; if (logger.isDebugEnabled()) { sbOpponent.append(permanent.getName()).append('[').append(onePermScore).append("] "); } } if (logger.isDebugEnabled()) { - sbOpponent.insert(0, opponentScore + " - "); + sbOpponent.insert(0, opponentPermanentsScore + " - "); sbOpponent.insert(0, "Opponent: "); - logger.debug(sbOpponent); } - permanentScore = playerScore - opponentScore; } catch (Throwable t) { } - int handScore; - handScore = player.getHand().size() - opponent.getHand().size(); - handScore *= 5; - int score = lifeScore + permanentScore + handScore; - logger.debug(score + " total Score (life:" + lifeScore + " permanents:" + permanentScore + " hand:" + handScore + ')'); - return score; + int playerHandScore = player.getHand().size() * 5; + int opponentHandScore = opponent.getHand().size() * 5; + + 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( + playerLifeScore, playerHandScore, playerPermanentsScore, + opponentLifeScore, opponentHandScore, opponentPermanentsScore); } public static int evaluatePermanent(Permanent permanent, Game game) { @@ -104,4 +112,81 @@ public final class GameStateEvaluator2 { return value; } + public static class PlayerEvaluateScore { + 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 other) + + public PlayerEvaluateScore(int specialScore) { + this.specialScore = specialScore; + } + + public PlayerEvaluateScore(int playerLifeScore, int playerHandScore, int playerPermanentsScore, + int opponentLifeScore, int opponentHandScore, int opponentPermanentsScore) { + this.playerLifeScore = playerLifeScore; + this.playerHandScore = playerHandScore; + this.playerPermanentsScore = playerPermanentsScore; + this.opponentLifeScore = opponentLifeScore; + this.opponentHandScore = opponentHandScore; + this.opponentPermanentsScore = opponentPermanentsScore; + } + + 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; + } + } } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java index 379dadee6a..f9f4103705 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/ArtificialScoringSystem.java @@ -1,17 +1,17 @@ package mage.player.ai.ma; -import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.effects.Effect; import mage.abilities.keyword.HasteAbility; import mage.cards.Card; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * @author ubeefx, nantuko */ @@ -20,7 +20,7 @@ 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[] 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; @@ -79,29 +79,21 @@ public final class ArtificialScoringSystem { abilityScore += MagicAbility.getAbilityScore(ability); } score += power * 300 + getPositive(toughness) * 200 + abilityScore * (getPositive(power) + 1) / 2; - //TODO: it can be improved - //score += permanent.getEquipmentPermanents().size() * 50 + permanent.getAuraPermanents().size() * 100; int enchantments = 0; int equipments = 0; for (UUID uuid : permanent.getAttachments()) { - Card card = game.getCard(uuid); - if (card != null) { + MageObject object = game.getObject(uuid); + if (object instanceof Card) { + Card card = (Card) object; + int outcomeScore = object.getAbilities().getOutcomeTotal(); if (card.getCardType().contains(CardType.ENCHANTMENT)) { - Effect effect = card.getSpellAbility().getEffects().get(0); - if (effect != null) { - Outcome outcome = effect.getOutcome(); - if (outcome.isGood()) { - enchantments++; - } else if (outcome != Outcome.Detriment) { - enchantments--; - } - } + enchantments = enchantments + outcomeScore * 100; } else { - equipments++; + equipments = equipments + outcomeScore * 50; } } } - score += equipments * 50 + enchantments * 100; + score += equipments + enchantments; if (!permanent.canAttack(null, game)) { score -= 100; diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/MagicAbility.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/MagicAbility.java index bb854bf370..43fae122d1 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/MagicAbility.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ma/MagicAbility.java @@ -38,13 +38,13 @@ public final class MagicAbility { // 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.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; } diff --git a/Mage.Sets/src/mage/cards/m/MiresGrasp.java b/Mage.Sets/src/mage/cards/m/MiresGrasp.java index 0205aea176..9c76c6658a 100644 --- a/Mage.Sets/src/mage/cards/m/MiresGrasp.java +++ b/Mage.Sets/src/mage/cards/m/MiresGrasp.java @@ -28,7 +28,7 @@ public final class MiresGrasp extends CardImpl { // Enchant creature TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); - this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.UnboostCreature)); Ability ability = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(ability);