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);
This commit is contained in:
Oleg Agafonov 2020-01-29 07:38:08 +04:00
parent 89394ffe0a
commit 6cbf94bad6
6 changed files with 149 additions and 64 deletions

View file

@ -118,7 +118,12 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
} }
Player player = game.getPlayer(playerId); 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: ["); StringBuilder sb = new StringBuilder("-> Hand: [");
for (Card card : player.getHand().getCards(game)) { for (Card card : player.getHand().getCards(game)) {
sb.append(card.getName()).append(';'); sb.append(card.getName()).append(';');
@ -135,6 +140,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
if (permanent.isAttacking()) { if (permanent.isAttacking()) {
sb.append("(attacking)"); sb.append("(attacking)");
} }
sb.append(':' + String.valueOf(GameStateEvaluator2.evaluatePermanent(permanent, game)));
sb.append(';'); sb.append(';');
} }
} }
@ -200,13 +206,13 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
&& Thread.interrupted()) { && Thread.interrupted()) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
logger.debug("interrupted"); logger.debug("interrupted");
return GameStateEvaluator2.evaluate(playerId, game); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
} }
// Condition to stop deeper simulation // Condition to stop deeper simulation
if (depth <= 0 if (depth <= 0
|| SimulationNode2.nodeCount > maxNodes || SimulationNode2.nodeCount > maxNodes
|| game.checkIfGameIsOver()) { || game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>'); StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>');
SimulationNode2 logNode = node; SimulationNode2 logNode = node;
@ -240,20 +246,20 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
} }
if (game.checkIfGameIsOver()) { if (game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
} else if (stepFinished) { } else if (stepFinished) {
logger.debug("Step finished"); logger.debug("Step finished");
int testScore = GameStateEvaluator2.evaluate(playerId, game); int testScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
if (game.isActivePlayer(playerId)) { if (game.isActivePlayer(playerId)) {
if (testScore < currentScore) { if (testScore < currentScore) {
// if score at end of step is worse than original score don't check further // if score at end of step is worse than original score don't check further
//logger.debug("Add Action -- abandoning check, no immediate benefit"); //logger.debug("Add Action -- abandoning check, no immediate benefit");
val = testScore; val = testScore;
} else { } else {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
} }
} else { } else {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
} }
} else if (!node.getChildren().isEmpty()) { } else if (!node.getChildren().isEmpty()) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -445,7 +451,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
&& Thread.interrupted()) { && Thread.interrupted()) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
logger.info("interrupted"); logger.info("interrupted");
return GameStateEvaluator2.evaluate(playerId, game); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
} }
node.setGameValue(game.getState().getValue(true).hashCode()); node.setGameValue(game.getState().getValue(true).hashCode());
SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get()); SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get());
@ -490,7 +496,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
int val; int val;
if (action instanceof PassAbility && sim.getStack().isEmpty()) { if (action instanceof PassAbility && sim.getStack().isEmpty()) {
// Stop to simulate deeper if PassAbility and stack is empty // Stop to simulate deeper if PassAbility and stack is empty
val = GameStateEvaluator2.evaluate(this.getId(), sim); val = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore();
} else { } else {
val = addActions(newNode, depth - 1, alpha, beta); 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()); bestNode.setCombat(newNode.getChildren().get(0).getCombat());
} }
if (depth == maxDepth) { 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.clear();
node.children.add(bestNode); node.children.add(bestNode);
node.setScore(bestNode.getScore()); 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) { if (action instanceof PassAbility || action instanceof SpellAbility || action.getAbilityType() == AbilityType.MANA) {
return false; return false;
} }
int newVal = GameStateEvaluator2.evaluate(playerId, sim); int newVal = GameStateEvaluator2.evaluate(playerId, sim).getTotalScore();
SimulationNode2 test = node.getParent(); SimulationNode2 test = node.getParent();
while (test != null) { while (test != null) {
if (test.getPlayerId().equals(playerId)) { if (test.getPlayerId().equals(playerId)) {
@ -942,7 +950,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
if (test.getParent() != null) { if (test.getParent() != null) {
Game prevGame = node.getGame(); Game prevGame = node.getGame();
if (prevGame != null) { if (prevGame != null) {
int oldVal = GameStateEvaluator2.evaluate(playerId, prevGame); int oldVal = GameStateEvaluator2.evaluate(playerId, prevGame).getTotalScore();
if (oldVal >= newVal) { if (oldVal >= newVal) {
return true; return true;
} }

View file

@ -108,7 +108,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
protected void calculateActions(Game game) { protected void calculateActions(Game game) {
if (!getNextAction(game)) { if (!getNextAction(game)) {
Date startTime = new Date(); Date startTime = new Date();
currentScore = GameStateEvaluator2.evaluate(playerId, game); currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
Game sim = createSimulation(game); Game sim = createSimulation(game);
SimulationNode2.resetCount(); SimulationNode2.resetCount();
root = new SimulationNode2(null, sim, maxDepth, playerId); root = new SimulationNode2(null, sim, maxDepth, playerId);

View file

@ -4,19 +4,18 @@
*/ */
package mage.player.ai; package mage.player.ai;
import java.util.UUID;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.player.ai.ma.ArtificialScoringSystem; import mage.player.ai.ma.ArtificialScoringSystem;
import mage.players.Player; import mage.players.Player;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.util.UUID;
/** /**
*
* @author nantuko * @author nantuko
* * <p>
* This evaluator is only good for two player games * This evaluator is only good for two player games
*
*/ */
public final class GameStateEvaluator2 { public final class GameStateEvaluator2 {
@ -25,43 +24,47 @@ public final class GameStateEvaluator2 {
public static final int WIN_GAME_SCORE = 100000000; public static final int WIN_GAME_SCORE = 100000000;
public static final int LOSE_GAME_SCORE = -WIN_GAME_SCORE; 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 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 (game.checkIfGameIsOver()) {
if (player.hasLost() if (player.hasLost()
|| opponent.hasWon()) { || opponent.hasWon()) {
return LOSE_GAME_SCORE; return new PlayerEvaluateScore(LOSE_GAME_SCORE);
} }
if (opponent.hasLost() if (opponent.hasLost()
|| player.hasWon()) { || 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 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) { } else if (opponent.getLife() <= 0) {
lifeScore = ArtificialScoringSystem.WIN_GAME_SCORE; playerLifeScore = ArtificialScoringSystem.WIN_GAME_SCORE;
} else { } 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 playerPermanentsScore = 0;
int opponentScore = 0; int opponentPermanentsScore = 0;
try { try {
StringBuilder sbPlayer = new StringBuilder(); StringBuilder sbPlayer = new StringBuilder();
StringBuilder sbOpponent = new StringBuilder(); StringBuilder sbOpponent = new StringBuilder();
// add values of player // add values of player
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
int onePermScore = evaluatePermanent(permanent, game); int onePermScore = evaluatePermanent(permanent, game);
playerScore += onePermScore; playerPermanentsScore += onePermScore;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
sbPlayer.append(permanent.getName()).append('[').append(onePermScore).append("] "); sbPlayer.append(permanent.getName()).append('[').append(onePermScore).append("] ");
} }
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
sbPlayer.insert(0, playerScore + " - "); sbPlayer.insert(0, playerPermanentsScore + " - ");
sbPlayer.insert(0, "Player..: "); sbPlayer.insert(0, "Player..: ");
logger.debug(sbPlayer); logger.debug(sbPlayer);
} }
@ -69,27 +72,32 @@ public final class GameStateEvaluator2 {
// add values of opponent // add values of opponent
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) {
int onePermScore = evaluatePermanent(permanent, game); int onePermScore = evaluatePermanent(permanent, game);
opponentScore += onePermScore; opponentPermanentsScore += onePermScore;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
sbOpponent.append(permanent.getName()).append('[').append(onePermScore).append("] "); sbOpponent.append(permanent.getName()).append('[').append(onePermScore).append("] ");
} }
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
sbOpponent.insert(0, opponentScore + " - "); sbOpponent.insert(0, opponentPermanentsScore + " - ");
sbOpponent.insert(0, "Opponent: "); sbOpponent.insert(0, "Opponent: ");
logger.debug(sbOpponent); logger.debug(sbOpponent);
} }
permanentScore = playerScore - opponentScore;
} catch (Throwable t) { } catch (Throwable t) {
} }
int handScore;
handScore = player.getHand().size() - opponent.getHand().size();
handScore *= 5;
int score = lifeScore + permanentScore + handScore; int playerHandScore = player.getHand().size() * 5;
logger.debug(score + " total Score (life:" + lifeScore + " permanents:" + permanentScore + " hand:" + handScore + ')'); int opponentHandScore = opponent.getHand().size() * 5;
return 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(
playerLifeScore, playerHandScore, playerPermanentsScore,
opponentLifeScore, opponentHandScore, opponentPermanentsScore);
} }
public static int evaluatePermanent(Permanent permanent, Game game) { public static int evaluatePermanent(Permanent permanent, Game game) {
@ -104,4 +112,81 @@ public final class GameStateEvaluator2 {
return value; 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;
}
}
} }

View file

@ -1,17 +1,17 @@
package mage.player.ai.ma; package mage.player.ai.ma;
import java.util.UUID; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.HasteAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import java.util.UUID;
/** /**
* @author ubeefx, nantuko * @author ubeefx, nantuko
*/ */
@ -20,7 +20,7 @@ public final class ArtificialScoringSystem {
public static final int WIN_GAME_SCORE = 100000000; public static final int WIN_GAME_SCORE = 100000000;
public static final int LOSE_GAME_SCORE = -WIN_GAME_SCORE; 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 MAX_LIFE = LIFE_SCORES.length - 1;
private static final int UNKNOWN_CARD_SCORE = 300; private static final int UNKNOWN_CARD_SCORE = 300;
private static final int PERMANENT_SCORE = 300; private static final int PERMANENT_SCORE = 300;
@ -79,29 +79,21 @@ public final class ArtificialScoringSystem {
abilityScore += MagicAbility.getAbilityScore(ability); abilityScore += MagicAbility.getAbilityScore(ability);
} }
score += power * 300 + getPositive(toughness) * 200 + abilityScore * (getPositive(power) + 1) / 2; 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 enchantments = 0;
int equipments = 0; int equipments = 0;
for (UUID uuid : permanent.getAttachments()) { for (UUID uuid : permanent.getAttachments()) {
Card card = game.getCard(uuid); MageObject object = game.getObject(uuid);
if (card != null) { if (object instanceof Card) {
Card card = (Card) object;
int outcomeScore = object.getAbilities().getOutcomeTotal();
if (card.getCardType().contains(CardType.ENCHANTMENT)) { if (card.getCardType().contains(CardType.ENCHANTMENT)) {
Effect effect = card.getSpellAbility().getEffects().get(0); enchantments = enchantments + outcomeScore * 100;
if (effect != null) {
Outcome outcome = effect.getOutcome();
if (outcome.isGood()) {
enchantments++;
} else if (outcome != Outcome.Detriment) {
enchantments--;
}
}
} else { } else {
equipments++; equipments = equipments + outcomeScore * 50;
} }
} }
} }
score += equipments * 50 + enchantments * 100; score += equipments + enchantments;
if (!permanent.canAttack(null, game)) { if (!permanent.canAttack(null, game)) {
score -= 100; score -= 100;

View file

@ -38,13 +38,13 @@ public final class MagicAbility {
// gatecrash // gatecrash
put(new EvolveAbility().getRule(), 50); put(new EvolveAbility().getRule(), 50);
put(new ExtortAbility().getRule(), 30); put(new ExtortAbility().getRule(), 30);
}}; }};
public static int getAbilityScore(Ability ability) { public static int getAbilityScore(Ability ability) {
if (!scores.containsKey(ability.getRule())) { 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 //TODO: add handling protection from ..., levelup, kicker, etc. abilities
return 0; return 0;
} }

View file

@ -28,7 +28,7 @@ public final class MiresGrasp extends CardImpl {
// Enchant creature // Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent(); TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.UnboostCreature));
Ability ability = new EnchantAbility(auraTarget.getTargetName()); Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability); this.addAbility(ability);