mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
[Simulated AI] Smart decisions about whether to play spells before or after blocks.
For example, using a doom blade before blocks to attack for lethal is preferred vs using a pump spell after blocks to pump an unblocked creature for lethal damage. Adds relevant tests.
This commit is contained in:
@@ -24,6 +24,7 @@ import forge.game.card.CardFactoryUtil;
|
|||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.RegisteredPlayer;
|
import forge.game.player.RegisteredPlayer;
|
||||||
import forge.game.spellability.AbilityActivated;
|
import forge.game.spellability.AbilityActivated;
|
||||||
@@ -64,6 +65,9 @@ public class GameCopier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Game makeCopy() {
|
public Game makeCopy() {
|
||||||
|
return makeCopy(null);
|
||||||
|
}
|
||||||
|
public Game makeCopy(PhaseType advanceToPhase) {
|
||||||
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
|
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
|
||||||
List<RegisteredPlayer> newPlayers = new ArrayList<>();
|
List<RegisteredPlayer> newPlayers = new ArrayList<>();
|
||||||
for (RegisteredPlayer p : origPlayers) {
|
for (RegisteredPlayer p : origPlayers) {
|
||||||
@@ -148,6 +152,10 @@ public class GameCopier {
|
|||||||
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
|
newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (advanceToPhase != null) {
|
||||||
|
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase);
|
||||||
|
}
|
||||||
|
|
||||||
return newGame;
|
return newGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import forge.ai.simulation.GameStateEvaluator.Score;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
@@ -28,10 +29,10 @@ public class GameSimulator {
|
|||||||
private Score origScore;
|
private Score origScore;
|
||||||
private SpellAbilityChoicesIterator interceptor;
|
private SpellAbilityChoicesIterator interceptor;
|
||||||
|
|
||||||
public GameSimulator(final SimulationController controller, final Game origGame, final Player origAiPlayer) {
|
public GameSimulator(SimulationController controller, Game origGame, Player origAiPlayer, PhaseType advanceToPhase) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
copier = new GameCopier(origGame);
|
copier = new GameCopier(origGame);
|
||||||
simGame = copier.makeCopy();
|
simGame = copier.makeCopy(advanceToPhase);
|
||||||
|
|
||||||
aiPlayer = (Player) copier.find(origAiPlayer);
|
aiPlayer = (Player) copier.find(origAiPlayer);
|
||||||
eval = new GameStateEvaluator();
|
eval = new GameStateEvaluator();
|
||||||
@@ -42,20 +43,9 @@ public class GameSimulator {
|
|||||||
debugPrint = false;
|
debugPrint = false;
|
||||||
origScore = eval.getScoreForGameState(origGame, origAiPlayer);
|
origScore = eval.getScoreForGameState(origGame, origAiPlayer);
|
||||||
|
|
||||||
eval.setDebugging(true);
|
if (advanceToPhase == null) {
|
||||||
List<String> simLines = new ArrayList<String>();
|
ensureGameCopyScoreMatches(origGame, origAiPlayer);
|
||||||
debugLines = simLines;
|
|
||||||
Score simScore = eval.getScoreForGameState(simGame, aiPlayer);
|
|
||||||
if (!simScore.equals(origScore)) {
|
|
||||||
// Re-eval orig with debug printing.
|
|
||||||
origLines = new ArrayList<String>();
|
|
||||||
debugLines = origLines;
|
|
||||||
eval.getScoreForGameState(origGame, origAiPlayer);
|
|
||||||
// Print debug info.
|
|
||||||
printDiff(origLines, simLines);
|
|
||||||
throw new RuntimeException("Game copy error. See diff output above for details.");
|
|
||||||
}
|
}
|
||||||
eval.setDebugging(false);
|
|
||||||
|
|
||||||
// If the stack on the original game is not empty, resolve it
|
// If the stack on the original game is not empty, resolve it
|
||||||
// first and get the updated eval score, since this is what we'll
|
// first and get the updated eval score, since this is what we'll
|
||||||
@@ -73,6 +63,23 @@ public class GameSimulator {
|
|||||||
debugLines = null;
|
debugLines = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureGameCopyScoreMatches(Game origGame, Player origAiPlayer) {
|
||||||
|
eval.setDebugging(true);
|
||||||
|
List<String> simLines = new ArrayList<String>();
|
||||||
|
debugLines = simLines;
|
||||||
|
Score simScore = eval.getScoreForGameState(simGame, aiPlayer);
|
||||||
|
if (!simScore.equals(origScore)) {
|
||||||
|
// Re-eval orig with debug printing.
|
||||||
|
origLines = new ArrayList<String>();
|
||||||
|
debugLines = origLines;
|
||||||
|
eval.getScoreForGameState(origGame, origAiPlayer);
|
||||||
|
// Print debug info.
|
||||||
|
printDiff(origLines, simLines);
|
||||||
|
throw new RuntimeException("Game copy error. See diff output above for details.");
|
||||||
|
}
|
||||||
|
eval.setDebugging(false);
|
||||||
|
}
|
||||||
|
|
||||||
public void setInterceptor(SpellAbilityChoicesIterator interceptor) {
|
public void setInterceptor(SpellAbilityChoicesIterator interceptor) {
|
||||||
this.interceptor = interceptor;
|
this.interceptor = interceptor;
|
||||||
((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor);
|
((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor);
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
import forge.ai.AiAttackController;
|
|
||||||
import forge.ai.CreatureEvaluator;
|
import forge.ai.CreatureEvaluator;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class GameStateEvaluator {
|
public class GameStateEvaluator {
|
||||||
private boolean debugging = false;
|
private boolean debugging = false;
|
||||||
private boolean ignoreTempBoosts = false;
|
|
||||||
private SimulationCreatureEvaluator eval = new SimulationCreatureEvaluator();
|
private SimulationCreatureEvaluator eval = new SimulationCreatureEvaluator();
|
||||||
|
|
||||||
public void setDebugging(boolean debugging) {
|
public void setDebugging(boolean debugging) {
|
||||||
@@ -21,19 +18,25 @@ public class GameStateEvaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void debugPrint(String s) {
|
private static void debugPrint(String s) {
|
||||||
//System.err.println(s);
|
|
||||||
GameSimulator.debugPrint(s);
|
GameSimulator.debugPrint(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Combat simulateUpcomingCombatThisTurn(Game game) {
|
private static class CombatSimResult {
|
||||||
PhaseHandler handler = game.getPhaseHandler();
|
public GameCopier copier;
|
||||||
if (handler.getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
public Game gameCopy;
|
||||||
|
}
|
||||||
|
private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame) {
|
||||||
|
PhaseType phase = evalGame.getPhaseHandler().getPhase();
|
||||||
|
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
AiAttackController aiAtk = new AiAttackController(handler.getPlayerTurn());
|
GameCopier copier = new GameCopier(evalGame);
|
||||||
Combat combat = new Combat(handler.getPlayerTurn());
|
Game gameCopy = copier.makeCopy();
|
||||||
aiAtk.declareAttackers(combat);
|
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE);
|
||||||
return combat;
|
CombatSimResult result = new CombatSimResult();
|
||||||
|
result.copier = copier;
|
||||||
|
result.gameCopy = gameCopy;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String cardToString(Card c) {
|
private static String cardToString(Card c) {
|
||||||
@@ -44,13 +47,27 @@ public class GameStateEvaluator {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Score getScoreForGameState(Game game, Player aiPlayer) {
|
private Score getScoreForGameOver(Game game, Player aiPlayer) {
|
||||||
if (game.isGameOver()) {
|
|
||||||
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
|
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Combat combat = simulateUpcomingCombatThisTurn(game);
|
public Score getScoreForGameState(Game game, Player aiPlayer) {
|
||||||
|
if (game.isGameOver()) {
|
||||||
|
return getScoreForGameOver(game, aiPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
CombatSimResult result = simulateUpcomingCombatThisTurn(game);
|
||||||
|
if (result != null) {
|
||||||
|
Player aiPlayerCopy = (Player) result.copier.find(aiPlayer);
|
||||||
|
if (result.gameCopy.isGameOver()) {
|
||||||
|
return getScoreForGameOver(result.gameCopy, aiPlayerCopy);
|
||||||
|
}
|
||||||
|
return getScoreForGameStateImpl(result.gameCopy, aiPlayerCopy);
|
||||||
|
}
|
||||||
|
return getScoreForGameStateImpl(game, aiPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Score getScoreForGameStateImpl(Game game, Player aiPlayer) {
|
||||||
int score = 0;
|
int score = 0;
|
||||||
// TODO: more than 2 players
|
// TODO: more than 2 players
|
||||||
int myCards = 0;
|
int myCards = 0;
|
||||||
@@ -85,7 +102,7 @@ public class GameStateEvaluator {
|
|||||||
int summonSickScore = score;
|
int summonSickScore = score;
|
||||||
PhaseType gamePhase = game.getPhaseHandler().getPhase();
|
PhaseType gamePhase = game.getPhaseHandler().getPhase();
|
||||||
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);
|
||||||
int summonSickValue = value;
|
int summonSickValue = value;
|
||||||
// To make the AI hold-off on playing creatures before MAIN2 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.
|
||||||
@@ -111,20 +128,10 @@ public class GameStateEvaluator {
|
|||||||
return new Score(score, summonSickScore);
|
return new Score(score, summonSickScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int evalCard(Game game, Player aiPlayer, Card c, Combat combat) {
|
public int evalCard(Game game, Player aiPlayer, Card c) {
|
||||||
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
|
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
// Ignore temp boosts post combat, since it's a waste.
|
return eval.evaluateCreature(c);
|
||||||
// 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 = true;
|
|
||||||
if (combat != null && combat.isAttacking(c)) {
|
|
||||||
ignoreTempBoosts = false;
|
|
||||||
}
|
|
||||||
int result = eval.evaluateCreature(c);
|
|
||||||
return result;
|
|
||||||
} else if (c.isLand()) {
|
} else if (c.isLand()) {
|
||||||
return 100;
|
return 100;
|
||||||
} else if (c.isEnchantingCard()) {
|
} else if (c.isEnchantingCard()) {
|
||||||
@@ -153,20 +160,14 @@ public class GameStateEvaluator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getEffectivePower(final Card c) {
|
protected int getEffectivePower(final Card c) {
|
||||||
if (ignoreTempBoosts) {
|
|
||||||
Card.StatBreakdown breakdown = c.getNetCombatDamageBreakdown();
|
Card.StatBreakdown breakdown = c.getNetCombatDamageBreakdown();
|
||||||
return breakdown.getTotal() - breakdown.tempBoost;
|
return breakdown.getTotal() - breakdown.tempBoost;
|
||||||
}
|
}
|
||||||
return c.getNetCombatDamage();
|
|
||||||
}
|
|
||||||
@Override
|
@Override
|
||||||
protected int getEffectiveToughness(final Card c) {
|
protected int getEffectiveToughness(final Card c) {
|
||||||
if (ignoreTempBoosts) {
|
|
||||||
Card.StatBreakdown breakdown = c.getNetToughnessBreakdown();
|
Card.StatBreakdown breakdown = c.getNetToughnessBreakdown();
|
||||||
return breakdown.getTotal() - breakdown.tempBoost;
|
return breakdown.getTotal() - breakdown.tempBoost;
|
||||||
}
|
}
|
||||||
return c.getNetToughness();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Score {
|
public static class Score {
|
||||||
|
|||||||
@@ -7,16 +7,28 @@ import com.google.common.base.Joiner;
|
|||||||
|
|
||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class Plan {
|
public class Plan {
|
||||||
private List<Decision> decisions;
|
private final List<Decision> decisions;
|
||||||
|
private final Score finalScore;
|
||||||
private int nextDecisionIndex;
|
private int nextDecisionIndex;
|
||||||
private int nextChoice;
|
private int nextChoice;
|
||||||
private Decision selectedDecision;
|
private Decision selectedDecision;
|
||||||
|
private PhaseType startPhase;
|
||||||
|
|
||||||
public Plan(ArrayList<Decision> decisions) {
|
public Plan(ArrayList<Decision> decisions, Score finalScore) {
|
||||||
this.decisions = decisions;
|
this.decisions = decisions;
|
||||||
|
this.finalScore = finalScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Score getFinalScore() {
|
||||||
|
return finalScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhaseType getStartPhase() {
|
||||||
|
return startPhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Decision> getDecisions() {
|
public List<Decision> getDecisions() {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class SimulationController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sequence.subList(writeIndex, sequence.size()).clear();
|
sequence.subList(writeIndex, sequence.size()).clear();
|
||||||
return new Plan(sequence);
|
return new Plan(sequence, getBestScore());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Plan.Decision getLastMergedDecision() {
|
private Plan.Decision getLastMergedDecision() {
|
||||||
@@ -221,7 +221,7 @@ public class SimulationController {
|
|||||||
if (effect.hostCard == hostAndTarget[0] && effect.target == hostAndTarget[1] && effect.sa.equals(saString)) {
|
if (effect.hostCard == hostAndTarget[0] && effect.target == hostAndTarget[1] && effect.sa.equals(saString)) {
|
||||||
GameStateEvaluator evaluator = new GameStateEvaluator();
|
GameStateEvaluator evaluator = new GameStateEvaluator();
|
||||||
Player player = sa.getActivatingPlayer();
|
Player player = sa.getActivatingPlayer();
|
||||||
int cardScore = evaluator.evalCard(player.getGame(), player, (Card) hostAndTarget[2], null);
|
int cardScore = evaluator.evalCard(player.getGame(), player, (Card) hostAndTarget[2]);
|
||||||
if (cardScore == effect.targetScore) {
|
if (cardScore == effect.targetScore) {
|
||||||
Score currentScore = getCurrentScore();
|
Score currentScore = getCurrentScore();
|
||||||
// TODO: summonSick score?
|
// TODO: summonSick score?
|
||||||
@@ -249,7 +249,7 @@ public class SimulationController {
|
|||||||
if (currentHostAndTarget != null) {
|
if (currentHostAndTarget != null) {
|
||||||
GameStateEvaluator evaluator = new GameStateEvaluator();
|
GameStateEvaluator evaluator = new GameStateEvaluator();
|
||||||
Player player = sa.getActivatingPlayer();
|
Player player = sa.getActivatingPlayer();
|
||||||
int cardScore = evaluator.evalCard(player.getGame(), player, (Card) hostAndTarget[2], null);
|
int cardScore = evaluator.evalCard(player.getGame(), player, (Card) hostAndTarget[2]);
|
||||||
effectCache.add(new CachedEffect(hostAndTarget[0], sa, hostAndTarget[1], cardScore, scoreDelta));
|
effectCache.add(new CachedEffect(hostAndTarget[0], sa, hostAndTarget[1], cardScore, scoreDelta));
|
||||||
cached = " (added to cache)";
|
cached = " (added to cache)";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -57,7 +58,22 @@ public class SpellAbilityPicker {
|
|||||||
print("---- choose ability (phase = " + phaseStr + ")");
|
print("---- choose ability (phase = " + phaseStr + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SpellAbility> getCandidateSpellsAndAbilities(List<SpellAbility> all) {
|
private List<SpellAbility> getCandidateSpellsAndAbilities() {
|
||||||
|
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
|
||||||
|
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
|
||||||
|
CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
||||||
|
if (landsToPlay != null) {
|
||||||
|
HashMap<String, Card> landsDeDupe = new HashMap<String, Card>();
|
||||||
|
for (Card land : landsToPlay) {
|
||||||
|
Card previousLand = landsDeDupe.get(land.getName());
|
||||||
|
// Skip identical lands.
|
||||||
|
if (previousLand != null && previousLand.getZone() == land.getZone() && previousLand.getOwner() == land.getOwner()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
landsDeDupe.put(land.getName(), land);
|
||||||
|
all.add(new PlayLandAbility(land));
|
||||||
|
}
|
||||||
|
}
|
||||||
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
|
List<SpellAbility> candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player);
|
||||||
int writeIndex = 0;
|
int writeIndex = 0;
|
||||||
for (int i = 0; i < candidateSAs.size(); i++) {
|
for (int i = 0; i < candidateSAs.size(); i++) {
|
||||||
@@ -89,28 +105,12 @@ public class SpellAbilityPicker {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
|
|
||||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
|
|
||||||
CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
|
||||||
if (landsToPlay != null) {
|
|
||||||
HashMap<String, Card> landsDeDupe = new HashMap<String, Card>();
|
|
||||||
for (Card land : landsToPlay) {
|
|
||||||
Card previousLand = landsDeDupe.get(land.getName());
|
|
||||||
// Skip identical lands.
|
|
||||||
if (previousLand != null && previousLand.getZone() == land.getZone() && previousLand.getOwner() == land.getOwner()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
landsDeDupe.put(land.getName(), land);
|
|
||||||
all.add(new PlayLandAbility(land));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Score origGameScore = new GameStateEvaluator().getScoreForGameState(game, player);
|
Score origGameScore = new GameStateEvaluator().getScoreForGameState(game, player);
|
||||||
List<SpellAbility> candidateSAs = getCandidateSpellsAndAbilities(all);
|
List<SpellAbility> candidateSAs = getCandidateSpellsAndAbilities();
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
// This is a recursion during a higher-level simulation. Just return the head of the best
|
// 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.
|
// sequence directly, no need to create a Plan object.
|
||||||
return chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore);
|
return chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
printPhaseInfo();
|
printPhaseInfo();
|
||||||
@@ -130,31 +130,81 @@ public class SpellAbilityPicker {
|
|||||||
return sa;
|
return sa;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewPlan(Score origGameScore, List<SpellAbility> candidateSAs) {
|
private Plan formulatePlanWithPhase(Score origGameScore, List<SpellAbility> candidateSAs, PhaseType phase) {
|
||||||
plan = null;
|
|
||||||
SimulationController controller = new SimulationController(origGameScore);
|
SimulationController controller = new SimulationController(origGameScore);
|
||||||
SpellAbility sa = chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore);
|
SpellAbility sa = chooseSpellAbilityToPlayImpl(controller, candidateSAs, origGameScore, phase);
|
||||||
if (sa == null) {
|
if (sa != null) {
|
||||||
print("No good plan at this time");
|
return controller.getBestPlan();
|
||||||
return;
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
plan = controller.getBestPlan();
|
private void printPlan(Plan plan, String intro) {
|
||||||
print("New plan with score " + controller.getBestScore() + ":");
|
if (plan == null) {
|
||||||
|
print(intro + ": no plan!");
|
||||||
|
}
|
||||||
|
print(intro +" plan with score " + plan.getFinalScore() + ":");
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Plan.Decision d : plan.getDecisions()) {
|
for (Plan.Decision d : plan.getDecisions()) {
|
||||||
print(++i + ". " + d);
|
print(++i + ". " + d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpellAbility chooseSpellAbilityToPlayImpl(SimulationController controller, List<SpellAbility> candidateSAs, Score origGameScore) {
|
private static boolean isSorcerySpeed(SpellAbility sa) {
|
||||||
|
// TODO: Can we use the actual rules engine for this instead of trying to do the logic ourselves?
|
||||||
|
if (sa instanceof PlayLandAbility) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sa.isSpell()) {
|
||||||
|
return !sa.getHostCard().isInstant() && !sa.getHostCard().hasKeyword("Flash");
|
||||||
|
}
|
||||||
|
if (sa.getRestrictions().isPwAbility()) {
|
||||||
|
return !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.");
|
||||||
|
}
|
||||||
|
return sa.isAbility() && sa.getRestrictions().isSorcerySpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewPlan(Score origGameScore, List<SpellAbility> candidateSAs) {
|
||||||
|
plan = null;
|
||||||
|
|
||||||
|
Plan bestPlan = formulatePlanWithPhase(origGameScore, candidateSAs, null);
|
||||||
|
if (bestPlan == null) {
|
||||||
|
print("No good plan at this time");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhaseType currentPhase = game.getPhaseHandler().getPhase();
|
||||||
|
if (currentPhase.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
List<SpellAbility> candidateSAs2 = new ArrayList<SpellAbility>();
|
||||||
|
for (SpellAbility sa : candidateSAs) {
|
||||||
|
if (!isSorcerySpeed(sa)) {
|
||||||
|
System.err.println("Not sorcery: " + sa);
|
||||||
|
candidateSAs2.add(sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!candidateSAs2.isEmpty()) {
|
||||||
|
System.err.println("Formula plan with phase bloom");
|
||||||
|
Plan afterBlockersPlan = formulatePlanWithPhase(origGameScore, candidateSAs2, PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||||
|
if (afterBlockersPlan != null && afterBlockersPlan.getFinalScore().value >= bestPlan.getFinalScore().value) {
|
||||||
|
printPlan(afterBlockersPlan, "After blockers");
|
||||||
|
print("Deciding to wait until after declare blockers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printPlan(bestPlan, "Current phase (" + currentPhase + ")");
|
||||||
|
plan = bestPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpellAbility chooseSpellAbilityToPlayImpl(SimulationController controller, List<SpellAbility> candidateSAs, Score origGameScore, PhaseType phase) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
SpellAbility bestSa = null;
|
SpellAbility bestSa = null;
|
||||||
Score bestSaValue = origGameScore;
|
Score bestSaValue = origGameScore;
|
||||||
print("Evaluating... (orig score = " + origGameScore + ")");
|
print("Evaluating... (orig score = " + origGameScore + ")");
|
||||||
for (int i = 0; i < candidateSAs.size(); i++) {
|
for (int i = 0; i < candidateSAs.size(); i++) {
|
||||||
Score value = evaluateSa(controller, candidateSAs, i);
|
Score value = evaluateSa(controller, phase, candidateSAs, i);
|
||||||
if (value.value > bestSaValue.value) {
|
if (value.value > bestSaValue.value) {
|
||||||
bestSaValue = value;
|
bestSaValue = value;
|
||||||
bestSa = candidateSAs.get(i);
|
bestSa = candidateSAs.get(i);
|
||||||
@@ -195,6 +245,11 @@ public class SpellAbilityPicker {
|
|||||||
plan = null;
|
plan = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
PhaseType startPhase = plan.getStartPhase();
|
||||||
|
if (startPhase != null && game.getPhaseHandler().getPhase().isBefore(startPhase)) {
|
||||||
|
print("Waiting until phase " + startPhase + " to proceed with the plan.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Plan.Decision decision = plan.selectNextDecision();
|
Plan.Decision decision = plan.selectNextDecision();
|
||||||
if (!decision.initialScore.equals(origGameScore)) {
|
if (!decision.initialScore.equals(origGameScore)) {
|
||||||
printPlannedActionFailure(decision, "Unexpected game score (" + decision.initialScore + " vs. expected " + origGameScore + ")");
|
printPlannedActionFailure(decision, "Unexpected game score (" + decision.initialScore + " vs. expected " + origGameScore + ")");
|
||||||
@@ -308,7 +363,7 @@ public class SpellAbilityPicker {
|
|||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Score evaluateSa(final SimulationController controller, List<SpellAbility> saList, int saIndex) {
|
private Score evaluateSa(final SimulationController controller, PhaseType phase, List<SpellAbility> saList, int saIndex) {
|
||||||
controller.evaluateSpellAbility(saList, saIndex);
|
controller.evaluateSpellAbility(saList, saIndex);
|
||||||
SpellAbility sa = saList.get(saIndex);
|
SpellAbility sa = saList.get(saIndex);
|
||||||
|
|
||||||
@@ -316,7 +371,7 @@ public class SpellAbilityPicker {
|
|||||||
final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller);
|
final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller);
|
||||||
Score lastScore = null;
|
Score lastScore = null;
|
||||||
do {
|
do {
|
||||||
GameSimulator simulator = new GameSimulator(controller, game, player);
|
GameSimulator simulator = new GameSimulator(controller, game, player, phase);
|
||||||
simulator.setInterceptor(choicesIterator);
|
simulator.setInterceptor(choicesIterator);
|
||||||
lastScore = simulator.simulateSpellAbility(sa);
|
lastScore = simulator.simulateSpellAbility(sa);
|
||||||
if (lastScore.value > bestScore.value) {
|
if (lastScore.value > bestScore.value) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package forge.game.combat;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -91,19 +92,23 @@ public class Combat {
|
|||||||
attackableEntries.add(map.map(entry));
|
attackableEntries.add(map.map(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HashMap<AttackingBand, AttackingBand> bandsMap = new HashMap<>();
|
||||||
for (Entry<GameEntity, AttackingBand> entry : combat.attackedByBands.entries()) {
|
for (Entry<GameEntity, AttackingBand> entry : combat.attackedByBands.entries()) {
|
||||||
|
AttackingBand origBand = entry.getValue();
|
||||||
ArrayList<Card> attackers = new ArrayList<Card>();
|
ArrayList<Card> attackers = new ArrayList<Card>();
|
||||||
for (Card c : entry.getValue().getAttackers()) {
|
for (Card c : origBand.getAttackers()) {
|
||||||
attackers.add(map.map(c));
|
attackers.add(map.map(c));
|
||||||
}
|
}
|
||||||
attackedByBands.put(map.map(entry.getKey()), new AttackingBand(attackers));
|
AttackingBand newBand = new AttackingBand(attackers);
|
||||||
|
Boolean blocked = entry.getValue().isBlocked();
|
||||||
|
if (blocked != null) {
|
||||||
|
newBand.setBlocked(blocked);
|
||||||
|
}
|
||||||
|
bandsMap.put(origBand, newBand);
|
||||||
|
attackedByBands.put(map.map(entry.getKey()), newBand);
|
||||||
}
|
}
|
||||||
for (Entry<AttackingBand, Card> entry : combat.blockedBands.entries()) {
|
for (Entry<AttackingBand, Card> entry : combat.blockedBands.entries()) {
|
||||||
ArrayList<Card> attackers = new ArrayList<Card>();
|
blockedBands.put(bandsMap.get(entry.getKey()), map.map(entry.getValue()));
|
||||||
for (Card c : entry.getKey().getAttackers()) {
|
|
||||||
attackers.add(map.map(c));
|
|
||||||
}
|
|
||||||
blockedBands.put(new AttackingBand(attackers), map.map(entry.getValue()));
|
|
||||||
}
|
}
|
||||||
for (Entry<Card, Integer> entry : combat.defendingDamageMap.entrySet()) {
|
for (Entry<Card, Integer> entry : combat.defendingDamageMap.entrySet()) {
|
||||||
defendingDamageMap.put(map.map(entry.getKey()), entry.getValue());
|
defendingDamageMap.put(map.map(entry.getKey()), entry.getValue());
|
||||||
@@ -123,6 +128,28 @@ public class Combat {
|
|||||||
attackConstraints = new AttackConstraints(this);
|
attackConstraints = new AttackConstraints(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (GameEntity defender : attackableEntries) {
|
||||||
|
CardCollection attackers = getAttackersOf(defender);
|
||||||
|
if (attackers.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sb.append(defender);
|
||||||
|
sb.append(" is being attacked by:\n");
|
||||||
|
for (Card attacker : attackers) {
|
||||||
|
sb.append(" ").append(attacker).append("\n");
|
||||||
|
for (Card blocker : getBlockers(attacker)) {
|
||||||
|
sb.append(" ... blocked by: ").append(blocker).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() == 0) {
|
||||||
|
return "<no attacks>";
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void endCombat() {
|
public void endCombat() {
|
||||||
//backup attackers and blockers
|
//backup attackers and blockers
|
||||||
|
|||||||
@@ -889,8 +889,6 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
// don't even offer priority, because it's untap of 1st turn now
|
// don't even offer priority, because it's untap of 1st turn now
|
||||||
givePriorityToPlayer = false;
|
givePriorityToPlayer = false;
|
||||||
|
|
||||||
final Set<Card> allAffectedCards = new HashSet<Card>();
|
|
||||||
|
|
||||||
// MAIN GAME LOOP
|
// MAIN GAME LOOP
|
||||||
while (!game.isGameOver()) {
|
while (!game.isGameOver()) {
|
||||||
if (givePriorityToPlayer) {
|
if (givePriorityToPlayer) {
|
||||||
@@ -903,17 +901,9 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
int loopCount = 0;
|
int loopCount = 0;
|
||||||
do {
|
do {
|
||||||
do {
|
if (checkStateBasedEffects()) {
|
||||||
// Rule 704.3 Whenever a player would get priority, the game checks ... for state-based actions,
|
// state-based effects check could lead to game over
|
||||||
game.getAction().checkStateEffects(false, allAffectedCards);
|
return;
|
||||||
if (game.isGameOver()) {
|
|
||||||
return; // state-based effects check could lead to game over
|
|
||||||
}
|
|
||||||
} while (game.getStack().addAllTriggeredAbilitiesToStack()); //loop so long as something was added to stack
|
|
||||||
|
|
||||||
if (!allAffectedCards.isEmpty()) {
|
|
||||||
game.fireEvent(new GameEventCardStatsChanged(allAffectedCards));
|
|
||||||
allAffectedCards.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerTurn.hasLost() && pPlayerPriority.equals(playerTurn) && pFirstPriority.equals(playerTurn)) {
|
if (playerTurn.hasLost() && pPlayerPriority.equals(playerTurn) && pFirstPriority.equals(playerTurn)) {
|
||||||
@@ -996,9 +986,39 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkStateBasedEffects() {
|
||||||
|
final Set<Card> allAffectedCards = new HashSet<Card>();
|
||||||
|
do {
|
||||||
|
// Rule 704.3 Whenever a player would get priority, the game checks ... for state-based actions,
|
||||||
|
game.getAction().checkStateEffects(false, allAffectedCards);
|
||||||
|
if (game.isGameOver()) {
|
||||||
|
return true; // state-based effects check could lead to game over
|
||||||
|
}
|
||||||
|
} while (game.getStack().addAllTriggeredAbilitiesToStack()); //loop so long as something was added to stack
|
||||||
|
|
||||||
|
if (!allAffectedCards.isEmpty()) {
|
||||||
|
game.fireEvent(new GameEventCardStatsChanged(allAffectedCards));
|
||||||
|
allAffectedCards.clear();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean devAdvanceToPhase(PhaseType targetPhase) {
|
||||||
|
while (phase.isBefore(targetPhase)) {
|
||||||
|
if (checkStateBasedEffects()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
onPhaseEnd();
|
||||||
|
advanceToNextPhase();
|
||||||
|
onPhaseBegin();
|
||||||
|
}
|
||||||
|
checkStateBasedEffects();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// this is a hack for the setup game state mode, do not use outside of devSetupGameState code
|
// this is a hack for the setup game state mode, do not use outside of devSetupGameState code
|
||||||
// as it avoids calling any of the phase effects that may be necessary in a less enforced context
|
// as it avoids calling any of the phase effects that may be necessary in a less enforced context
|
||||||
public final void devModeSet(final PhaseType phase0, final Player player0) {
|
public final void devModeSet(final PhaseType phase0, final Player player0, boolean endCombat) {
|
||||||
if (phase0 != null) {
|
if (phase0 != null) {
|
||||||
setPhase(phase0);
|
setPhase(phase0);
|
||||||
}
|
}
|
||||||
@@ -1007,8 +1027,13 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
game.fireEvent(new GameEventTurnPhase(playerTurn, phase, ""));
|
game.fireEvent(new GameEventTurnPhase(playerTurn, phase, ""));
|
||||||
|
if (endCombat) {
|
||||||
endCombat(); // not-null can be created only when declare attackers phase begins
|
endCombat(); // not-null can be created only when declare attackers phase begins
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
public final void devModeSet(final PhaseType phase0, final Player player0) {
|
||||||
|
devModeSet(phase0, player0, true);
|
||||||
|
}
|
||||||
|
|
||||||
public final void endTurnByEffect() {
|
public final void endTurnByEffect() {
|
||||||
endCombat();
|
endCombat();
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public class GameSimulatorTest extends SimulationTestCase {
|
|||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
assertEquals(20, p.getOpponent().getLife());
|
assertEquals(20, game.getPlayers().get(0).getLife());
|
||||||
|
|
||||||
GameSimulator sim = createSimulator(game, p);
|
GameSimulator sim = createSimulator(game, p);
|
||||||
Game simGame = sim.getSimulatedGameState();
|
Game simGame = sim.getSimulatedGameState();
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class SimulationTestCase extends TestCase {
|
|||||||
public boolean shouldRecurse() {
|
public boolean shouldRecurse() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, game, p);
|
}, game, p, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Card findCardWithName(Game game, String name) {
|
protected Card findCardWithName(Game game, String name) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -22,7 +23,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
addCard("Runeclaw Bear", opponent);
|
addCard("Runeclaw Bear", opponent);
|
||||||
opponent.setLife(2, null);
|
opponent.setLife(2, null);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
@@ -43,7 +44,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
Card bearCard = addCard("Runeclaw Bear", opponent);
|
Card bearCard = addCard("Runeclaw Bear", opponent);
|
||||||
opponent.setLife(20, null);
|
opponent.setLife(20, null);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
@@ -91,7 +92,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
Player opponent = game.getPlayers().get(0);
|
Player opponent = game.getPlayers().get(0);
|
||||||
addCard("Runeclaw Bear", opponent);
|
addCard("Runeclaw Bear", opponent);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
// Expected: All creatures get -2/-2 to kill the bear.
|
// Expected: All creatures get -2/-2 to kill the bear.
|
||||||
@@ -110,7 +111,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
addCard("Swamp", p);
|
addCard("Swamp", p);
|
||||||
Card spell = addCardToZone("Dromar's Charm", p, ZoneType.Hand);
|
Card spell = addCardToZone("Dromar's Charm", p, ZoneType.Hand);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
// Expected: Gain 5 life, since other modes aren't helpful.
|
// Expected: Gain 5 life, since other modes aren't helpful.
|
||||||
@@ -134,7 +135,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
addCard("Runeclaw Bear", opponent);
|
addCard("Runeclaw Bear", opponent);
|
||||||
opponent.setLife(20, null);
|
opponent.setLife(20, null);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
// Expected: 2x 1 damage to each creature, 1x 2 damage to each opponent.
|
// Expected: 2x 1 damage to each creature, 1x 2 damage to each opponent.
|
||||||
@@ -162,7 +163,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
addCard("Runeclaw Bear", opponent);
|
addCard("Runeclaw Bear", opponent);
|
||||||
opponent.setLife(6, null);
|
opponent.setLife(6, null);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
// Expected: 3x 2 damage to each opponent.
|
// Expected: 3x 2 damage to each opponent.
|
||||||
@@ -188,7 +189,7 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
Card men = addCard("Flying Men", opponent);
|
Card men = addCard("Flying Men", opponent);
|
||||||
opponent.setLife(20, null);
|
opponent.setLife(20, null);
|
||||||
|
|
||||||
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
|
||||||
game.getAction().checkStateEffects(true);
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
@@ -292,4 +293,96 @@ public class SpellAbilityPickerTest extends SimulationTestCase {
|
|||||||
String saDesc = plan.getDecisions().get(1).saRef.toString();
|
String saDesc = plan.getDecisions().get(1).saRef.toString();
|
||||||
assertTrue(saDesc, saDesc.startsWith("Lightning Bolt deals 3 damage to target creature or player."));
|
assertTrue(saDesc, saDesc.startsWith("Lightning Bolt deals 3 damage to target creature or player."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPlayingPumpSpellsAfterBlocks() {
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setLife(2, null);
|
||||||
|
|
||||||
|
Card blocker = addCard("Fugitive Wizard", opponent);
|
||||||
|
Card attacker1 = addCard("Dwarven Trader", p);
|
||||||
|
attacker1.setSickness(false);
|
||||||
|
Card attacker2 = addCard("Kird Ape", p);
|
||||||
|
attacker2.setSickness(false);
|
||||||
|
addCard("Mountain", p);
|
||||||
|
addCardToZone("Brute Force", p, ZoneType.Hand);
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
|
assertNull(picker.chooseSpellAbilityToPlay(null));
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.COMBAT_BEGIN, p);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
assertNull(picker.chooseSpellAbilityToPlay(null));
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.COMBAT_DECLARE_ATTACKERS, p);
|
||||||
|
Combat combat = new Combat(p);
|
||||||
|
combat.addAttacker(attacker1, opponent);
|
||||||
|
combat.addAttacker(attacker2, opponent);
|
||||||
|
game.getPhaseHandler().setCombat(combat);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
assertNull(picker.chooseSpellAbilityToPlay(null));
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.COMBAT_DECLARE_BLOCKERS, p, false);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
combat.addBlocker(attacker1, blocker);
|
||||||
|
combat.getBandOfAttacker(attacker1).setBlocked(true);
|
||||||
|
combat.getBandOfAttacker(attacker2).setBlocked(false);
|
||||||
|
combat.orderBlockersForDamageAssignment();
|
||||||
|
combat.orderAttackersForDamageAssignment();
|
||||||
|
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
|
||||||
|
assertNotNull(sa);
|
||||||
|
assertEquals("Target creature gets +3/+3 until end of turn.", sa.toString());
|
||||||
|
assertEquals(attacker2, sa.getTargetCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPlayingSorceryPumpSpellsBeforeBlocks() {
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setLife(2, null);
|
||||||
|
|
||||||
|
addCard("Fugitive Wizard", opponent);
|
||||||
|
Card attacker1 = addCard("Dwarven Trader", p);
|
||||||
|
attacker1.setSickness(false);
|
||||||
|
Card attacker2 = addCard("Kird Ape", p);
|
||||||
|
attacker2.setSickness(false);
|
||||||
|
addCard("Mountain", p);
|
||||||
|
Card furor = addCardToZone("Furor of the Bitten", p, ZoneType.Hand);
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
|
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
|
||||||
|
assertNotNull(sa);
|
||||||
|
assertEquals(furor.getSpellAbilities().get(0), sa);
|
||||||
|
assertEquals(attacker1, sa.getTargetCard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPlayingRemovalBeforeBlocks() {
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setLife(2, null);
|
||||||
|
|
||||||
|
Card blocker = addCard("Fugitive Wizard", opponent);
|
||||||
|
Card attacker1 = addCard("Dwarven Trader", p);
|
||||||
|
attacker1.setSickness(false);
|
||||||
|
addCard("Swamp", p);
|
||||||
|
addCard("Swamp", p);
|
||||||
|
addCardToZone("Doom Blade", p, ZoneType.Hand);
|
||||||
|
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||||
|
game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
|
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
|
||||||
|
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
|
||||||
|
assertNotNull(sa);
|
||||||
|
assertEquals("Destroy target nonblack creature.", sa.toString());
|
||||||
|
assertEquals(blocker, sa.getTargetCard());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user