* Improved game is infinite loop check to ask players only if some kind of iteration is recognized.

This commit is contained in:
LevelX2 2017-06-05 12:27:59 +02:00
parent 2728a274ca
commit 3c6ede7407
2 changed files with 185 additions and 148 deletions

View file

@ -119,4 +119,34 @@ public class GameIsADrawTest extends CardTestPlayerBase {
} }
/**
* Check that a simple triggered ability does not trigger the infinite loop
* request to players
*/
@Test
public void GameDrawByInfiniteLoopNot() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 43);
// Whenever a creature enters the battlefield under your control, you gain life equal to its toughness.
addCard(Zone.BATTLEFIELD, playerA, "Angelic Chorus", 1); // Enchantment {5}
// Create X 4/4 white Angel creature tokens with flying.
// Miracle (You may cast this card for its miracle cost when you draw it if it's the first card you drew this turn.)
addCard(Zone.HAND, playerA, "Entreat the Angels", 1); // Sorcery {X}{X}{W}{W}{W}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Entreat the Angels");
setChoice(playerA, "X=20");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Angel", 20);
Assert.assertFalse("Game should not have ended.", currentGame.hasEnded());
assertLife(playerA, 100);
Assert.assertFalse("No inifinite loop detected, game has be no draw.", currentGame.isADraw());
}
} }

View file

@ -177,6 +177,8 @@ public abstract class GameImpl implements Game, Serializable {
protected PlayerList playerList; protected PlayerList playerList;
private int infiniteLoopCounter; // used to check if the game is in an infinite loop private int infiniteLoopCounter; // used to check if the game is in an infinite loop
private int lastNumberOfAbilitiesOnTheStack; // used to check how long no new ability was put to stack
private final LinkedList<UUID> stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist) // used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
protected Map<UUID, Counters> enterWithCounters = new HashMap<>(); protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
@ -1289,7 +1291,6 @@ public abstract class GameImpl implements Game, Serializable {
} }
if (allPassed()) { if (allPassed()) {
if (!state.getStack().isEmpty()) { if (!state.getStack().isEmpty()) {
checkInfiniteLoop();
//20091005 - 115.4 //20091005 - 115.4
resolve(); resolve();
applyEffects(); applyEffects();
@ -1298,7 +1299,6 @@ public abstract class GameImpl implements Game, Serializable {
resetShortLivingLKI(); resetShortLivingLKI();
break; break;
} else { } else {
infiniteLoopCounter = 0;
resetLKI(); resetLKI();
return; return;
} }
@ -1346,6 +1346,7 @@ public abstract class GameImpl implements Game, Serializable {
if (top != null) { if (top != null) {
state.getStack().remove(top); // seems partly redundant because move card from stack to grave is already done and the stack removed state.getStack().remove(top); // seems partly redundant because move card from stack to grave is already done and the stack removed
rememberLKI(top.getSourceId(), Zone.STACK, top); rememberLKI(top.getSourceId(), Zone.STACK, top);
checkInfiniteLoop(top.getSourceId());
if (!getTurn().isEndTurnRequested()) { if (!getTurn().isEndTurnRequested()) {
while (state.hasSimultaneousEvents()) { while (state.hasSimultaneousEvents()) {
state.handleSimultaneousEvent(this); state.handleSimultaneousEvent(this);
@ -1357,20 +1358,19 @@ public abstract class GameImpl implements Game, Serializable {
/** /**
* This checks if the stack gets filled iterated, without ever getting empty * This checks if the stack gets filled iterated, without ever getting empty
* If the defined number of iterations is reached, the players in range of * If the defined number of iterations with not more than 4 different
* the stackObject get asked to confirm a draw. If they do, all confirming * sourceIds for the removed stack Objects is reached, the players in range
* players get set to a draw. * of the stackObject get asked to confirm a draw. If they do, all
* confirming players get set to a draw.
* *
* Possible to improve: check that always the same set of stackObjects are * @param removedStackObjectSourceId
* again aand again on the stack
*/ */
protected void checkInfiniteLoop() { protected void checkInfiniteLoop(UUID removedStackObjectSourceId) {
if (getStack().size() < 4) { // to prevent that this also pops up, if e.g. 20 triggers resolve at once if (stackObjectsCheck.contains(removedStackObjectSourceId)
&& getStack().size() >= lastNumberOfAbilitiesOnTheStack) {
infiniteLoopCounter++; infiniteLoopCounter++;
if (infiniteLoopCounter > 15) { if (infiniteLoopCounter > 15) {
StackObject stackObject = getStack().getFirst(); Player controller = getPlayer(getControllerId(removedStackObjectSourceId));
if (stackObject != null) {
Player controller = getPlayer(stackObject.getControllerId());
if (controller != null) { if (controller != null) {
for (UUID playerId : getState().getPlayersInRange(controller.getId(), this)) { for (UUID playerId : getState().getPlayersInRange(controller.getId(), this)) {
Player player = getPlayer(playerId); Player player = getPlayer(playerId);
@ -1389,8 +1389,13 @@ public abstract class GameImpl implements Game, Serializable {
} }
} }
} }
} else {
stackObjectsCheck.add(removedStackObjectSourceId);
if (stackObjectsCheck.size() > 4) {
stackObjectsCheck.removeFirst();
} }
} }
lastNumberOfAbilitiesOnTheStack = getStack().size();
} }
protected boolean allPassed() { protected boolean allPassed() {
@ -2618,6 +2623,8 @@ public abstract class GameImpl implements Game, Serializable {
public void resetLKI() { public void resetLKI() {
lki.clear(); lki.clear();
lkiExtended.clear(); lkiExtended.clear();
infiniteLoopCounter = 0;
stackObjectsCheck.clear();
} }
@Override @Override