mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
[Simulated AI] Refactor code to create a Plan object.
This allows coming up with a multi-step planning and caching it, so it doesn't need to be re-computed at subsequent steps if nothing meaningful changed.
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -156,6 +156,7 @@ forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java -text
|
|||||||
forge-ai/src/main/java/forge/ai/simulation/GameCopier.java -text
|
forge-ai/src/main/java/forge/ai/simulation/GameCopier.java -text
|
||||||
forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java -text
|
forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java -text
|
||||||
forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java -text
|
forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java -text
|
||||||
|
forge-ai/src/main/java/forge/ai/simulation/Plan.java -text
|
||||||
forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java -text
|
forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java -text
|
||||||
forge-ai/src/main/java/forge/ai/simulation/SimulationController.java -text
|
forge-ai/src/main/java/forge/ai/simulation/SimulationController.java -text
|
||||||
forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java -text
|
forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java -text
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class GameSimulator {
|
|||||||
}
|
}
|
||||||
controller.printState(score, origSa);
|
controller.printState(score, origSa);
|
||||||
if (controller.shouldRecurse() && !simGame.isGameOver()) {
|
if (controller.shouldRecurse() && !simGame.isGameOver()) {
|
||||||
controller.push(sa);
|
controller.push(sa, score);
|
||||||
SpellAbilityPicker sim = new SpellAbilityPicker(simGame, aiPlayer);
|
SpellAbilityPicker sim = new SpellAbilityPicker(simGame, aiPlayer);
|
||||||
CardCollection cards = ComputerUtilAbility.getAvailableCards(simGame, aiPlayer);
|
CardCollection cards = ComputerUtilAbility.getAvailableCards(simGame, aiPlayer);
|
||||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, aiPlayer);
|
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, aiPlayer);
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ public class GameStateEvaluator {
|
|||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
int value = evalCard(game, aiPlayer, c, combat);
|
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 before MAIN2 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.
|
||||||
if (gamePhase == PhaseType.MAIN1 && c.isSick() && c.getController() == aiPlayer) {
|
if (gamePhase.isBefore(PhaseType.MAIN2) && c.isSick() && c.getController() == aiPlayer) {
|
||||||
summonSickValue = 0;
|
summonSickValue = 0;
|
||||||
}
|
}
|
||||||
String str = c.getName();
|
String str = c.getName();
|
||||||
@@ -167,7 +167,6 @@ public class GameStateEvaluator {
|
|||||||
public static class Score {
|
public static class Score {
|
||||||
public final int value;
|
public final int value;
|
||||||
public final int summonSickValue;
|
public final int summonSickValue;
|
||||||
public String choice;
|
|
||||||
|
|
||||||
public Score(int value) {
|
public Score(int value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
|||||||
78
forge-ai/src/main/java/forge/ai/simulation/Plan.java
Normal file
78
forge-ai/src/main/java/forge/ai/simulation/Plan.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package forge.ai.simulation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class Plan {
|
||||||
|
private List<Decision> decisions;
|
||||||
|
private int nextDecisionIndex;
|
||||||
|
private Decision selectedDecision;
|
||||||
|
|
||||||
|
public Plan(ArrayList<Decision> decisions) {
|
||||||
|
this.decisions = decisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Decision> getDecisions() {
|
||||||
|
return decisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNextDecision() {
|
||||||
|
return nextDecisionIndex < decisions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Decision selectNextDecision() {
|
||||||
|
selectedDecision = decisions.get(nextDecisionIndex);
|
||||||
|
nextDecisionIndex++;
|
||||||
|
return selectedDecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Decision getSelectedDecision() {
|
||||||
|
return selectedDecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNextDecisionIndex() {
|
||||||
|
return nextDecisionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Decision {
|
||||||
|
final Decision prevDecision;
|
||||||
|
final Score initialScore;
|
||||||
|
|
||||||
|
final String sa;
|
||||||
|
PossibleTargetSelector.Targets targets;
|
||||||
|
String choice;
|
||||||
|
|
||||||
|
public Decision(Score initialScore, Decision prevDecision, SpellAbility sa) {
|
||||||
|
this.initialScore = initialScore;
|
||||||
|
this.prevDecision = prevDecision;
|
||||||
|
this.sa = sa.toString();
|
||||||
|
this.targets = null;
|
||||||
|
this.choice = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Decision(Score initialScore, Decision prevDecision, PossibleTargetSelector.Targets targets) {
|
||||||
|
this.initialScore = initialScore;
|
||||||
|
this.prevDecision = prevDecision;
|
||||||
|
this.sa = null;
|
||||||
|
this.targets = targets;
|
||||||
|
this.choice = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Decision(Score initialScore, Decision prevDecision, Card choice) {
|
||||||
|
this.initialScore = initialScore;
|
||||||
|
this.prevDecision = prevDecision;
|
||||||
|
this.sa = null;
|
||||||
|
this.targets = null;
|
||||||
|
this.choice = choice.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[initScore=" + initialScore + " " + sa + " " + targets + " " + choice + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,23 +16,43 @@ public class PossibleTargetSelector {
|
|||||||
private int targetIndex;
|
private int targetIndex;
|
||||||
private List<GameObject> validTargets;
|
private List<GameObject> validTargets;
|
||||||
|
|
||||||
|
public static class Targets {
|
||||||
|
final int originalTargetCount;
|
||||||
|
final int targetIndex;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
private Targets(int originalTargetCount, int targetIndex, String description) {
|
||||||
|
this.originalTargetCount = originalTargetCount;
|
||||||
|
this.targetIndex = targetIndex;
|
||||||
|
this.description = description;
|
||||||
|
|
||||||
|
if (targetIndex < 0 || targetIndex >= originalTargetCount) {
|
||||||
|
throw new IllegalArgumentException("Invalid targetIndex=" + targetIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PossibleTargetSelector(Game game, Player self, SpellAbility sa) {
|
public PossibleTargetSelector(Game game, Player self, SpellAbility sa) {
|
||||||
this.sa = sa;
|
this.sa = sa;
|
||||||
this.tgt = sa.getTargetRestrictions();
|
this.tgt = sa.getTargetRestrictions();
|
||||||
this.targetIndex = 0;
|
this.targetIndex = 0;
|
||||||
this.validTargets = new ArrayList<GameObject>();
|
this.validTargets = new ArrayList<GameObject>();
|
||||||
|
sa.resetTargets();
|
||||||
sa.setActivatingPlayer(self);
|
sa.setActivatingPlayer(self);
|
||||||
for (GameObject o : tgt.getAllCandidates(sa, true)) {
|
for (GameObject o : tgt.getAllCandidates(sa, true)) {
|
||||||
validTargets.add(o);
|
validTargets.add(o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean selectNextTargets() {
|
private void selectTargetsByIndex(int index) {
|
||||||
if (targetIndex >= validTargets.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
int index = targetIndex;
|
|
||||||
|
// TODO: smarter about multiple targets, identical targets, etc...
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa) && index < validTargets.size()) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa) && index < validTargets.size()) {
|
||||||
sa.getTargets().add(validTargets.get(index++));
|
sa.getTargets().add(validTargets.get(index++));
|
||||||
}
|
}
|
||||||
@@ -53,8 +73,25 @@ public class PossibleTargetSelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: smarter about multiple targets, identical targets, etc...
|
public Targets getLastSelectedTargets() {
|
||||||
|
return new Targets(validTargets.size(), targetIndex - 1, sa.getTargets().getTargetedString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean selectTargets(Targets targets) {
|
||||||
|
if (targets.originalTargetCount != validTargets.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
selectTargetsByIndex(targets.targetIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean selectNextTargets() {
|
||||||
|
if (targetIndex >= validTargets.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
selectTargetsByIndex(targetIndex);
|
||||||
targetIndex++;
|
targetIndex++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class SimulationController {
|
public class SimulationController {
|
||||||
@@ -8,21 +13,93 @@ public class SimulationController {
|
|||||||
|
|
||||||
private int recursionDepth;
|
private int recursionDepth;
|
||||||
|
|
||||||
public SimulationController() {
|
private List<Plan.Decision> currentStack;
|
||||||
|
private List<Score> scoreStack;
|
||||||
|
private Plan.Decision bestSequence; // last action of sequence
|
||||||
|
private Score bestScore;
|
||||||
|
|
||||||
|
public SimulationController(Score score) {
|
||||||
|
bestScore = score;
|
||||||
|
scoreStack = new ArrayList<Score>();
|
||||||
|
scoreStack.add(score);
|
||||||
|
currentStack = new ArrayList<Plan.Decision>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldRecurse() {
|
public boolean shouldRecurse() {
|
||||||
return recursionDepth < MAX_DEPTH;
|
return recursionDepth < MAX_DEPTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void push(SpellAbility sa) {
|
private Plan.Decision getLastDecision() {
|
||||||
|
if (currentStack.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return currentStack.get(currentStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Score getCurrentScore() {
|
||||||
|
return scoreStack.get(scoreStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evaluateSpellAbility(SpellAbility sa) {
|
||||||
|
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), sa));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evaluateCardChoice(Card choice) {
|
||||||
|
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), choice));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evaluateTargetChoices(PossibleTargetSelector.Targets targets) {
|
||||||
|
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), targets));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doneEvaluating(Score score) {
|
||||||
|
if (score.value > bestScore.value) {
|
||||||
|
bestScore = score;
|
||||||
|
bestSequence = currentStack.get(currentStack.size() - 1);
|
||||||
|
}
|
||||||
|
currentStack.remove(currentStack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Score getBestScore() {
|
||||||
|
return bestScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plan getBestPlan() {
|
||||||
|
ArrayList<Plan.Decision> sequence = new ArrayList<Plan.Decision>();
|
||||||
|
Plan.Decision current = bestSequence;
|
||||||
|
while (current != null) {
|
||||||
|
sequence.add(current);
|
||||||
|
current = current.prevDecision;
|
||||||
|
}
|
||||||
|
Collections.reverse(sequence);
|
||||||
|
// Merge targets & choices into their parents.
|
||||||
|
int writeIndex = 0;
|
||||||
|
for (int i = 0; i < sequence.size(); i++) {
|
||||||
|
Plan.Decision d = sequence.get(i);
|
||||||
|
System.out.println("SeqInput: " + d);
|
||||||
|
if (d.sa != null) {
|
||||||
|
sequence.set(writeIndex, d);
|
||||||
|
writeIndex++;
|
||||||
|
} else if (d.targets != null) {
|
||||||
|
sequence.get(writeIndex - 1).targets = d.targets;
|
||||||
|
} else if (d.choice != null) {
|
||||||
|
sequence.get(writeIndex - 1).choice = d.choice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sequence.subList(writeIndex, sequence.size()).clear();
|
||||||
|
return new Plan(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(SpellAbility sa, Score score) {
|
||||||
GameSimulator.debugPrint("Recursing DEPTH=" + recursionDepth);
|
GameSimulator.debugPrint("Recursing DEPTH=" + recursionDepth);
|
||||||
GameSimulator.debugPrint(" With: " + sa);
|
GameSimulator.debugPrint(" With: " + sa);
|
||||||
recursionDepth++;
|
recursionDepth++;
|
||||||
|
scoreStack.add(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pop(Score score, SpellAbility nextSa) {
|
public void pop(Score score, SpellAbility nextSa) {
|
||||||
recursionDepth--;
|
recursionDepth--;
|
||||||
|
scoreStack.remove(scoreStack.size() - 1);
|
||||||
GameSimulator.debugPrint("DEPTH"+recursionDepth+" best score " + score + " " + nextSa);
|
GameSimulator.debugPrint("DEPTH"+recursionDepth+" best score " + score + " " + nextSa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -26,6 +25,8 @@ public class SpellAbilityPicker {
|
|||||||
private boolean printOutput;
|
private boolean printOutput;
|
||||||
private Interceptor interceptor;
|
private Interceptor interceptor;
|
||||||
|
|
||||||
|
private Plan plan;
|
||||||
|
|
||||||
public SpellAbilityPicker(Game game, Player player) {
|
public SpellAbilityPicker(Game game, Player player) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
@@ -41,21 +42,19 @@ public class SpellAbilityPicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpellAbility chooseSpellAbilityToPlay(SimulationController controller, final List<SpellAbility> all, boolean skipCounter) {
|
private void printPhaseInfo() {
|
||||||
printOutput = false;
|
|
||||||
if (controller == null) {
|
|
||||||
controller = new SimulationController();
|
|
||||||
printOutput = true;
|
|
||||||
}
|
|
||||||
String phaseStr = game.getPhaseHandler().getPhase().toString();
|
String phaseStr = game.getPhaseHandler().getPhase().toString();
|
||||||
if (game.getPhaseHandler().getPlayerTurn() != player) {
|
if (game.getPhaseHandler().getPlayerTurn() != player) {
|
||||||
phaseStr = "opponent " + phaseStr;
|
phaseStr = "opponent " + phaseStr;
|
||||||
}
|
}
|
||||||
print("---- choose ability (phase = " + phaseStr + ")");
|
print("---- choose ability (phase = " + phaseStr + ")");
|
||||||
|
}
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
private List<SpellAbility> getCandidateSpellsAndAbilities(List<SpellAbility> all) {
|
||||||
List<SpellAbility> candidateSAs = new ArrayList<>();
|
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
|
||||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
int writeIndex = 0;
|
||||||
|
for (int i = 0; i < candidateSAs.size(); i++) {
|
||||||
|
SpellAbility sa = candidateSAs.get(i);
|
||||||
if (sa.isManaAbility()) {
|
if (sa.isManaAbility()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -68,20 +67,59 @@ public class SpellAbilityPicker {
|
|||||||
|
|
||||||
if (opinion != AiPlayDecision.WillPlay)
|
if (opinion != AiPlayDecision.WillPlay)
|
||||||
continue;
|
continue;
|
||||||
candidateSAs.add(sa);
|
candidateSAs.set(writeIndex, sa);
|
||||||
|
writeIndex++;
|
||||||
|
}
|
||||||
|
candidateSAs.subList(writeIndex, candidateSAs.size()).clear();
|
||||||
|
return candidateSAs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidateSAs.isEmpty()) {
|
public SpellAbility chooseSpellAbilityToPlay(SimulationController controller, List<SpellAbility> all, boolean skipCounter) {
|
||||||
return null;
|
printOutput = (controller == null);
|
||||||
}
|
|
||||||
SpellAbility bestSa = null;
|
|
||||||
GameSimulator simulator = new GameSimulator(controller, game, player);
|
|
||||||
// FIXME: This is wasteful, we should re-use the same simulator...
|
// FIXME: This is wasteful, we should re-use the same simulator...
|
||||||
|
GameSimulator simulator = new GameSimulator(controller, game, player);
|
||||||
Score origGameScore = simulator.getScoreForOrigGame();
|
Score origGameScore = simulator.getScoreForOrigGame();
|
||||||
|
List<SpellAbility> candidateSAs = getCandidateSpellsAndAbilities(all);
|
||||||
|
if (controller != null) {
|
||||||
|
// This is a recursion during a higher-level simulation. Just return the head of the best
|
||||||
|
// sequence directly, no need to create a Plan object.
|
||||||
|
return chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
printPhaseInfo();
|
||||||
|
SpellAbility sa = getPlannedSpellAbility(origGameScore, candidateSAs);
|
||||||
|
if (sa != null) {
|
||||||
|
return sa;
|
||||||
|
}
|
||||||
|
createNewPlan(origGameScore, candidateSAs);
|
||||||
|
return getPlannedSpellAbility(origGameScore, candidateSAs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewPlan(Score origGameScore, List<SpellAbility> candidateSAs) {
|
||||||
|
plan = null;
|
||||||
|
SimulationController controller = new SimulationController(origGameScore);
|
||||||
|
SpellAbility sa = chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore);
|
||||||
|
if (sa == null) {
|
||||||
|
print("No good plan at this time");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plan = controller.getBestPlan();
|
||||||
|
print("New plan with score " + controller.getBestScore() + ":");
|
||||||
|
int i = 0;
|
||||||
|
for (Plan.Decision d : plan.getDecisions()) {
|
||||||
|
print(++i + ". " + d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpellAbility chooseSpellAbilityToPlayImpl(SimulationController controller, List<SpellAbility> candidateSAs, Score origGameScore) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
SpellAbility bestSa = null;
|
||||||
Score bestSaValue = origGameScore;
|
Score bestSaValue = origGameScore;
|
||||||
print("Evaluating... (orig score = " + origGameScore + ")");
|
print("Evaluating... (orig score = " + origGameScore + ")");
|
||||||
for (final SpellAbility sa : candidateSAs) {
|
for (final SpellAbility sa : candidateSAs) {
|
||||||
print(abilityToString(sa));;
|
|
||||||
Score value = evaluateSa(controller, sa);
|
Score value = evaluateSa(controller, sa);
|
||||||
if (value.value > bestSaValue.value) {
|
if (value.value > bestSaValue.value) {
|
||||||
bestSaValue = value;
|
bestSaValue = value;
|
||||||
@@ -104,6 +142,41 @@ public class SpellAbilityPicker {
|
|||||||
return bestSa;
|
return bestSa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SpellAbility getPlannedSpellAbility(Score origGameScore, List<SpellAbility> availableSAs) {
|
||||||
|
if (plan != null && plan.hasNextDecision()) {
|
||||||
|
boolean badTargets = false;
|
||||||
|
boolean saNotFound = false;
|
||||||
|
Plan.Decision decision = plan.selectNextDecision();
|
||||||
|
if (decision.initialScore.equals(origGameScore)) {
|
||||||
|
// TODO: Other safeguards like list of SAs and maybe the index and such?
|
||||||
|
for (final SpellAbility sa : availableSAs) {
|
||||||
|
if (sa.toString().equals(decision.sa)) {
|
||||||
|
if (decision.targets != null) {
|
||||||
|
PossibleTargetSelector selector = new PossibleTargetSelector(game, player, sa);
|
||||||
|
if (!selector.selectTargets(decision.targets)) {
|
||||||
|
badTargets = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("Planned decision " + plan.getNextDecisionIndex() + ": " + abilityToString(sa) + " " + decision.choice);
|
||||||
|
return sa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saNotFound = true;
|
||||||
|
}
|
||||||
|
print("Failed to continue planned action (" + decision.sa + "). Cause:");
|
||||||
|
if (badTargets) {
|
||||||
|
print(" Bad targets!");
|
||||||
|
} else if (saNotFound) {
|
||||||
|
print(" Couldn't find spell/ability!");
|
||||||
|
} else {
|
||||||
|
print(" Unexpected game score (" + decision.initialScore + " vs. expected " + origGameScore + ")!");
|
||||||
|
}
|
||||||
|
plan = null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Score getScoreForChosenAbility() {
|
public Score getScoreForChosenAbility() {
|
||||||
return bestScore;
|
return bestScore;
|
||||||
}
|
}
|
||||||
@@ -161,12 +234,11 @@ public class SpellAbilityPicker {
|
|||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Score evaluateSa(SimulationController controller, SpellAbility sa) {
|
private Score evaluateSa(final SimulationController controller, SpellAbility sa) {
|
||||||
GameSimulator.debugPrint("Evaluate SA: " + sa);
|
controller.evaluateSpellAbility(sa);
|
||||||
|
|
||||||
Score bestScore = new Score(Integer.MIN_VALUE);
|
Score bestScore = new Score(Integer.MIN_VALUE);
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
// TODO: Refactor this into a general decision tree.
|
|
||||||
Interceptor interceptor = new Interceptor() {
|
Interceptor interceptor = new Interceptor() {
|
||||||
private int numChoices = -1;
|
private int numChoices = -1;
|
||||||
private int nextChoice = 0;
|
private int nextChoice = 0;
|
||||||
@@ -185,7 +257,9 @@ public class SpellAbilityPicker {
|
|||||||
}
|
}
|
||||||
numChoices = uniqueCards.size();
|
numChoices = uniqueCards.size();
|
||||||
nextChoice++;
|
nextChoice++;
|
||||||
GameSimulator.debugPrint("Trying out choice " + choice);
|
if (choice != null) {
|
||||||
|
controller.evaluateCardChoice(choice);
|
||||||
|
}
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,30 +278,33 @@ public class SpellAbilityPicker {
|
|||||||
GameSimulator simulator = new GameSimulator(controller, game, player);
|
GameSimulator simulator = new GameSimulator(controller, game, player);
|
||||||
simulator.setInterceptor(interceptor);
|
simulator.setInterceptor(interceptor);
|
||||||
Score score = simulator.simulateSpellAbility(sa);
|
Score score = simulator.simulateSpellAbility(sa);
|
||||||
|
if (interceptor.getLastChoice() != null) {
|
||||||
|
controller.doneEvaluating(score);
|
||||||
|
}
|
||||||
if (score.value > bestScore.value) {
|
if (score.value > bestScore.value) {
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
Card choice = interceptor.getLastChoice();
|
|
||||||
if (choice != null) {
|
|
||||||
bestScore.choice = choice.getName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while (interceptor.hasMoreChoices());
|
} while (interceptor.hasMoreChoices());
|
||||||
|
controller.doneEvaluating(bestScore);
|
||||||
return bestScore;
|
return bestScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameSimulator.debugPrint("Checking out targets");
|
|
||||||
PossibleTargetSelector selector = new PossibleTargetSelector(game, player, sa);
|
PossibleTargetSelector selector = new PossibleTargetSelector(game, player, sa);
|
||||||
TargetChoices tgt = null;
|
TargetChoices tgt = null;
|
||||||
while (selector.selectNextTargets()) {
|
while (selector.selectNextTargets()) {
|
||||||
GameSimulator.debugPrint("Trying targets: " + sa.getTargets().getTargetedString());
|
controller.evaluateTargetChoices(selector.getLastSelectedTargets());
|
||||||
GameSimulator simulator = new GameSimulator(controller, game, player);
|
GameSimulator simulator = new GameSimulator(controller, game, player);
|
||||||
Score score = simulator.simulateSpellAbility(sa);
|
Score score = simulator.simulateSpellAbility(sa);
|
||||||
|
controller.doneEvaluating(score);
|
||||||
|
// TODO: Get rid of the below when no longer needed.
|
||||||
if (score.value > bestScore.value) {
|
if (score.value > bestScore.value) {
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
tgt = sa.getTargets();
|
tgt = sa.getTargets();
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
controller.doneEvaluating(bestScore);
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.setTargets(tgt);
|
sa.setTargets(tgt);
|
||||||
}
|
}
|
||||||
@@ -240,12 +317,15 @@ public class SpellAbilityPicker {
|
|||||||
return interceptor.chooseCard(fetchList);
|
return interceptor.chooseCard(fetchList);
|
||||||
}
|
}
|
||||||
// TODO: Make the below more robust?
|
// TODO: Make the below more robust?
|
||||||
if (bestScore != null && bestScore.choice != null) {
|
if (plan != null && plan.getSelectedDecision() != null) {
|
||||||
|
String choice = plan.getSelectedDecision().choice;
|
||||||
for (Card c : fetchList) {
|
for (Card c : fetchList) {
|
||||||
if (c.getName().equals(bestScore.choice)) {
|
if (c.getName().equals(choice)) {
|
||||||
|
print(" Planned choice: " + c);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
print("Failed to use planned choice (" + choice + "). Not found!");
|
||||||
}
|
}
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.GuiBase;
|
|||||||
import forge.GuiDesktop;
|
import forge.GuiDesktop;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.LobbyPlayerAi;
|
import forge.ai.LobbyPlayerAi;
|
||||||
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -51,7 +52,7 @@ public class GameSimulatorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GameSimulator createSimulator(Game game, Player p) {
|
private GameSimulator createSimulator(Game game, Player p) {
|
||||||
return new GameSimulator(new SimulationController(), game, p);
|
return new GameSimulator(new SimulationController(new Score(0)), game, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Card findCardWithName(Game game, String name) {
|
private Card findCardWithName(Game game, String name) {
|
||||||
|
|||||||
Reference in New Issue
Block a user