Some improvements to simulated AI:

- When evaluating creatures, ignore temporary boosts unless the creature is likely to participate in combat.
  - Set default search depth to 2.
This commit is contained in:
Myrd
2015-03-09 02:45:10 +00:00
parent 9eac489ab4
commit 1a3025d3be
3 changed files with 47 additions and 19 deletions

View File

@@ -555,6 +555,8 @@ public class AiAttackController {
} }
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures) // Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
if (ai.getController() instanceof PlayerControllerAi) {
// Only do this if |ai| is actually an AI - as we could be trying to predict how the human will attack.
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory(); AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) { if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
@@ -562,6 +564,7 @@ public class AiAttackController {
attackersLeft.remove(attacker); attackersLeft.remove(attacker);
} }
} }
}
// Exalted // Exalted
if (combat.getAttackers().isEmpty()) { if (combat.getAttackers().isEmpty()) {

View File

@@ -1,9 +1,12 @@
package forge.ai.simulation; package forge.ai.simulation;
import forge.ai.AiAttackController;
import forge.ai.CreatureEvaluator; import forge.ai.CreatureEvaluator;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -17,10 +20,29 @@ public class GameStateEvaluator {
this.debugging = debugging; this.debugging = debugging;
} }
private static void debugPrint(String s) {
//System.err.println(s);
GameSimulator.debugPrint(s);
}
private Combat simulateUpcomingCombatThisTurn(Game game) {
PhaseHandler handler = game.getPhaseHandler();
if (handler.getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
return null;
}
AiAttackController aiAtk = new AiAttackController(handler.getPlayerTurn());
Combat combat = new Combat(handler.getPlayerTurn());
aiAtk.declareAttackers(combat);
return combat;
}
public Score getScoreForGameState(Game game, Player aiPlayer) { public Score getScoreForGameState(Game game, Player aiPlayer) {
if (game.isGameOver()) { if (game.isGameOver()) {
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE); return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
} }
Combat combat = simulateUpcomingCombatThisTurn(game);
int score = 0; int score = 0;
// TODO: more than 2 players // TODO: more than 2 players
int myCards = 0; int myCards = 0;
@@ -32,21 +54,21 @@ public class GameStateEvaluator {
theirCards++; theirCards++;
} }
} }
GameSimulator.debugPrint("My cards in hand: " + myCards); debugPrint("My cards in hand: " + myCards);
GameSimulator.debugPrint("Their cards in hand: " + theirCards); debugPrint("Their cards in hand: " + theirCards);
if (myCards > aiPlayer.getMaxHandSize()) { if (myCards > aiPlayer.getMaxHandSize()) {
// Count excess cards for less. // Count excess cards for less.
score += myCards - aiPlayer.getMaxHandSize(); score += myCards - aiPlayer.getMaxHandSize();
myCards = aiPlayer.getMaxHandSize(); myCards = aiPlayer.getMaxHandSize();
} }
score += 5 * myCards - 4 * theirCards; score += 5 * myCards - 4 * theirCards;
GameSimulator.debugPrint(" My life: " + aiPlayer.getLife()); debugPrint(" My life: " + aiPlayer.getLife());
score += 2 * aiPlayer.getLife(); score += 2 * aiPlayer.getLife();
int opponentIndex = 1; int opponentIndex = 1;
int opponentLife = 0; int opponentLife = 0;
for (Player opponent : game.getPlayers()) { for (Player opponent : game.getPlayers()) {
if (opponent != aiPlayer) { if (opponent != aiPlayer) {
GameSimulator.debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife()); debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
opponentLife += opponent.getLife(); opponentLife += opponent.getLife();
opponentIndex++; opponentIndex++;
} }
@@ -55,7 +77,7 @@ public class GameStateEvaluator {
int summonSickScore = score; int summonSickScore = score;
PhaseType gamePhase = game.getPhaseHandler().getPhase(); PhaseType gamePhase = game.getPhaseHandler().getPhase();
for (Card c : game.getCardsIn(ZoneType.Battlefield)) { for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
int value = evalCard(game, aiPlayer, c); int value = evalCard(game, aiPlayer, c, combat);
int summonSickValue = value; int summonSickValue = value;
// To make the AI hold-off on playing creatures in MAIN1 if they give no other benefits, // To make the AI hold-off on playing creatures in MAIN1 if they give no other benefits,
// keep track of the score while treating summon sick creatures as having a value of 0. // keep track of the score while treating summon sick creatures as having a value of 0.
@@ -67,32 +89,35 @@ public class GameStateEvaluator {
str += " " + c.getNetPower() + "/" + c.getNetToughness(); str += " " + c.getNetPower() + "/" + c.getNetToughness();
} }
if (c.getController() == aiPlayer) { if (c.getController() == aiPlayer) {
GameSimulator.debugPrint(" Battlefield: " + str + " = " + value); debugPrint(" Battlefield: " + str + " = " + value);
score += value; score += value;
summonSickScore += summonSickValue; summonSickScore += summonSickValue;
} else { } else {
GameSimulator.debugPrint(" Battlefield: " + str + " = -" + value); debugPrint(" Battlefield: " + str + " = -" + value);
score -= value; score -= value;
summonSickScore -= summonSickValue; summonSickScore -= summonSickValue;
} }
String nonAbilityText = c.getNonAbilityText(); String nonAbilityText = c.getNonAbilityText();
if (!nonAbilityText.isEmpty()) { if (!nonAbilityText.isEmpty()) {
GameSimulator.debugPrint(" "+nonAbilityText.replaceAll("CARDNAME", c.getName())); debugPrint(" "+nonAbilityText.replaceAll("CARDNAME", c.getName()));
} }
} }
GameSimulator.debugPrint("Score = " + score); debugPrint("Score = " + score);
return new Score(score, summonSickScore); return new Score(score, summonSickScore);
} }
protected int evalCard(Game game, Player aiPlayer, Card c) { protected int evalCard(Game game, Player aiPlayer, Card c, Combat combat) {
// TODO: These should be based on other considerations - e.g. in relation to opponents state. // TODO: These should be based on other considerations - e.g. in relation to opponents state.
if (c.isCreature()) { if (c.isCreature()) {
// Ignore temp boosts post combat, since it's a waste. // Ignore temp boosts post combat, since it's a waste.
// TODO: Make this smarter. Temp boosts pre-combat are also useless if there's no plan to attack // TODO: Make this smarter. Right now, it only looks if the creature will attack - but
// with that creature - or if you're just temporarily pumping down. // does not consider things like blocks or the outcome of combat.
// Also, sometimes temp boosts post combat could be useful - e.g. if you then want to make your // Also, sometimes temp boosts post combat could be useful - e.g. if you then want to make your
// creature fight another, etc. // creature fight another, etc.
ignoreTempBoosts = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE); ignoreTempBoosts = true;
if (combat != null && combat.isAttacking(c)) {
ignoreTempBoosts = false;
}
int result = eval.evaluateCreature(c); int result = eval.evaluateCreature(c);
return result; return result;
} else if (c.isLand()) { } else if (c.isLand()) {

View File

@@ -4,7 +4,7 @@ import forge.ai.simulation.GameStateEvaluator.Score;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class SimulationController { public class SimulationController {
private static int MAX_DEPTH = 5; private static int MAX_DEPTH = 2;
private int recursionDepth; private int recursionDepth;