mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Simulated AI: Evaluate different fetch targets.
This lets the AI figure out such sequences as: Game state: In play: Urborg, Thespian's stage & fetch land In hand: Crop rotation Sequence: 1. Fetch for a forest. 2. Crop rotate forest into Dark Depths. 3. Activate Thespian's stage targeting Dark Depths. 4. Profit!
This commit is contained in:
@@ -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<ZoneType> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ZoneType> 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
|
||||
|
||||
@@ -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<String> lines1, List<String> lines2) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
@@ -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 + ")" :"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
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) {
|
||||
System.out.println(str);
|
||||
@@ -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<String> uniqueCards = new HashSet<String>();
|
||||
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<ZoneType> 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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user