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,11 +555,14 @@ public class AiAttackController {
}
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
for (Card attacker : this.attackers) {
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
combat.addAttacker(attacker, defender);
attackersLeft.remove(attacker);
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();
for (Card attacker : this.attackers) {
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
combat.addAttacker(attacker, defender);
attackersLeft.remove(attacker);
}
}
}

View File

@@ -1,9 +1,12 @@
package forge.ai.simulation;
import forge.ai.AiAttackController;
import forge.ai.CreatureEvaluator;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -17,10 +20,29 @@ public class GameStateEvaluator {
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) {
if (game.isGameOver()) {
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
}
Combat combat = simulateUpcomingCombatThisTurn(game);
int score = 0;
// TODO: more than 2 players
int myCards = 0;
@@ -32,21 +54,21 @@ public class GameStateEvaluator {
theirCards++;
}
}
GameSimulator.debugPrint("My cards in hand: " + myCards);
GameSimulator.debugPrint("Their cards in hand: " + theirCards);
debugPrint("My cards in hand: " + myCards);
debugPrint("Their cards in hand: " + theirCards);
if (myCards > aiPlayer.getMaxHandSize()) {
// Count excess cards for less.
score += myCards - aiPlayer.getMaxHandSize();
myCards = aiPlayer.getMaxHandSize();
}
score += 5 * myCards - 4 * theirCards;
GameSimulator.debugPrint(" My life: " + aiPlayer.getLife());
debugPrint(" My life: " + aiPlayer.getLife());
score += 2 * aiPlayer.getLife();
int opponentIndex = 1;
int opponentLife = 0;
for (Player opponent : game.getPlayers()) {
if (opponent != aiPlayer) {
GameSimulator.debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
opponentLife += opponent.getLife();
opponentIndex++;
}
@@ -55,7 +77,7 @@ public class GameStateEvaluator {
int summonSickScore = score;
PhaseType gamePhase = game.getPhaseHandler().getPhase();
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
int value = evalCard(game, aiPlayer, c);
int value = evalCard(game, aiPlayer, c, combat);
int summonSickValue = value;
// 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.
@@ -67,32 +89,35 @@ public class GameStateEvaluator {
str += " " + c.getNetPower() + "/" + c.getNetToughness();
}
if (c.getController() == aiPlayer) {
GameSimulator.debugPrint(" Battlefield: " + str + " = " + value);
debugPrint(" Battlefield: " + str + " = " + value);
score += value;
summonSickScore += summonSickValue;
} else {
GameSimulator.debugPrint(" Battlefield: " + str + " = -" + value);
debugPrint(" Battlefield: " + str + " = -" + value);
score -= value;
summonSickScore -= summonSickValue;
}
String nonAbilityText = c.getNonAbilityText();
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);
}
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.
if (c.isCreature()) {
// 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
// with that creature - or if you're just temporarily pumping down.
// TODO: Make this smarter. Right now, it only looks if the creature will attack - but
// 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
// 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);
return result;
} else if (c.isLand()) {

View File

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