Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
// TODO update thisTurnCast

if (advanceToPhase != null) {
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, () -> GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent()));
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, () -> GameSimulator.resolveStack(newGame));
}

return newGame;
Expand Down
32 changes: 22 additions & 10 deletions forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public GameSimulator(SimulationController controller, Game origGame, Player orig
debugLines = origLines;
Game copyOrigGame = copier.makeCopy();
Player copyOrigAiPlayer = copyOrigGame.getPlayers().get(1);
resolveStack(copyOrigGame, copyOrigGame.getPlayers().get(0));
resolveStack(copyOrigGame);
origScore = eval.getScoreForGameState(copyOrigGame, copyOrigAiPlayer);
}

Expand Down Expand Up @@ -225,9 +225,7 @@ public Score simulateSpellAbility(SpellAbility origSa, GameStateEvaluator eval,
}

if (resolve) {
// TODO: Support multiple opponents.
Player opponent = aiPlayer.getWeakestOpponent();
resolveStack(simGame, opponent);
resolveStack(simGame);
}

// TODO: If this is during combat, before blockers are declared,
Expand Down Expand Up @@ -260,11 +258,9 @@ public Score simulateSpellAbility(SpellAbility origSa, GameStateEvaluator eval,
return score;
}

public static void resolveStack(final Game game, final Player opponent) {
// TODO: This needs to set an AI controller for all opponents, in case of multiplayer.
PlayerControllerAi sim = new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer());
sim.setUseSimulation(true);
opponent.runWithController(() -> {
public static void resolveStack(final Game game) {
List<Player> players = new ArrayList<>(game.getPlayers());
runWithSimulationControllers(game, players, () -> {
final Set<Card> allAffectedCards = new HashSet<>();
game.getAction().checkStateEffects(false, allAffectedCards);
game.getStack().addAllTriggeredAbilitiesToStack();
Expand All @@ -288,7 +284,23 @@ public static void resolveStack(final Game game, final Player opponent) {

// Continue until stack is empty.
}
}, sim);
});
}

private static void runWithSimulationControllers(final Game game, final List<Player> players, final Runnable proc) {
if (players.isEmpty()) {
proc.run();
return;
}

Player player = players.remove(0);
if (player.getController().isAI()) {
runWithSimulationControllers(game, players, proc);
return;
}
PlayerControllerAi sim = new PlayerControllerAi(game, player, player.getLobbyPlayer());
sim.setUseSimulation(true);
player.runWithController(() -> runWithSimulationControllers(game, players, proc), sim);
}

public Game getSimulatedGameState() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame, fina
gameCopy = copier.makeCopy(null, aiPlayer);
}

gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, () -> GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent()));
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, () -> GameSimulator.resolveStack(gameCopy));
CombatSimResult result = new CombatSimResult();
result.copier = copier;
result.gameCopy = gameCopy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.gamesimulationtests.util.PlayerControllerForTests;
import forge.util.StreamUtil;

import org.testng.AssertJUnit;
Expand Down Expand Up @@ -61,6 +62,44 @@ public void testActivateAbilityTriggers() {
AssertJUnit.assertEquals(1, warriorToken.getCurrentToughness());
}

@Test
public void testResolveStackUsesSimulationControllersForNonAiPlayers() {
Game game = initAndCreateThreePlayerGame();

Player opponent = game.getPlayers().get(0);
Player ai = game.getPlayers().get(1);
Player otherOpponent = game.getPlayers().get(2);

opponent.setTeam(0);
ai.setTeam(1);
otherOpponent.setTeam(2);

opponent.setLife(1, null);
otherOpponent.setLife(20, null);

addCard("Runeclaw Bear", opponent);
addCard("Runeclaw Bear", ai);
Card otherOpponentBear = addCard("Runeclaw Bear", otherOpponent);

Card spell = addCardToZone("Innocent Blood", opponent, ZoneType.Hand);
SpellAbility sa = spell.getFirstSpellAbility();
sa.setActivatingPlayer(opponent);
game.getStack().addAndUnfreeze(sa);

PlayerControllerForTests throwingController = new PlayerControllerForTests(game, otherOpponent, otherOpponent.getLobbyPlayer()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh, actually this test isn't proof either and it looks to me like the TODO was just outdated:
I spent the effort of actually debugging and thanks to clonePlayer I don't see how a GUI player should end up in a simulated game anyway...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll close this PR. If you have any feedback on how I can actually solve the issue of AI drawing cards into Xyris -> Impact tremors -> death, let me know.

@Override
public CardCollectionView choosePermanentsToSacrifice(SpellAbility ability, int min, int max,
CardCollectionView validTargets, String message) {
throw new AssertionError("resolveStack should install a simulation controller for non-AI players");
}
};

otherOpponent.runWithController(() -> GameSimulator.resolveStack(game), throwingController);

AssertJUnit.assertTrue("The non-AI non-weakest player should have sacrificed during stack resolution",
otherOpponent.getZone(ZoneType.Graveyard).contains(otherOpponentBear));
}

@Test
public void testStaticAbilities() {
String sliverCardName = "Sidewinder Sliver";
Expand Down
Loading