Fixed player leaving/conceding handling.

This commit is contained in:
LevelX2 2017-10-21 16:13:45 +02:00
parent 79d4c07d20
commit 58d3fc2328
21 changed files with 553 additions and 463 deletions

View file

@ -519,7 +519,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
logger.trace("interrupted - " + val); logger.trace("interrupted - " + val);
return val; return val;
} }
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) { if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) {
logger.trace("Add actions -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth); logger.trace("Add actions -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth);
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game);
UUID currentPlayerId = node.getGame().getPlayerList().get(); UUID currentPlayerId = node.getGame().getPlayerList().get();
@ -540,7 +540,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
} }
} }
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game);
} else if (!node.getChildren().isEmpty()) { } else if (!node.getChildren().isEmpty()) {
//declared attackers or blockers or triggered abilities //declared attackers or blockers or triggered abilities
@ -588,7 +588,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
logger.debug("Sim Prio [" + depth + "] -- repeated action: " + action.toString()); logger.debug("Sim Prio [" + depth + "] -- repeated action: " + action.toString());
continue; continue;
} }
if (!sim.gameOver(null) && action.isUsesStack()) { if (!sim.checkIfGameIsOver() && action.isUsesStack()) {
// only pass if the last action uses the stack // only pass if the last action uses the stack
UUID nextPlayerId = sim.getPlayerList().get(); UUID nextPlayerId = sim.getPlayerList().get();
do { do {
@ -864,7 +864,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
break; break;
case CLEANUP: case CLEANUP:
game.getPhase().getStep().beginStep(game, activePlayerId); game.getPhase().getStep().beginStep(game, activePlayerId);
if (!game.checkStateAndTriggered() && !game.gameOver(null)) { if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) {
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
game.getTurn().setPhase(new BeginningPhase()); game.getTurn().setPhase(new BeginningPhase());
game.getPhase().setStep(new UntapStep()); game.getPhase().setStep(new UntapStep());

View file

@ -233,7 +233,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
return GameStateEvaluator2.evaluate(playerId, game); return GameStateEvaluator2.evaluate(playerId, game);
} }
// Condition to stop deeper simulation // Condition to stop deeper simulation
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) { if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>'); StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>');
@ -267,7 +267,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
} }
} }
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game); val = GameStateEvaluator2.evaluate(playerId, game);
} else if (stepFinished) { } else if (stepFinished) {
logger.debug("Step finished"); logger.debug("Step finished");
@ -481,7 +481,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
Combat simCombat = sim.getCombat().copy(); Combat simCombat = sim.getCombat().copy();
finishCombat(sim); finishCombat(sim);
if (sim.gameOver(null)) { if (sim.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, sim); val = GameStateEvaluator2.evaluate(playerId, sim);
} else if (!counter) { } else if (!counter) {
val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta); val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta);
@ -549,7 +549,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
logger.debug("interrupted"); logger.debug("interrupted");
return; return;
} }
if (!game.gameOver(null)) { if (!game.checkIfGameIsOver()) {
game.getPhase().setStep(step); game.getPhase().setStep(step);
if (!step.skipStep(game, game.getActivePlayerId())) { if (!step.skipStep(game, game.getActivePlayerId())) {
step.beginStep(game, game.getActivePlayerId()); step.beginStep(game, game.getActivePlayerId());
@ -598,7 +598,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
logger.debug("interrupted"); logger.debug("interrupted");
return; return;
} }
if (!game.gameOver(null)) { if (!game.checkIfGameIsOver()) {
game.getTurn().getPhase().endPhase(game, game.getActivePlayerId()); game.getTurn().getPhase().endPhase(game, game.getActivePlayerId());
game.getTurn().setPhase(new EndPhase()); game.getTurn().setPhase(new EndPhase());
if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) { if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) {

View file

@ -33,7 +33,7 @@ public final class GameStateEvaluator2 {
public static int evaluate(UUID playerId, Game game) { public static int evaluate(UUID playerId, Game game) {
Player player = game.getPlayer(playerId); Player player = game.getPlayer(playerId);
Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next());
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) { if (player.hasLost() || opponent.hasWon()) {
return LOSE_GAME_SCORE; return LOSE_GAME_SCORE;
} }

View file

@ -61,7 +61,7 @@ public class ActionSimulator {
public int evaluateState() { public int evaluateState() {
Player opponent = game.getPlayer(game.getOpponents(player.getId()).iterator().next()); Player opponent = game.getPlayer(game.getOpponents(player.getId()).iterator().next());
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) { if (player.hasLost() || opponent.hasWon()) {
return Integer.MIN_VALUE; return Integer.MIN_VALUE;
} }

View file

@ -79,7 +79,7 @@ public class MCTSNode {
this.game = game; this.game = game;
this.stateValue = game.getState().getValue(game, targetPlayer); this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game); this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null); this.terminal = game.checkIfGameIsOver();
setPlayer(); setPlayer();
nodeCount = 1; nodeCount = 1;
// logger.info(this.stateValue); // logger.info(this.stateValue);
@ -90,7 +90,7 @@ public class MCTSNode {
this.game = game; this.game = game;
this.stateValue = game.getState().getValue(game, targetPlayer); this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game); this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null); this.terminal = game.checkIfGameIsOver();
this.parent = parent; this.parent = parent;
this.action = action; this.action = action;
setPlayer(); setPlayer();
@ -104,7 +104,7 @@ public class MCTSNode {
this.combat = combat; this.combat = combat;
this.stateValue = game.getState().getValue(game, targetPlayer); this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game); this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null); this.terminal = game.checkIfGameIsOver();
this.parent = parent; this.parent = parent;
setPlayer(); setPlayer();
nodeCount++; nodeCount++;

View file

@ -330,7 +330,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
return GameStateEvaluator.evaluate(playerId, game); return GameStateEvaluator.evaluate(playerId, game);
} }
int val; int val;
if (node.depth > maxDepth || game.gameOver(null)) { if (node.depth > maxDepth || game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- reached end state"); logger.debug(indent(node.depth) + "simulating -- reached end state");
val = GameStateEvaluator.evaluate(playerId, game); val = GameStateEvaluator.evaluate(playerId, game);
} }
@ -357,7 +357,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
} }
} }
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, game); val = GameStateEvaluator.evaluate(playerId, game);
} }
else if (!node.getChildren().isEmpty()) { else if (!node.getChildren().isEmpty()) {
@ -403,7 +403,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
logger.debug(indent(node.depth) + "found useless action: " + action); logger.debug(indent(node.depth) + "found useless action: " + action);
continue; continue;
} }
if (!sim.gameOver(null) && action.isUsesStack()) { if (!sim.checkIfGameIsOver() && action.isUsesStack()) {
// only pass if the last action uses the stack // only pass if the last action uses the stack
sim.getPlayer(currentPlayer.getId()).pass(game); sim.getPlayer(currentPlayer.getId()).pass(game);
sim.getPlayerList().getNext(); sim.getPlayerList().getNext();
@ -588,7 +588,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
break; break;
case CLEANUP: case CLEANUP:
game.getPhase().getStep().beginStep(game, activePlayerId); game.getPhase().getStep().beginStep(game, activePlayerId);
if (!game.checkStateAndTriggered() && !game.gameOver(null)) { if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) {
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
game.getTurn().setPhase(new BeginningPhase()); game.getTurn().setPhase(new BeginningPhase());
game.getPhase().setStep(new UntapStep()); game.getPhase().setStep(new UntapStep());

View file

@ -184,7 +184,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug(indent(node.depth) + "interrupted"); logger.debug(indent(node.depth) + "interrupted");
return GameStateEvaluator.evaluate(playerId, game); return GameStateEvaluator.evaluate(playerId, game);
} }
if (node.depth > maxDepth || game.gameOver(null)) { if (node.depth > maxDepth || game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- reached end state"); logger.debug(indent(node.depth) + "simulating -- reached end state");
val = GameStateEvaluator.evaluate(playerId, game); val = GameStateEvaluator.evaluate(playerId, game);
} }
@ -204,7 +204,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
} }
} }
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, game); val = GameStateEvaluator.evaluate(playerId, game);
} }
else if (stepFinished) { else if (stepFinished) {
@ -408,7 +408,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
Combat simCombat = sim.getCombat().copy(); Combat simCombat = sim.getCombat().copy();
finishCombat(sim); finishCombat(sim);
if (sim.gameOver(null)) { if (sim.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, sim); val = GameStateEvaluator.evaluate(playerId, sim);
} }
else if (!counter) { else if (!counter) {
@ -450,7 +450,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
return GameStateEvaluator.evaluate(playerId, game); return GameStateEvaluator.evaluate(playerId, game);
} }
Integer val = null; Integer val = null;
if (!game.gameOver(null)) { if (!game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- ending turn"); logger.debug(indent(node.depth) + "simulating -- ending turn");
simulateToEnd(game); simulateToEnd(game);
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
@ -478,7 +478,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug("interrupted"); logger.debug("interrupted");
return; return;
} }
if (!game.gameOver(null)) { if (!game.checkIfGameIsOver()) {
game.getPhase().setStep(step); game.getPhase().setStep(step);
if (!step.skipStep(game, game.getActivePlayerId())) { if (!step.skipStep(game, game.getActivePlayerId())) {
step.beginStep(game, game.getActivePlayerId()); step.beginStep(game, game.getActivePlayerId());
@ -526,7 +526,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug("interrupted"); logger.debug("interrupted");
return; return;
} }
if (!game.gameOver(null)) { if (!game.checkIfGameIsOver()) {
game.getTurn().getPhase().endPhase(game, game.getActivePlayerId()); game.getTurn().getPhase().endPhase(game, game.getActivePlayerId());
game.getTurn().setPhase(new EndPhase()); game.getTurn().setPhase(new EndPhase());
if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) { if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) {

View file

@ -70,7 +70,7 @@ public final class GameStateEvaluator {
public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) { public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) {
Player player = game.getPlayer(playerId); Player player = game.getPlayer(playerId);
Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next());
if (game.gameOver(null)) { if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) if (player.hasLost() || opponent.hasWon())
return LOSE_SCORE; return LOSE_SCORE;
if (opponent.hasLost() || player.hasWon()) if (opponent.hasLost() || player.hasWon())

View file

@ -53,6 +53,7 @@ import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.GameImpl;
import mage.game.combat.CombatGroup; import mage.game.combat.CombatGroup;
import mage.game.draft.Draft; import mage.game.draft.Draft;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
@ -186,13 +187,25 @@ public class HumanPlayer extends PlayerImpl {
response.clear(); response.clear();
logger.debug("Waiting response from player: " + getId()); logger.debug("Waiting response from player: " + getId());
game.resumeTimer(getTurnControlledBy()); game.resumeTimer(getTurnControlledBy());
synchronized (response) { boolean loop = true;
try { while (loop) {
response.wait(); loop = false;
} catch (InterruptedException ex) { synchronized (response) {
logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex); try {
} finally { response.wait();
game.pauseTimer(getTurnControlledBy()); } catch (InterruptedException ex) {
logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex);
} finally {
game.pauseTimer(getTurnControlledBy());
}
}
if (response.getResponseConcedeCheck()) {
((GameImpl) game).checkConcede();
if (game.hasEnded()) {
return;
}
response.clear();
loop = true;
} }
} }
if (recordingMacro && !macroTriggeredSelectionFlag) { if (recordingMacro && !macroTriggeredSelectionFlag) {
@ -1706,6 +1719,15 @@ public class HumanPlayer extends PlayerImpl {
} }
} }
@Override
public void signalPlayerConcede() {
synchronized (response) {
response.setResponseConcedeCheck();
response.notifyAll();
logger.debug("Set check concede for waiting player: " + getId());
}
}
@Override @Override
public void skip() { public void skip() {
synchronized (response) { synchronized (response) {

View file

@ -43,6 +43,7 @@ public class PlayerResponse implements Serializable {
private Integer responseInteger; private Integer responseInteger;
private ManaType responseManaType; private ManaType responseManaType;
private UUID responseManaTypePlayerId; private UUID responseManaTypePlayerId;
private Boolean responseConcedeCheck;
public PlayerResponse() { public PlayerResponse() {
clear(); clear();
@ -55,7 +56,8 @@ public class PlayerResponse implements Serializable {
+ ',' + responseBoolean + ',' + responseBoolean
+ ',' + responseInteger + ',' + responseInteger
+ ',' + responseManaType + ',' + responseManaType
+ ',' + responseManaTypePlayerId; + ',' + responseManaTypePlayerId
+ ',' + responseConcedeCheck;
} }
public PlayerResponse(PlayerResponse other) { public PlayerResponse(PlayerResponse other) {
@ -69,6 +71,7 @@ public class PlayerResponse implements Serializable {
responseInteger = other.responseInteger; responseInteger = other.responseInteger;
responseManaType = other.responseManaType; responseManaType = other.responseManaType;
responseManaTypePlayerId = other.responseManaTypePlayerId; responseManaTypePlayerId = other.responseManaTypePlayerId;
responseConcedeCheck = other.responseConcedeCheck;
} }
public void clear() { public void clear() {
@ -78,6 +81,7 @@ public class PlayerResponse implements Serializable {
responseInteger = null; responseInteger = null;
responseManaType = null; responseManaType = null;
responseManaTypePlayerId = null; responseManaTypePlayerId = null;
responseConcedeCheck = null;
} }
public String getString() { public String getString() {
@ -104,6 +108,17 @@ public class PlayerResponse implements Serializable {
this.responseBoolean = responseBoolean; this.responseBoolean = responseBoolean;
} }
public Boolean getResponseConcedeCheck() {
if (responseConcedeCheck == null) {
return false;
}
return responseConcedeCheck;
}
public void setResponseConcedeCheck() {
this.responseConcedeCheck = true;
}
public Integer getInteger() { public Integer getInteger() {
return responseInteger; return responseInteger;
} }

View file

@ -61,7 +61,7 @@ public class WatertrapWeaver extends CardImpl {
this.power = new MageInt(2); this.power = new MageInt(2);
this.toughness = new MageInt(2); this.toughness = new MageInt(2);
// When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step. // When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.
EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect());
ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect("that creature")); ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect("that creature"));
ability.addTarget(new TargetCreaturePermanent(filter)); ability.addTarget(new TargetCreaturePermanent(filter));

View file

@ -1,373 +1,373 @@
/* /*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package org.mage.test.multiplayer; package org.mage.test.multiplayer;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption; import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.FreeForAll; import mage.game.FreeForAll;
import mage.game.Game; import mage.game.Game;
import mage.game.GameException; import mage.game.GameException;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase; import org.mage.test.serverside.base.CardTestMultiPlayerBase;
/** /**
* *
* @author LevelX2 * @author LevelX2
*/ */
public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase { public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
@Override @Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2 // Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2); Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2);
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
playerA = createPlayer(game, playerA, "PlayerA"); playerA = createPlayer(game, playerA, "PlayerA");
playerB = createPlayer(game, playerB, "PlayerB"); playerB = createPlayer(game, playerB, "PlayerB");
playerC = createPlayer(game, playerC, "PlayerC"); playerC = createPlayer(game, playerC, "PlayerC");
playerD = createPlayer(game, playerD, "PlayerD"); playerD = createPlayer(game, playerD, "PlayerD");
return game; return game;
} }
/** /**
* Tests Enchantment to control other permanent * Tests Enchantment to control other permanent
*/ */
@Test @Test
public void TestControlledByEnchantment() { public void TestControlledByEnchantment() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4); addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Enchant creature // Enchant creature
// You control enchanted creature. // You control enchanted creature.
addCard(Zone.HAND, playerA, "Control Magic"); addCard(Zone.HAND, playerA, "Control Magic");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB); attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerB, 0); assertLife(playerB, 0);
assertPermanentCount(playerB, 0); assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); assertPermanentCount(playerA, "Rootwater Commando", 0);
assertGraveyardCount(playerA, "Control Magic", 1); assertGraveyardCount(playerA, "Control Magic", 1);
} }
/** /**
* Tests Sorcery to control other players permanent * Tests Sorcery to control other players permanent
*/ */
@Test @Test
public void TestControlledBySorcery() { public void TestControlledBySorcery() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4); addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Exchange control of target artifact or creature and another target permanent that shares one of those types with it. // Exchange control of target artifact or creature and another target permanent that shares one of those types with it.
// (This effect lasts indefinitely.) // (This effect lasts indefinitely.)
addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery
addCard(Zone.BATTLEFIELD, playerA, "Wall of Air"); addCard(Zone.BATTLEFIELD, playerA, "Wall of Air");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air");
attack(3, playerC, "Silvercoat Lion", playerB); attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerB, 0); assertLife(playerB, 0);
assertGraveyardCount(playerA, "Legerdemain", 1); assertGraveyardCount(playerA, "Legerdemain", 1);
assertPermanentCount(playerB, 0); assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left
assertPermanentCount(playerB, "Wall of Air", 0); assertPermanentCount(playerB, "Wall of Air", 0);
assertGraveyardCount(playerA, "Wall of Air", 0); assertGraveyardCount(playerA, "Wall of Air", 0);
assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A
} }
/** /**
* Tests Instant to control other permanent * Tests Instant to control other permanent
*/ */
@Test @Test
public void TestOtherPlayerControllsCreature() { public void TestOtherPlayerControllsCreature() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn. // Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn.
addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB); attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerB, 0); assertLife(playerB, 0);
assertGraveyardCount(playerA, "Blind with Anger", 1); assertGraveyardCount(playerA, "Blind with Anger", 1);
assertPermanentCount(playerB, 0); assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left
assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A
} }
/** /**
* Xmage throws an error involving an emblem unable to find the initial * Xmage throws an error involving an emblem unable to find the initial
* source if it has a proc. To reproduce, a Planeswalker was taken from an * source if it has a proc. To reproduce, a Planeswalker was taken from an
* original player's control, such as using Scrambleverse to shuffle Jace, * original player's control, such as using Scrambleverse to shuffle Jace,
* Unraveler of Secrets, to a second player and then the second player uses * Unraveler of Secrets, to a second player and then the second player uses
* Jace's ability to create an emblem ("Whenever an opponent casts his or * Jace's ability to create an emblem ("Whenever an opponent casts his or
* her first spell each turn, counter that spell."). Then the original * her first spell each turn, counter that spell."). Then the original
* player concedes the game and removes the Planeswalker. Once it becomes an * player concedes the game and removes the Planeswalker. Once it becomes an
* opponent of the original player's turn and that opponent plays a spell, * opponent of the original player's turn and that opponent plays a spell,
* Xmage throws an error and rollsback the turn. * Xmage throws an error and rollsback the turn.
* *
* I don't have the actual error report on my due to negligence, but what I * I don't have the actual error report on my due to negligence, but what I
* can recollect is that the error message was along the lines of "The * can recollect is that the error message was along the lines of "The
* emblem cannot find the original source. This turn will be rolled back". * emblem cannot find the original source. This turn will be rolled back".
* This error message will always appear when an opponent tries to play a * This error message will always appear when an opponent tries to play a
* spell. Player order: A -> D -> C -> B * spell. Player order: A -> D -> C -> B
*/ */
@Test @Test
public void TestOtherPlayerPlaneswalkerCreatedEmblem() { public void TestOtherPlayerPlaneswalkerCreatedEmblem() {
// +1: Scry 1, then draw a card. // +1: Scry 1, then draw a card.
// -2: Return target creature to its owner's hand. // -2: Return target creature to its owner's hand.
// -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell." // -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell."
addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets"); addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets");
addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8); addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8);
addCard(Zone.BATTLEFIELD, playerA, "Island", 6); addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
// Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.) // Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.)
// You control enchanted permanent. // You control enchanted permanent.
addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura
addCard(Zone.BATTLEFIELD, playerC, "Plains", 2); addCard(Zone.BATTLEFIELD, playerC, "Plains", 2);
addCard(Zone.HAND, playerC, "Silvercoat Lion"); addCard(Zone.HAND, playerC, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
addCard(Zone.HAND, playerD, "Silvercoat Lion"); addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB); attack(3, playerC, "Silvercoat Lion", playerB);
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion"); castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion");
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
setStopAt(5, PhaseStep.END_TURN); setStopAt(5, PhaseStep.END_TURN);
execute(); execute();
assertLife(playerB, 0); assertLife(playerB, 0);
assertPermanentCount(playerB, 0); assertPermanentCount(playerB, 0);
assertGraveyardCount(playerA, "Confiscate", 1); assertGraveyardCount(playerA, "Confiscate", 1);
assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game
assertEmblemCount(playerA, 1); assertEmblemCount(playerA, 1);
assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1 assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1
assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell
} }
/** /**
* Situation: I attacked an opponent with some creatures with True * Situation: I attacked an opponent with some creatures with True
* Conviction in play. There were multiple "deals combat damage to a * Conviction in play. There were multiple "deals combat damage to a
* player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al), * player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al),
* then the opponent lost the game during the first strike combat * then the opponent lost the game during the first strike combat
* damage-step . In the second combat damage step the triggers went on the * damage-step . In the second combat damage step the triggers went on the
* stack again, although there was no player being dealt damage (multiplayer * stack again, although there was no player being dealt damage (multiplayer
* game, so the game wasn't over yet). I don't think these abilities should * game, so the game wasn't over yet). I don't think these abilities should
* trigger again here. * trigger again here.
*/ */
@Test @Test
public void TestPlayerDiesDuringFirstStrikeDamageStep() { public void TestPlayerDiesDuringFirstStrikeDamageStep() {
// Creatures you control have double strike and lifelink. // Creatures you control have double strike and lifelink.
addCard(Zone.BATTLEFIELD, playerD, "True Conviction"); addCard(Zone.BATTLEFIELD, playerD, "True Conviction");
// Whenever a creature deals combat damage to one of your opponents, its controller may draw a card. // Whenever a creature deals combat damage to one of your opponents, its controller may draw a card.
addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest"); addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest");
addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1 addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1
attack(2, playerD, "Dross Crocodile", playerC); attack(2, playerD, "Dross Crocodile", playerC);
setStopAt(3, PhaseStep.END_TURN); setStopAt(3, PhaseStep.END_TURN);
execute(); execute();
assertLife(playerC, -3); assertLife(playerC, -3);
assertLife(playerD, 7); assertLife(playerD, 7);
assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition
assertPermanentCount(playerC, 0); assertPermanentCount(playerC, 0);
} }
/** /**
* I've encountered a case today where someone conceded on their turn. The * I've encountered a case today where someone conceded on their turn. The
* remaining phases were went through as normal, but my Luminarch Ascension * remaining phases were went through as normal, but my Luminarch Ascension
* did not trigger during the end step. * did not trigger during the end step.
*/ */
// Player order: A -> D -> C -> B // Player order: A -> D -> C -> B
@Test @Test
public void TestTurnEndTrigger() { public void TestTurnEndTrigger() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerC, "Lightning Bolt"); addCard(Zone.HAND, playerC, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1);
addCard(Zone.HAND, playerD, "Silvercoat Lion"); addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD); castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN); setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute(); execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1); assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertGraveyardCount(playerC, "Lightning Bolt", 1); assertGraveyardCount(playerC, "Lightning Bolt", 1);
assertLife(playerD, -1); assertLife(playerD, -1);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
} }
@Test @Test
public void TestTurnEndTriggerAfterConcede() { public void TestTurnEndTriggerAfterConcede() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerD, "Silvercoat Lion"); addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
concede(2, PhaseStep.BEGIN_COMBAT, playerD); concede(2, PhaseStep.BEGIN_COMBAT, playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN); setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute(); execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1); assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertLife(playerD, 2); assertLife(playerD, 2);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
} }
/** /**
* Pithing Needle keeps the named card's abilities disabled even after the * Pithing Needle keeps the named card's abilities disabled even after the
* player controlling the Needle loses the game. * player controlling the Needle loses the game.
* *
* I saw it happen during a Commander game. A player cast Pithing Needle * I saw it happen during a Commander game. A player cast Pithing Needle
* targeting my Proteus Staff. After I killed him, I still couldn't activate * targeting my Proteus Staff. After I killed him, I still couldn't activate
* the Staff. * the Staff.
*/ */
@Test @Test
public void TestPithingNeedle() { public void TestPithingNeedle() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
// As Pithing Needle enters the battlefield, name a card. // As Pithing Needle enters the battlefield, name a card.
// Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.
addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1} addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1}
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1);
addCard(Zone.BATTLEFIELD, playerD, "Island", 3); addCard(Zone.BATTLEFIELD, playerD, "Island", 3);
// {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the // {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the
// top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the // top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the
// rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery. // rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery.
addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1); addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1); addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1);
addCard(Zone.LIBRARY, playerD, "Storm Crow", 2); addCard(Zone.LIBRARY, playerD, "Storm Crow", 2);
addCard(Zone.BATTLEFIELD, playerC, "Island", 3); addCard(Zone.BATTLEFIELD, playerC, "Island", 3);
addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1); addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1); addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1);
addCard(Zone.LIBRARY, playerC, "Wind Drake", 2); addCard(Zone.LIBRARY, playerC, "Wind Drake", 2);
addCard(Zone.BATTLEFIELD, playerB, "Island", 3); addCard(Zone.BATTLEFIELD, playerB, "Island", 3);
addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1); addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1);
skipInitShuffling(); skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle");
setChoice(playerA, "Proteus Staff"); setChoice(playerA, "Proteus Staff");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game
setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertPermanentCount(playerA, 0); assertLife(playerA, 2);
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame());
assertLife(playerA, 2);
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame()); assertPermanentCount(playerA, 0);
Permanent staffPlayerD = getPermanent("Proteus Staff", playerD); Permanent staffPlayerD = getPermanent("Proteus Staff", playerD);
Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped()); Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped());
assertPermanentCount(playerD, "Eager Cadet", 0); assertPermanentCount(playerD, "Eager Cadet", 0);
assertPermanentCount(playerD, "Storm Crow", 1); assertPermanentCount(playerD, "Storm Crow", 1);
Permanent staffPlayerC = getPermanent("Proteus Staff", playerC); Permanent staffPlayerC = getPermanent("Proteus Staff", playerC);
Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped()); Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped());
assertPermanentCount(playerC, "Wall of Air", 0); assertPermanentCount(playerC, "Wall of Air", 0);
assertPermanentCount(playerC, "Wind Drake", 1); assertPermanentCount(playerC, "Wind Drake", 1);
Permanent staffPlayerB = getPermanent("Proteus Staff", playerB); Permanent staffPlayerB = getPermanent("Proteus Staff", playerB);
Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped()); Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped());
} }
} }

View file

@ -57,6 +57,7 @@ import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.SummoningSicknessPredicate; import mage.filter.predicate.permanent.SummoningSicknessPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.GameImpl;
import mage.game.Graveyard; import mage.game.Graveyard;
import mage.game.Table; import mage.game.Table;
import mage.game.combat.CombatGroup; import mage.game.combat.CombatGroup;
@ -519,6 +520,7 @@ public class TestPlayer implements Player {
} }
if (groups[0].equals("Concede")) { if (groups[0].equals("Concede")) {
game.concede(getId()); game.concede(getId());
((GameImpl) game).checkConcede();
actions.remove(action); actions.remove(action);
} }
} }
@ -1182,6 +1184,11 @@ public class TestPlayer implements Player {
computerPlayer.abort(); computerPlayer.abort();
} }
@Override
public void signalPlayerConcede() {
computerPlayer.signalPlayerConcede();
}
@Override @Override
public void abortReset() { public void abortReset() {
computerPlayer.abortReset(); computerPlayer.abortReset();

View file

@ -27,6 +27,8 @@
*/ */
package org.mage.test.stub; package org.mage.test.stub;
import java.io.Serializable;
import java.util.*;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.AlternativeSourceCosts;
@ -62,9 +64,6 @@ import mage.target.TargetAmount;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.io.Serializable;
import java.util.*;
/** /**
* *
* @author Quercitron * @author Quercitron
@ -702,6 +701,11 @@ public class PlayerStub implements Player {
} }
@Override
public void signalPlayerConcede() {
}
@Override @Override
public void abortReset() { public void abortReset() {

View file

@ -173,7 +173,7 @@ public interface Game extends MageItem, Serializable {
UUID getPriorityPlayerId(); UUID getPriorityPlayerId();
boolean gameOver(UUID playerId); boolean checkIfGameIsOver();
boolean hasEnded(); boolean hasEnded();
@ -347,6 +347,8 @@ public interface Game extends MageItem, Serializable {
void concede(UUID playerId); void concede(UUID playerId);
void setConcedingPlayer(UUID playerId);
void setManaPaymentMode(UUID playerId, boolean autoPayment); void setManaPaymentMode(UUID playerId, boolean autoPayment);
void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted); void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted);

View file

@ -161,6 +161,8 @@ public abstract class GameImpl implements Game, Serializable {
private final LinkedList<UUID> stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the 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<>();
// used to proceed player conceding requests
private final LinkedList<UUID> concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) {
this.id = UUID.randomUUID(); this.id = UUID.randomUUID();
@ -535,26 +537,58 @@ public abstract class GameImpl implements Game, Serializable {
} }
} }
/** // /**
* Starts check if game is over or if playerId is given let the player // * Starts check if game is over or if playerId is given let the player
* concede. // * concede.
* // *
* @param playerId // * @param playerId
* @return // * @return
*/ // */
// @Override
// public synchronized boolean gameOver(UUID playerId) {
// if (playerId == null) {
// boolean result = checkIfGameIsOver();
// return result;
// } else {
// logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
// concedingPlayers.add(playerId);
// Player player = getPlayer(state.getPriorityPlayerId());
// if (player != null && player.isHuman()) {
// player.signalPlayerConcede();
// } else {
// checkConcede();
// }
// return true;
// }
// }
@Override @Override
public synchronized boolean gameOver(UUID playerId) { public void setConcedingPlayer(UUID playerId) {
if (playerId == null) { Player player = getPlayer(state.getPriorityPlayerId());
boolean result = checkIfGameIsOver(); if (player != null) {
return result; if (!player.hasLeft() && player.isHuman()) {
if (!concedingPlayers.contains(playerId)) {
logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
concedingPlayers.add(playerId);
player.signalPlayerConcede();
}
} else {
// no asynchronous action so check directly
checkConcede();
}
} else { } else {
logger.debug("Game over for player Id: " + playerId + " gameId " + getId()); checkConcede();
leave(playerId); checkIfGameIsOver();
return true;
} }
} }
private boolean checkIfGameIsOver() { public void checkConcede() {
while (!concedingPlayers.isEmpty()) {
leave(concedingPlayers.removeFirst());
}
}
@Override
public boolean checkIfGameIsOver() {
if (state.isGameOver()) { if (state.isGameOver()) {
return true; return true;
} }
@ -578,7 +612,7 @@ public abstract class GameImpl implements Game, Serializable {
} }
for (Player player : state.getPlayers().values()) { for (Player player : state.getPlayers().values()) {
if (!player.hasLeft() && !player.hasLost()) { if (!player.hasLeft() && !player.hasLost()) {
logger.debug(new StringBuilder("Player ").append(player.getName()).append(" has won gameId: ").append(this.getId())); logger.debug("Player " + player.getName() + " has won gameId: " + this.getId());
player.won(this); player.won(this);
} }
} }
@ -696,13 +730,13 @@ public abstract class GameImpl implements Game, Serializable {
Player player = getPlayer(playerList.get()); Player player = getPlayer(playerList.get());
boolean wasPaused = state.isPaused(); boolean wasPaused = state.isPaused();
state.resume(); state.resume();
if (!gameOver(null)) { if (!checkIfGameIsOver()) {
fireInformEvent("Turn " + state.getTurnNum()); fireInformEvent("Turn " + state.getTurnNum());
if (checkStopOnTurnOption()) { if (checkStopOnTurnOption()) {
return; return;
} }
state.getTurn().resumePlay(this, wasPaused); state.getTurn().resumePlay(this, wasPaused);
if (!isPaused() && !gameOver(null)) { if (!isPaused() && !checkIfGameIsOver()) {
endOfTurn(); endOfTurn();
player = playerList.getNext(this); player = playerList.getNext(this);
state.setTurnNum(state.getTurnNum() + 1); state.setTurnNum(state.getTurnNum() + 1);
@ -712,11 +746,11 @@ public abstract class GameImpl implements Game, Serializable {
} }
protected void play(UUID nextPlayerId) { protected void play(UUID nextPlayerId) {
if (!isPaused() && !gameOver(null)) { if (!isPaused() && !checkIfGameIsOver()) {
playerList = state.getPlayerList(nextPlayerId); playerList = state.getPlayerList(nextPlayerId);
Player playerByOrder = getPlayer(playerList.get()); Player playerByOrder = getPlayer(playerList.get());
state.setPlayerByOrderId(playerByOrder.getId()); state.setPlayerByOrderId(playerByOrder.getId());
while (!isPaused() && !gameOver(null)) { while (!isPaused() && !checkIfGameIsOver()) {
if (!playExtraTurns()) { if (!playExtraTurns()) {
break; break;
} }
@ -733,7 +767,7 @@ public abstract class GameImpl implements Game, Serializable {
state.setPlayerByOrderId(playerByOrder.getId()); state.setPlayerByOrderId(playerByOrder.getId());
} }
} }
if (gameOver(null) && !isSimulation()) { if (checkIfGameIsOver() && !isSimulation()) {
winnerId = findWinnersAndLosers(); winnerId = findWinnersAndLosers();
StringBuilder sb = new StringBuilder("GAME END gameId: ").append(this.getId()).append(' '); StringBuilder sb = new StringBuilder("GAME END gameId: ").append(this.getId()).append(' ');
int count = 0; int count = 0;
@ -816,7 +850,7 @@ public abstract class GameImpl implements Game, Serializable {
skipTurn = state.getTurn().play(this, player); skipTurn = state.getTurn().play(this, player);
} while (executingRollback); } while (executingRollback);
if (isPaused() || gameOver(null)) { if (isPaused() || checkIfGameIsOver()) {
return false; return false;
} }
if (!skipTurn) { if (!skipTurn) {
@ -854,7 +888,7 @@ public abstract class GameImpl implements Game, Serializable {
saveState(false); saveState(false);
if (gameOver(null)) { if (checkIfGameIsOver()) {
return; return;
} }
@ -1245,7 +1279,7 @@ public abstract class GameImpl implements Game, Serializable {
clearAllBookmarks(); clearAllBookmarks();
try { try {
applyEffects(); applyEffects();
while (!isPaused() && !gameOver(null) && !this.getTurn().isEndTurnRequested()) { while (!isPaused() && !checkIfGameIsOver() && !this.getTurn().isEndTurnRequested()) {
if (!resuming) { if (!resuming) {
state.getPlayers().resetPassed(); state.getPlayers().resetPassed();
state.getPlayerList().setCurrent(activePlayerId); state.getPlayerList().setCurrent(activePlayerId);
@ -1254,14 +1288,14 @@ public abstract class GameImpl implements Game, Serializable {
} }
fireUpdatePlayersEvent(); fireUpdatePlayersEvent();
Player player; Player player;
while (!isPaused() && !gameOver(null)) { while (!isPaused() && !checkIfGameIsOver()) {
try { try {
if (bookmark == 0) { if (bookmark == 0) {
bookmark = bookmarkState(); bookmark = bookmarkState();
} }
player = getPlayer(state.getPlayerList().get()); player = getPlayer(state.getPlayerList().get());
state.setPriorityPlayerId(player.getId()); state.setPriorityPlayerId(player.getId());
while (!player.isPassed() && player.canRespond() && !isPaused() && !gameOver(null)) { while (!player.isPassed() && player.canRespond() && !isPaused() && !checkIfGameIsOver()) {
if (!resuming) { if (!resuming) {
// 603.3. Once an ability has triggered, its controller puts it on the stack as an object that's not a card the next time a player would receive priority // 603.3. Once an ability has triggered, its controller puts it on the stack as an object that's not a card the next time a player would receive priority
checkStateAndTriggered(); checkStateAndTriggered();
@ -1270,7 +1304,7 @@ public abstract class GameImpl implements Game, Serializable {
resetLKI(); resetLKI();
} }
saveState(false); saveState(false);
if (isPaused() || gameOver(null)) { if (isPaused() || checkIfGameIsOver()) {
return; return;
} }
// resetPassed should be called if player performs any action // resetPassed should be called if player performs any action
@ -1289,13 +1323,14 @@ public abstract class GameImpl implements Game, Serializable {
} }
resetShortLivingLKI(); resetShortLivingLKI();
resuming = false; resuming = false;
if (isPaused() || gameOver(null)) { if (isPaused() || checkIfGameIsOver()) {
return; return;
} }
if (allPassed()) { if (allPassed()) {
if (!state.getStack().isEmpty()) { if (!state.getStack().isEmpty()) {
//20091005 - 115.4 //20091005 - 115.4
resolve(); resolve();
checkConcede();
applyEffects(); applyEffects();
state.getPlayers().resetPassed(); state.getPlayers().resetPassed();
fireUpdatePlayersEvent(); fireUpdatePlayersEvent();
@ -1609,11 +1644,11 @@ public abstract class GameImpl implements Game, Serializable {
public boolean checkStateAndTriggered() { public boolean checkStateAndTriggered() {
boolean somethingHappened = false; boolean somethingHappened = false;
//20091005 - 115.5 //20091005 - 115.5
while (!isPaused() && !gameOver(null)) { while (!isPaused() && !checkIfGameIsOver()) {
if (!checkStateBasedActions()) { if (!checkStateBasedActions()) {
// nothing happened so check triggers // nothing happened so check triggers
state.handleSimultaneousEvent(this); state.handleSimultaneousEvent(this);
if (isPaused() || gameOver(null) || getTurn().isEndTurnRequested() || !checkTriggered()) { if (isPaused() || checkIfGameIsOver() || getTurn().isEndTurnRequested() || !checkTriggered()) {
break; break;
} }
} }
@ -1621,6 +1656,7 @@ public abstract class GameImpl implements Game, Serializable {
applyEffects(); // needed e.g if boost effects end and cause creatures to die applyEffects(); // needed e.g if boost effects end and cause creatures to die
somethingHappened = true; somethingHappened = true;
} }
checkConcede();
return somethingHappened; return somethingHappened;
} }
@ -1734,7 +1770,6 @@ public abstract class GameImpl implements Game, Serializable {
} }
} }
List<Permanent> planeswalkers = new ArrayList<>();
List<Permanent> legendary = new ArrayList<>(); List<Permanent> legendary = new ArrayList<>();
List<Permanent> worldEnchantment = new ArrayList<>(); List<Permanent> worldEnchantment = new ArrayList<>();
for (Permanent perm : getBattlefield().getAllActivePermanents()) { for (Permanent perm : getBattlefield().getAllActivePermanents()) {
@ -1781,7 +1816,6 @@ public abstract class GameImpl implements Game, Serializable {
continue; continue;
} }
} }
planeswalkers.add(perm);
} }
if (perm.isWorld()) { if (perm.isWorld()) {
worldEnchantment.add(perm); worldEnchantment.add(perm);
@ -2288,7 +2322,6 @@ public abstract class GameImpl implements Game, Serializable {
* @param playerId * @param playerId
*/ */
protected void leave(UUID playerId) { // needs to be executed from the game thread, not from the concede thread of conceding player! protected void leave(UUID playerId) { // needs to be executed from the game thread, not from the concede thread of conceding player!
Player player = getPlayer(playerId); Player player = getPlayer(playerId);
if (player == null || player.hasLeft()) { if (player == null || player.hasLeft()) {
logger.debug("Player already left " + (player != null ? player.getName() : playerId)); logger.debug("Player already left " + (player != null ? player.getName() : playerId));

View file

@ -264,7 +264,7 @@ public class Combat implements Serializable, Copyable<Combat> {
player.selectAttackers(game, attackingPlayerId); player.selectAttackers(game, attackingPlayerId);
} }
firstTime = false; firstTime = false;
if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
return; return;
} }
// because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!! // because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!!
@ -461,7 +461,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
while (choose) { while (choose) {
controller.selectBlockers(game, defenderId); controller.selectBlockers(game, defenderId);
if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
return; return;
} }
if (!game.getCombat().checkBlockRestrictions(defender, game)) { if (!game.getCombat().checkBlockRestrictions(defender, game)) {

View file

@ -95,7 +95,7 @@ public abstract class Phase implements Serializable {
} }
public boolean play(Game game, UUID activePlayerId) { public boolean play(Game game, UUID activePlayerId) {
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
@ -104,7 +104,7 @@ public abstract class Phase implements Serializable {
if (beginPhase(game, activePlayerId)) { if (beginPhase(game, activePlayerId)) {
for (Step step : steps) { for (Step step : steps) {
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
if (game.getTurn().isEndTurnRequested() && step.getType()!=PhaseStep.CLEANUP) { if (game.getTurn().isEndTurnRequested() && step.getType()!=PhaseStep.CLEANUP) {
@ -122,7 +122,7 @@ public abstract class Phase implements Serializable {
} }
} }
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
count++; count++;
@ -143,7 +143,7 @@ public abstract class Phase implements Serializable {
} }
public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) { public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) {
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
@ -157,7 +157,7 @@ public abstract class Phase implements Serializable {
resumeStep(game, wasPaused); resumeStep(game, wasPaused);
while (it.hasNext()) { while (it.hasNext()) {
step = it.next(); step = it.next();
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
currentStep = step; currentStep = step;
@ -169,7 +169,7 @@ public abstract class Phase implements Serializable {
} }
} }
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
count++; count++;
@ -206,13 +206,13 @@ public abstract class Phase implements Serializable {
if (!currentStep.skipStep(game, activePlayerId)) { if (!currentStep.skipStep(game, activePlayerId)) {
game.getState().increaseStepNum(); game.getState().increaseStepNum();
prePriority(game, activePlayerId); prePriority(game, activePlayerId);
if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) { if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
currentStep.priority(game, activePlayerId, false); currentStep.priority(game, activePlayerId, false);
if (game.executingRollback()) { if (game.executingRollback()) {
return; return;
} }
} }
if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) { if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
postPriority(game, activePlayerId); postPriority(game, activePlayerId);
} }
} }
@ -233,11 +233,11 @@ public abstract class Phase implements Serializable {
prePriority(game, activePlayerId); prePriority(game, activePlayerId);
} }
case PRIORITY: case PRIORITY:
if (!game.isPaused() && !game.gameOver(null)) { if (!game.isPaused() && !game.checkIfGameIsOver()) {
currentStep.priority(game, activePlayerId, resuming); currentStep.priority(game, activePlayerId, resuming);
} }
case POST: case POST:
if (!game.isPaused() && !game.gameOver(null)) { if (!game.isPaused() && !game.checkIfGameIsOver()) {
postPriority(game, activePlayerId); postPriority(game, activePlayerId);
} }
} }

View file

@ -127,7 +127,7 @@ public class Turn implements Serializable {
public boolean play(Game game, Player activePlayer) { public boolean play(Game game, Player activePlayer) {
activePlayer.becomesActivePlayer(); activePlayer.becomesActivePlayer();
this.setDeclareAttackersStepStarted(false); this.setDeclareAttackersStepStarted(false);
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
@ -143,7 +143,7 @@ public class Turn implements Serializable {
resetCounts(); resetCounts();
game.getPlayer(activePlayer.getId()).beginTurn(game); game.getPlayer(activePlayer.getId()).beginTurn(game);
for (Phase phase : phases) { for (Phase phase : phases) {
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return false; return false;
} }
if (!isEndTurnRequested() || phase.getType() == TurnPhase.END) { if (!isEndTurnRequested() || phase.getType() == TurnPhase.END) {
@ -189,7 +189,7 @@ public class Turn implements Serializable {
} }
while (it.hasNext()) { while (it.hasNext()) {
phase = it.next(); phase = it.next();
if (game.isPaused() || game.gameOver(null)) { if (game.isPaused() || game.checkIfGameIsOver()) {
return; return;
} }
currentPhase = phase; currentPhase = phase;

View file

@ -444,6 +444,8 @@ public interface Player extends MageItem, Copyable<Player> {
void abortReset(); void abortReset();
void signalPlayerConcede();
void skip(); void skip();
// priority, undo, ... // priority, undo, ...

View file

@ -2039,9 +2039,9 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public void concede(Game game) { public void concede(Game game) {
game.gameOver(playerId); game.setConcedingPlayer(playerId);
lost(game); lost(game);
this.left = true; // this.left = true;
} }
@Override @Override
@ -2136,7 +2136,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// for draw - first all players that have lost have to be set to lost // for draw - first all players that have lost have to be set to lost
if (!hasLeft()) { if (!hasLeft()) {
logger.debug("Game over playerId: " + playerId); logger.debug("Game over playerId: " + playerId);
game.gameOver(playerId); game.setConcedingPlayer(playerId);
} }
} }
@ -2197,7 +2197,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.draws = true; this.draws = true;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DRAW_PLAYER, null, null, playerId)); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DRAW_PLAYER, null, null, playerId));
game.informPlayers("For " + this.getLogName() + " the game is a draw."); game.informPlayers("For " + this.getLogName() + " the game is a draw.");
game.gameOver(playerId); game.setConcedingPlayer(playerId);
} }
} }
@ -3578,6 +3578,11 @@ public abstract class PlayerImpl implements Player, Serializable {
abort = false; abort = false;
} }
@Override
public void signalPlayerConcede() {
}
@Override @Override
public boolean scry(int value, Ability source, public boolean scry(int value, Ability source,
Game game Game game