diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 5634d0948c1..53b658a5a9b 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -33,6 +33,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import forge.ai.ability.ChangeZoneAi; import forge.ai.simulation.SpellAbilityPicker; import forge.card.CardStateName; import forge.card.MagicColor; @@ -115,6 +116,10 @@ public class AiController { this.useSimulation = value; } + public SpellAbilityPicker getSimulationPicker() { + return simPicker; + } + public Game getGame() { return game; } @@ -1556,5 +1561,12 @@ public class AiController { return MyRandom.getRandom().nextBoolean(); } + public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List origin, SpellAbility sa, + CardCollection fetchList, Player player2, Player decider) { + if (useSimulation) { + return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); + } + return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); + } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index bfcba292042..f8adfb8bb36 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -16,7 +16,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import forge.LobbyPlayer; -import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ProtectAi; import forge.card.ColorSet; import forge.card.ICardFace; @@ -864,11 +863,10 @@ public class PlayerControllerAi extends PlayerController { public Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider) { - if (delayedReveal != null) { reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } - return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider); + return brains.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider); } @Override diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index 20224bf722c..2dfc7de335f 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -10,6 +10,7 @@ import forge.ai.ComputerUtil; import forge.ai.ComputerUtilAbility; import forge.ai.PlayerControllerAi; import forge.ai.simulation.GameStateEvaluator.Score; +import forge.ai.simulation.SpellAbilityPicker.Interceptor; import forge.game.Game; import forge.game.GameObject; import forge.game.card.Card; @@ -74,6 +75,10 @@ public class GameSimulator { debugLines = null; } + public void setInterceptor(Interceptor interceptor) { + ((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor); + } + private void printDiff(List lines1, List lines2) { int i = 0; int j = 0; 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 19a5976a9b3..9931d9670a0 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java @@ -167,6 +167,7 @@ public class GameStateEvaluator { public static class Score { public final int value; public final int summonSickValue; + public String choice; public Score(int value) { this.value = value; @@ -183,5 +184,9 @@ public class GameStateEvaluator { return false; return value == other.value && summonSickValue == other.summonSickValue; } + + public String toString() { + return value + (summonSickValue != value ? " (ss " + summonSickValue + ")" :""); + } } } diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index e074e2626d8..badb9e5ad9e 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -1,29 +1,39 @@ package forge.ai.simulation; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import forge.ai.AiPlayDecision; import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilCost; +import forge.ai.ability.ChangeZoneAi; import forge.ai.simulation.GameStateEvaluator.Score; import forge.game.Game; +import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityCondition; import forge.game.spellability.TargetChoices; +import forge.game.zone.ZoneType; public class SpellAbilityPicker { private Game game; private Player player; private Score bestScore; private boolean printOutput; - + private Interceptor interceptor; + public SpellAbilityPicker(Game game, Player player) { this.game = game; this.player = player; } + + public void setInterceptor(Interceptor in) { + this.interceptor = in; + } private void print(String str) { if (printOutput) { @@ -65,11 +75,11 @@ public class SpellAbilityPicker { return null; } SpellAbility bestSa = null; - print("Evaluating..."); GameSimulator simulator = new GameSimulator(controller, game, player); // FIXME: This is wasteful, we should re-use the same simulator... Score origGameScore = simulator.getScoreForOrigGame(); Score bestSaValue = origGameScore; + print("Evaluating... (orig score = " + origGameScore + ")"); for (final SpellAbility sa : candidateSAs) { print(abilityToString(sa));; Score value = evaluateSa(controller, sa); @@ -153,13 +163,60 @@ public class SpellAbilityPicker { private Score evaluateSa(SimulationController controller, SpellAbility sa) { GameSimulator.debugPrint("Evaluate SA: " + sa); + + Score bestScore = new Score(Integer.MIN_VALUE); if (!sa.usesTargeting()) { - GameSimulator simulator = new GameSimulator(controller, game, player); - return simulator.simulateSpellAbility(sa); + // TODO: Refactor this into a general decision tree. + Interceptor interceptor = new Interceptor() { + private int numChoices = -1; + private int nextChoice = 0; + private Card choice; + + @Override + public Card chooseCard(CardCollection fetchList) { + choice = null; + // Prune duplicates. + HashSet uniqueCards = new HashSet(); + for (int i = 0; i < fetchList.size(); i++) { + Card card = fetchList.get(i); + if (uniqueCards.add(card.getName()) && uniqueCards.size() == nextChoice + 1) { + choice = card; + } + } + numChoices = uniqueCards.size(); + nextChoice++; + GameSimulator.debugPrint("Trying out choice " + choice); + return choice; + } + + @Override + public Card getLastChoice() { + return choice; + } + + @Override + public boolean hasMoreChoices() { + return nextChoice < numChoices; + } + }; + + do { + GameSimulator simulator = new GameSimulator(controller, game, player); + simulator.setInterceptor(interceptor); + Score score = simulator.simulateSpellAbility(sa); + if (score.value > bestScore.value) { + bestScore = score; + Card choice = interceptor.getLastChoice(); + if (choice != null) { + bestScore.choice = choice.getName(); + } + } + } while (interceptor.hasMoreChoices()); + return bestScore; } + GameSimulator.debugPrint("Checking out targets"); PossibleTargetSelector selector = new PossibleTargetSelector(game, player, sa); - Score bestScore = new Score(Integer.MIN_VALUE); TargetChoices tgt = null; while (selector.selectNextTargets()) { GameSimulator.debugPrint("Trying targets: " + sa.getTargets().getTargetedString()); @@ -177,4 +234,25 @@ public class SpellAbilityPicker { return bestScore; } + public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List origin, SpellAbility sa, + CardCollection fetchList, Player player2, Player decider) { + if (interceptor != null) { + return interceptor.chooseCard(fetchList); + } + // TODO: Make the below more robust? + if (bestScore != null && bestScore.choice != null) { + for (Card c : fetchList) { + if (c.getName().equals(bestScore.choice)) { + return c; + } + } + } + return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); + } + + public interface Interceptor { + public Card chooseCard(CardCollection fetchList); + public Card getLastChoice(); + public boolean hasMoreChoices(); + } }