From 1a3025d3be7c20875159a02ce868aecae92f795b Mon Sep 17 00:00:00 2001 From: Myrd Date: Mon, 9 Mar 2015 02:45:10 +0000 Subject: [PATCH] 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. --- .../java/forge/ai/AiAttackController.java | 13 +++-- .../ai/simulation/GameStateEvaluator.java | 51 ++++++++++++++----- .../ai/simulation/SimulationController.java | 2 +- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 3bcc46a222d..ff893deee31 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -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); + } } } diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java index 967554a8553..19a5976a9b3 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java @@ -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; @@ -16,11 +19,30 @@ public class GameStateEvaluator { public void setDebugging(boolean 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) { 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()) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java index 6c785107636..320cb279fb0 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java @@ -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;