A slow-down to AI vs AI games (not finished yet),

Closing the window during AI vs AI match leads to a draw
This commit is contained in:
Maxmtg
2013-05-30 12:24:34 +00:00
parent 545d64d23c
commit 4052f179d4
14 changed files with 306 additions and 162 deletions

2
.gitattributes vendored
View File

@@ -14163,6 +14163,7 @@ src/main/java/forge/control/ChatArea.java -text
src/main/java/forge/control/ControlBazaarUI.java -text
src/main/java/forge/control/FControl.java -text
src/main/java/forge/control/FControlGameEventHandler.java -text
src/main/java/forge/control/FControlGamePlayback.java -text
src/main/java/forge/control/InputQueue.java svneol=native#text/plain
src/main/java/forge/control/KeyboardShortcuts.java -text
src/main/java/forge/control/Lobby.java -text
@@ -14234,6 +14235,7 @@ src/main/java/forge/game/event/GameEventDuelFinished.java -text
src/main/java/forge/game/event/GameEventDuelOutcome.java -text
src/main/java/forge/game/event/GameEventFlipCoin.java -text
src/main/java/forge/game/event/GameEventGameRestarted.java -text
src/main/java/forge/game/event/GameEventGameStarted.java -text
src/main/java/forge/game/event/GameEventLandPlayed.java -text
src/main/java/forge/game/event/GameEventLifeLoss.java -text
src/main/java/forge/game/event/GameEventManaBurn.java -text

View File

@@ -24,6 +24,7 @@ import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JLayeredPane;
@@ -60,6 +61,7 @@ import forge.gui.match.controllers.CStack;
import forge.gui.match.nonsingleton.VField;
import forge.gui.match.views.VAntes;
import forge.gui.toolbox.CardFaceSymbols;
import forge.gui.toolbox.FOverlay;
import forge.gui.toolbox.FSkin;
import forge.net.NetServer;
import forge.properties.NewConstants;
@@ -127,7 +129,7 @@ public enum FControl {
Singletons.getView().getFrame().setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
if (!FControl.this.game.isGameOver())
CDock.SINGLETON_INSTANCE.concede();
stopGame();
else {
Singletons.getControl().changeState(FControl.Screens.HOME_SCREEN);
SOverlayUtils.hideOverlay();
@@ -351,6 +353,28 @@ public enum FControl {
return game;
}
public final void stopGame() {
List<Player> pp = new ArrayList<Player>();
for(Player p : game.getPlayers()) {
if ( p.getOriginalLobbyPlayer() == getLobby().getGuiPlayer() )
pp.add(p);
}
boolean hasHuman = !pp.isEmpty();
if ( pp.isEmpty() )
pp.addAll(game.getPlayers()); // no human? then all players surrender!
for(Player p: pp)
p.concede();
boolean humanHasPriority = game.getPhaseHandler().getPriorityPlayer().getLobbyPlayer() == getLobby().getGuiPlayer();
if ( hasHuman && humanHasPriority )
game.getAction().checkGameOverCondition();
else
game.isGameOver(); // this is synchronized method - it's used to make Game-0 thread see changes made here
}
private InputQueue inputQueue;
public InputQueue getInputQueue() {
return inputQueue;
@@ -358,6 +382,7 @@ public enum FControl {
private final FControlGameEventHandler fcVisitor = new FControlGameEventHandler(this);
private final FControlGamePlayback playbackControl = new FControlGamePlayback(this);
public void attachToGame(Game game0) {
// TODO: Detach from other game we might be looking at
@@ -382,14 +407,20 @@ public enum FControl {
CMessage.SINGLETON_INSTANCE.getInputControl().setGame(game);
// models shall notify controllers of changes
// some observers were set in CMatchUI.initMatch
// Listen to DuelOutcome event to show ViewWinLose
game.subscribeToEvents(fcVisitor);
// Add playback controls to match if needed
boolean hasHuman = false;
for(Player p : game.getPlayers()) {
if ( p.getController().getLobbyPlayer() == getLobby().getGuiPlayer() )
hasHuman = true;
}
if (!hasHuman) {
game.subscribeToEvents(playbackControl);
}
VAntes.SINGLETON_INSTANCE.setModel(game.getRegisteredPlayers());

View File

@@ -26,16 +26,15 @@ import forge.gui.match.nonsingleton.VHand;
import forge.gui.match.nonsingleton.VField.PhaseLabel;
public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
public final FControl fc;
private final FControl fc;
public FControlGameEventHandler(FControl fc ) {
this.fc = fc;
}
private final AtomicBoolean phaseUpdPlanned = new AtomicBoolean(false);
@Subscribe
public void receiveGameEvent(final GameEvent ev) { ev.visit(this); }
private final AtomicBoolean phaseUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(final GameEventTurnPhase ev) {
if ( phaseUpdPlanned.getAndSet(true) ) return null;
@@ -50,12 +49,10 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
PhaseLabel lbl = matchUi.getFieldViewFor(p).getLabelFor(ph);
matchUi.resetAllPhaseButtons();
if (lbl != null) {
lbl.setActive(true);
}
if (lbl != null) lbl.setActive(true);
} });
return null;
}
@@ -99,6 +96,4 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
} });
return null;
}
}

View File

@@ -0,0 +1,100 @@
package forge.control;
import com.google.common.eventbus.Subscribe;
import forge.game.event.GameEvent;
import forge.game.event.GameEventBlockerAssigned;
import forge.game.event.GameEventGameStarted;
import forge.game.event.GameEventLandPlayed;
import forge.game.event.GameEventSpellResolved;
import forge.game.event.GameEventTurnPhase;
import forge.game.event.IGameEventVisitor;
import forge.game.player.Player;
import forge.gui.match.CMatchUI;
public class FControlGamePlayback extends IGameEventVisitor.Base<Void> {
private final FControl fc;
public FControlGamePlayback(FControl fc ) {
this.fc = fc;
}
@Subscribe
public void receiveGameEvent(final GameEvent ev) { ev.visit(this); }
private int phasesDelay = 400;
private int combatDelay = 400;
private int resolveDelay = 600;
private void pauseForEvent(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
e.printStackTrace();
}
}
@Override
public Void visit(GameEventBlockerAssigned event) {
pauseForEvent(combatDelay);
return super.visit(event);
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventTurnPhase)
*/
@Override
public Void visit(GameEventTurnPhase ev) {
boolean isUiToStop = CMatchUI.SINGLETON_INSTANCE.stopAtPhase(ev.playerTurn, ev.phase);
switch(ev.phase) {
case COMBAT_END:
case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY:
case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY:
if( fc.getObservedGame().getPhaseHandler().inCombat() )
pauseForEvent(combatDelay);
break;
default:
if( isUiToStop )
pauseForEvent(phasesDelay);
break;
}
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventGameStarted)
*/
@Override
public Void visit(GameEventGameStarted event) {
boolean hasHuman = false;
for(Player p : event.players) {
if ( p.getController().getLobbyPlayer() == fc.getLobby().getGuiPlayer() )
hasHuman = true;
}
// show input here to adjust speed if no human playing
return null;
}
@Override
public Void visit(GameEventLandPlayed event) {
pauseForEvent(resolveDelay);
return super.visit(event);
}
@Override
public Void visit(GameEventSpellResolved event) {
pauseForEvent(resolveDelay);
return null;
}
}

View File

@@ -491,7 +491,10 @@ public class Game {
public Player getNextPlayerAfter(final Player playerTurn) {
int iPlayer = roIngamePlayers.indexOf(playerTurn);
if (-1 == iPlayer && !roIngamePlayers.isEmpty()) { // if playerTurn has just lost
if (roIngamePlayers.isEmpty())
return null;
if (-1 == iPlayer) { // if playerTurn has just lost
int iAlive;
iPlayer = allPlayers.indexOf(playerTurn);
do {
@@ -507,7 +510,6 @@ public class Game {
}
return roIngamePlayers.get(iPlayer);
}
public int getPosition(Player player, Player startingPlayer) {

View File

@@ -63,6 +63,8 @@ import forge.game.event.GameEventCardDestroyed;
import forge.game.event.GameEventCardRegenerated;
import forge.game.event.GameEventCardSacrificed;
import forge.game.event.GameEventDuelFinished;
import forge.game.event.GameEventFlipCoin;
import forge.game.event.GameEventGameStarted;
import forge.game.player.GameLossReason;
import forge.game.player.HumanPlay;
import forge.game.player.Player;
@@ -72,6 +74,7 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
import forge.util.Aggregates;
import forge.util.maps.CollectionSuppliers;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
@@ -786,75 +789,6 @@ public class GameAction {
game.getStack().add(activate);
}
/**
* <p>
* checkEndGameSate.
* </p>
*
* @return a boolean.
*/
private final GameEndReason checkEndGameState(final Game game) {
GameEndReason reason = null;
// award loses as SBE
List<Player> losers = null;
for (Player p : game.getPlayers()) {
if (p.checkLoseCondition()) { // this will set appropriate outcomes
// Run triggers
if (losers == null) {
losers = new ArrayList<Player>(3);
}
losers.add(p);
}
}
// Has anyone won by spelleffect?
for (Player p : game.getPlayers()) {
if (!p.hasWon()) {
continue;
}
// then the rest have lost!
reason = GameEndReason.WinsGameSpellEffect;
for (Player pl : game.getPlayers()) {
if (pl.equals(p)) {
continue;
}
if (!pl.loseConditionMet(GameLossReason.OpponentWon, p.getOutcome().altWinSourceName)) {
reason = null; // they cannot lose!
} else {
if (losers == null) {
losers = new ArrayList<Player>(3);
}
losers.add(p);
}
}
break;
}
// need a separate loop here, otherwise ConcurrentModificationException is raised
if (losers != null) {
for (Player p : losers) {
game.onPlayerLost(p);
}
}
// still unclear why this has not caught me conceding
if (reason == null && Iterables.size(Iterables.filter(game.getPlayers(), Player.Predicates.NOT_LOST)) == 1)
{
reason = GameEndReason.AllOpponentsLost;
}
// ai's cannot finish their game without human yet - so terminate a game if human has left.
/*
if (reason == null && !Iterables.any(game.getPlayers(), Predicates.and(Player.Predicates.NOT_LOST, Player.Predicates.isType(PlayerType.HUMAN)))) {
reason = GameEndReason.AllHumansLost;
}
*/
return reason;
}
/** */
public final void checkStaticAbilities() {
FThreads.assertExecutedByEdt(false);
@@ -1082,18 +1016,81 @@ public class GameAction {
}
} // for q=0;q<2
GameEndReason endGame = this.checkEndGameState(game);
if (endGame != null) {
// Clear Simultaneous triggers at the end of the game
game.setGameOver(endGame);
game.getStack().clearSimultaneousStack();
}
checkGameOverCondition();
if (!refreeze) {
game.getStack().unfreezeStack();
}
} // checkStateEffects()
public void checkGameOverCondition() {
GameEndReason reason = this.eliminateLosingPlayers();
// still unclear why this has not caught me conceding
if (reason == null )
{
int cntNotLost = Iterables.size(Iterables.filter(game.getPlayers(), Player.Predicates.NOT_LOST));
if( cntNotLost == 1 )
reason = GameEndReason.AllOpponentsLost;
else if ( cntNotLost == 0 )
reason = GameEndReason.Draw;
else
return;
}
// Clear Simultaneous triggers at the end of the game
game.setGameOver(reason);
game.getStack().clearSimultaneousStack();
}
private GameEndReason eliminateLosingPlayers() {
// award loses as SBE
List<Player> losers = null;
for (Player p : game.getPlayers()) {
if (p.checkLoseCondition()) { // this will set appropriate outcomes
// Run triggers
if (losers == null) {
losers = new ArrayList<Player>(3);
}
losers.add(p);
}
}
GameEndReason reason = null;
// Has anyone won by spelleffect?
for (Player p : game.getPlayers()) {
if (!p.hasWon()) {
continue;
}
// then the rest have lost!
reason = GameEndReason.WinsGameSpellEffect;
for (Player pl : game.getPlayers()) {
if (pl.equals(p)) {
continue;
}
if (!pl.loseConditionMet(GameLossReason.OpponentWon, p.getOutcome().altWinSourceName)) {
reason = null; // they cannot lose!
} else {
if (losers == null) {
losers = new ArrayList<Player>(3);
}
losers.add(p);
}
}
break;
}
// need a separate loop here, otherwise ConcurrentModificationException is raised
if (losers != null) {
for (Player p : losers) {
game.onPlayerLost(p);
}
}
return reason;
}
/**
* <p>
* destroyPlaneswalkers.
@@ -1475,28 +1472,50 @@ public class GameAction {
}
}
public void startGame(final Player firstPlayer) {
Player first = firstPlayer;
private Player determineFirstTurnPlayer(final GameOutcome lastGameOutcome) {
// Only cut/coin toss if it's the first game of the match
Player goesFirst = null;
boolean isFirstGame = lastGameOutcome == null;
if (isFirstGame) {
game.fireEvent(new GameEventFlipCoin()); // Play the Flip Coin sound
goesFirst = Aggregates.random(game.getPlayers());
} else {
for(Player p : game.getPlayers()) {
if(!lastGameOutcome.isWinner(p.getLobbyPlayer())) {
goesFirst = p;
break;
}
}
}
boolean willPlay = goesFirst.getController().getWillPlayOnFirstTurn(isFirstGame);
goesFirst = willPlay ? goesFirst : goesFirst.getOpponent();
return goesFirst;
}
public void startGame() {
Player first = determineFirstTurnPlayer(game.getMatch().getLastGameOutcome());
do {
if ( game.isGameOver() ) break; // conceded during "play or draw"
// Draw <handsize> cards
for (final Player p1 : game.getPlayers()) {
p1.drawCards(p1.getMaxHandSize());
}
// FControl should determine now if there are any human players.
// Where there are none, it should bring up speed controls
game.fireEvent(new GameEventGameStarted(first, game.getPlayers()));
game.setAge(GameAge.Mulligan);
for (final Player p1 : game.getPlayers())
p1.drawCards(p1.getMaxHandSize());
performMulligans(first, game.getType() == GameType.Commander);
if ( game.isGameOver() ) break; // conceded during "mulligan" prompt
// should I restore everyting exiled by Karn here, or before Mulligans is fine?
game.setAge(GameAge.Play);
// THIS CODE WILL WORK WITH PHASE = NULL {
if(game.getType() == GameType.Planechase)
firstPlayer.initPlane();
first.initPlane();
handleLeylinesAndChancellors();
checkStateEffects();
@@ -1511,7 +1530,7 @@ public class GameAction {
first = game.getPhaseHandler().getPlayerTurn(); // needed only for restart
} while( game.getAge() == GameAge.RestartedByKarn );
// will pull UI
// will pull UI dialog, when the UI is listening
game.fireEvent(new GameEventDuelFinished());
}
@@ -1588,6 +1607,7 @@ public class GameAction {
}
}
// Invokes given runnable in Game thread pool - used to start game and perform actions from UI (when game-0 waits for input)
public void invoke(final Runnable proc) {
if( FThreads.isGameThread() ) {
proc.run();

View File

@@ -100,8 +100,7 @@ public class Match {
currentGame.getAction().invoke(new Runnable() {
@Override
public void run() {
final Player firstPlayer = determineFirstTurnPlayer(getLastGameOutcome(), currentGame);
currentGame.getAction().startGame(firstPlayer);
currentGame.getAction().startGame();
}
});
}
@@ -205,26 +204,4 @@ public class Match {
return 10;
}
private Player determineFirstTurnPlayer(final GameOutcome lastGameOutcome, final Game game) {
// Only cut/coin toss if it's the first game of the match
Player goesFirst = null;
boolean isFirstGame = lastGameOutcome == null;
if (isFirstGame) {
game.fireEvent(new GameEventFlipCoin()); // Play the Flip Coin sound
goesFirst = Aggregates.random(game.getPlayers());
} else {
for(Player p : game.getPlayers()) {
if(!lastGameOutcome.isWinner(p.getLobbyPlayer())) {
goesFirst = p;
break;
}
}
}
boolean willPlay = goesFirst.getController().getWillPlayOnFirstTurn(isFirstGame);
goesFirst = willPlay ? goesFirst : goesFirst.getOpponent();
return goesFirst;
}
}

View File

@@ -0,0 +1,26 @@
package forge.game.event;
import forge.game.player.Player;
/**
* TODO: Write javadoc for this type.
*
*/
public class GameEventGameStarted extends GameEvent {
public final Player firstTurn;
public final Iterable<Player> players;
public GameEventGameStarted(Player firstTurn, Iterable<Player> players) {
super();
this.firstTurn = firstTurn;
this.players = players;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -20,6 +20,7 @@ public interface IGameEventVisitor<T> {
T visit(GameEventDuelFinished event);
T visit(GameEventDuelOutcome event);
T visit(GameEventFlipCoin event);
T visit(GameEventGameStarted event);
T visit(GameEventGameRestarted event);
T visit(GameEventLandPlayed event);
T visit(GameEventLifeLoss event);
@@ -53,6 +54,7 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventDuelFinished event) { return null; }
public T visit(GameEventDuelOutcome event) { return null; }
public T visit(GameEventFlipCoin event) { return null; }
public T visit(GameEventGameStarted event) { return null; }
public T visit(GameEventGameRestarted event) { return null; }
public T visit(GameEventLandPlayed event) { return null; }
public T visit(GameEventLifeLoss event) { return null; }
@@ -69,7 +71,5 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventPlayerDamaged event) { return null; }
}
}

View File

@@ -714,14 +714,14 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
System.out.print(" >>\n");
}
if ( game.isGameOver() ) return; // conceded?
// actingPlayer is the player who may act
// the firstAction is the player who gained Priority First in this segment
// of Priority
Player nextPlayer = game.getNextPlayerAfter(this.getPriorityPlayer());
if ( game.isGameOver() || nextPlayer == null ) return; // conceded?
// System.out.println(String.format("%s %s: %s passes priority to %s", playerTurn, phase, actingPlayer, nextPlayer));
if (this.getFirstPriority().equals(nextPlayer)) {
if (game.getStack().isEmpty()) {

View File

@@ -2066,7 +2066,6 @@ public class Player extends GameEntity implements Comparable<Player> {
* Concede.
*/
public final void concede() { // No cantLose checks - just lose
FThreads.assertExecutedByEdt(false);
setOutcome(PlayerOutcome.concede());
}
@@ -2705,6 +2704,10 @@ public class Player extends GameEntity implements Comparable<Player> {
return getController().getLobbyPlayer();
}
public final LobbyPlayer getOriginalLobbyPlayer() {
return controllerCreator.getLobbyPlayer();
}
public final boolean isMindSlaved() {
return controller.getLobbyPlayer() != controllerCreator.getLobbyPlayer();
}

View File

@@ -102,8 +102,8 @@ public class GuiDialog {
// Play the Flip A Coin sound
caller.getGame().fireEvent(new GameEventFlipCoin());
JOptionPane.showMessageDialog(null, source.getName() + " - " + caller + winMsg, source.getName(),
JOptionPane.PLAIN_MESSAGE);
message(source.getName() + " - " + caller + winMsg, source.getName());
return winFlip;
}

View File

@@ -198,8 +198,10 @@ public class ViewWinLose {
nHumansInGame++;
}
LobbyPlayer winner = match.getLastGameOutcome().getWinner();
String title = nHumansInGame == 1 ? "You " + (winner == guiPlayer ? "won!" : "lost!") : winner.getName() + " Won!";
return title;
if ( winner == null )
return "It's a draw!";
return nHumansInGame == 1 ? "You " + (winner == guiPlayer ? "won!" : "lost!") : winner.getName() + " Won!";
}
/** @return {@link forge.gui.toolbox.FButton} */

View File

@@ -33,6 +33,7 @@ import forge.FThreads;
import forge.Singletons;
import forge.CardPredicates.Presets;
import forge.Command;
import forge.control.FControl;
import forge.deck.Deck;
import forge.game.Game;
import forge.game.phase.CombatUtil;
@@ -81,22 +82,7 @@ public enum CDock implements ICDoc {
return;
}
final Player p = findAffectedPlayer();
if( p == null ) return;
if( p.isMindSlaved() ) {
GuiDialog.message("You cannot make concede a player you temporarily control");
return;
}
game.getAction().invoke(new Runnable() {
@Override
public void run() {
p.concede();
p.getGame().getAction().checkStateEffects();
}
});
game = null; // no second entry possible;
Singletons.getControl().stopGame();
}
/**