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);
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;
}

View file

@ -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);

View file

@ -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
*
* <p>
* 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;
}
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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);