package games.strategy.triplea.delegate; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.swing.JOptionPane; import games.strategy.engine.data.CompositeChange; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.PlayerList; import games.strategy.engine.data.Territory; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.engine.framework.headlessGameServer.HeadlessGameServer; import games.strategy.engine.message.IRemote; import games.strategy.sound.SoundPath; import games.strategy.triplea.Constants; import games.strategy.triplea.MapSupport; import games.strategy.triplea.attachments.AbstractTriggerAttachment; import games.strategy.triplea.attachments.ICondition; import games.strategy.triplea.attachments.PlayerAttachment; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.attachments.TriggerAttachment; import games.strategy.triplea.formatter.MyFormatter; import games.strategy.util.CompositeMatchAnd; import games.strategy.util.CompositeMatchOr; import games.strategy.util.CountDownLatchHandler; import games.strategy.util.EventThreadJOptionPane; import games.strategy.util.LocalizeHTML; import games.strategy.util.Match; /** * A delegate used to check for end of game conditions. */ @MapSupport public class EndRoundDelegate extends BaseTripleADelegate { private boolean m_gameOver = false; private Collection<PlayerID> m_winners = new ArrayList<>(); /** Creates a new instance of EndRoundDelegate. */ public EndRoundDelegate() {} /** * Called before the delegate will run. */ @Override public void start() { super.start(); if (m_gameOver) { return; } String victoryMessage = null; final GameData data = getData(); if (isPacificTheater()) { final PlayerID japanese = data.getPlayerList().getPlayerID(Constants.PLAYER_NAME_JAPANESE); final PlayerAttachment pa = PlayerAttachment.get(japanese); if (pa != null && pa.getVps() >= 22) { victoryMessage = "Axis achieve VP victory"; m_bridge.getHistoryWriter().startEvent(victoryMessage); final Collection<PlayerID> winners = data.getAllianceTracker() .getPlayersInAlliance(data.getAllianceTracker().getAlliancesPlayerIsIn(japanese).iterator().next()); signalGameOver(victoryMessage, winners, m_bridge); } } // Check for Winning conditions if (isTotalVictory()) { // Check for Win by Victory Cities victoryMessage = " achieve TOTAL VICTORY with "; checkVictoryCities(m_bridge, victoryMessage, " Total Victory VCs"); } if (isHonorableSurrender()) { victoryMessage = " achieve an HONORABLE VICTORY with "; checkVictoryCities(m_bridge, victoryMessage, " Honorable Victory VCs"); } if (isProjectionOfPower()) { victoryMessage = " achieve victory through a PROJECTION OF POWER with "; checkVictoryCities(m_bridge, victoryMessage, " Projection of Power VCs"); } if (isEconomicVictory()) { // Check for regular economic victory final Iterator<String> allianceIter = data.getAllianceTracker().getAlliances().iterator(); String allianceName = null; while (allianceIter.hasNext()) { allianceName = allianceIter.next(); final int victoryAmount = getEconomicVictoryAmount(data, allianceName); final Set<PlayerID> teamMembers = data.getAllianceTracker().getPlayersInAlliance(allianceName); final Iterator<PlayerID> teamIter = teamMembers.iterator(); int teamProd = 0; while (teamIter.hasNext()) { final PlayerID player = teamIter.next(); teamProd += getProduction(player); if (teamProd >= victoryAmount) { victoryMessage = allianceName + " achieve economic victory"; m_bridge.getHistoryWriter().startEvent(victoryMessage); final Collection<PlayerID> winners = data.getAllianceTracker().getPlayersInAlliance(allianceName); // Added this to end the game on victory conditions signalGameOver(victoryMessage, winners, m_bridge); } } } } // now check for generic trigger based victories if (isTriggeredVictory()) { // First set up a match for what we want to have fire as a default in this delegate. List out as a composite match // OR. // use 'null, null' because this is the Default firing location for any trigger that does NOT have 'when' set. final Match<TriggerAttachment> endRoundDelegateTriggerMatch = new CompositeMatchAnd<>(AbstractTriggerAttachment.availableUses, AbstractTriggerAttachment.whenOrDefaultMatch(null, null), new CompositeMatchOr<TriggerAttachment>( TriggerAttachment.activateTriggerMatch(), TriggerAttachment.victoryMatch())); // get all possible triggers based on this match. final HashSet<TriggerAttachment> toFirePossible = TriggerAttachment.collectForAllTriggersMatching( new HashSet<>(data.getPlayerList().getPlayers()), endRoundDelegateTriggerMatch, m_bridge); if (!toFirePossible.isEmpty()) { // get all conditions possibly needed by these triggers, and then test them. final HashMap<ICondition, Boolean> testedConditions = TriggerAttachment.collectTestsForAllTriggers(toFirePossible, m_bridge); // get all triggers that are satisfied based on the tested conditions. final Set<TriggerAttachment> toFireTestedAndSatisfied = new HashSet<>( Match.getMatches(toFirePossible, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions))); // now list out individual types to fire, once for each of the matches above. TriggerAttachment.triggerActivateTriggerOther(testedConditions, toFireTestedAndSatisfied, m_bridge, null, null, true, true, true, true); // will call TriggerAttachment.triggerVictory(toFireTestedAndSatisfied, m_bridge, null, null, true, true, true, true); // signalGameOver itself } } if (isWW2V2() || isWW2V3()) { return; } final PlayerList playerList = data.getPlayerList(); // now test older maps that only use these 5 players, to see if someone has won final PlayerID russians = playerList.getPlayerID(Constants.PLAYER_NAME_RUSSIANS); final PlayerID germans = playerList.getPlayerID(Constants.PLAYER_NAME_GERMANS); final PlayerID british = playerList.getPlayerID(Constants.PLAYER_NAME_BRITISH); final PlayerID japanese = playerList.getPlayerID(Constants.PLAYER_NAME_JAPANESE); final PlayerID americans = playerList.getPlayerID(Constants.PLAYER_NAME_AMERICANS); if (germans == null || russians == null || british == null || japanese == null || americans == null || playerList.size() > 5) { return; } // Quick check to see who still owns their own capital final boolean russia = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(russians, data).getOwner().equals(russians); final boolean germany = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(germans, data).getOwner().equals(germans); final boolean britain = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(british, data).getOwner().equals(british); final boolean japan = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(japanese, data).getOwner().equals(japanese); final boolean america = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(americans, data).getOwner().equals(americans); int count = 0; if (!russia) { count++; } if (!britain) { count++; } if (!america) { count++; } victoryMessage = " achieve a military victory"; if (germany && japan && count >= 2) { m_bridge.getHistoryWriter().startEvent("Axis" + victoryMessage); final Collection<PlayerID> winners = data.getAllianceTracker().getPlayersInAlliance("Axis"); signalGameOver("Axis" + victoryMessage, winners, m_bridge); } if (russia && !germany && britain && !japan && america) { m_bridge.getHistoryWriter().startEvent("Allies" + victoryMessage); final Collection<PlayerID> winners = data.getAllianceTracker().getPlayersInAlliance("Allies"); signalGameOver("Allies" + victoryMessage, winners, m_bridge); } } @Override public void end() { super.end(); final GameData data = getData(); if (games.strategy.triplea.Properties.getTriggers(data)) { final CompositeChange change = new CompositeChange(); for (final PlayerID player : data.getPlayerList().getPlayers()) { change.add(AbstractTriggerAttachment.triggerSetUsedForThisRound(player, m_bridge)); } if (!change.isEmpty()) { m_bridge.getHistoryWriter().startEvent("Setting uses for triggers used this round."); m_bridge.addChange(change); } } } @Override public Serializable saveState() { final EndRoundExtendedDelegateState state = new EndRoundExtendedDelegateState(); state.superState = super.saveState(); state.m_gameOver = m_gameOver; state.m_winners = m_winners; return state; } @Override public void loadState(final Serializable state) { final EndRoundExtendedDelegateState s = (EndRoundExtendedDelegateState) state; super.loadState(s.superState); m_gameOver = s.m_gameOver; m_winners = s.m_winners; } @Override public boolean delegateCurrentlyRequiresUserInput() { return false; } private void checkVictoryCities(final IDelegateBridge aBridge, final String victoryMessage, final String victoryType) { final GameData data = aBridge.getData(); final Iterator<String> allianceIter = data.getAllianceTracker().getAlliances().iterator(); String allianceName = null; final Collection<Territory> territories = data.getMap().getTerritories(); while (allianceIter.hasNext()) { allianceName = allianceIter.next(); final int vcAmount = getVCAmount(data, allianceName, victoryType); final Set<PlayerID> teamMembers = data.getAllianceTracker().getPlayersInAlliance(allianceName); int teamVCs = 0; for (final Territory t : territories) { if (Matches.isTerritoryOwnedBy(teamMembers).match(t)) { final TerritoryAttachment ta = TerritoryAttachment.get(t); if (ta != null) { teamVCs += ta.getVictoryCity(); } } } if (teamVCs >= vcAmount) { aBridge.getHistoryWriter().startEvent(allianceName + victoryMessage + vcAmount + " Victory Cities!"); final Collection<PlayerID> winners = data.getAllianceTracker().getPlayersInAlliance(allianceName); // Added this to end the game on victory conditions signalGameOver(allianceName + victoryMessage + vcAmount + " Victory Cities!", winners, aBridge); } } } private int getEconomicVictoryAmount(final GameData data, final String alliance) { return data.getProperties().get(alliance + " Economic Victory", 200); } private int getVCAmount(final GameData data, final String alliance, final String type) { int defaultVC = 20; if (type.equals(" Total Victory VCs")) { defaultVC = 18; } else if (type.equals(" Honorable Victory VCs")) { defaultVC = 15; } else if (type.equals(" Projection of Power VCs")) { defaultVC = 13; } return data.getProperties().get((alliance + type), defaultVC); } /** * Notify all players that the game is over. * * @param status * the "game over" text to be displayed to each user. */ public void signalGameOver(final String status, final Collection<PlayerID> winners, final IDelegateBridge aBridge) { // TO NOT USE m_bridge, because it might be null here! use aBridge instead. // If the game is over, we need to be able to alert all UIs to that fact. // The display object can send a message to all UIs. if (!m_gameOver) { m_gameOver = true; m_winners = winners; aBridge.getSoundChannelBroadcaster().playSoundForAll(SoundPath.CLIP_GAME_WON, ((m_winners != null && !m_winners.isEmpty()) ? m_winners.iterator().next() : PlayerID.NULL_PLAYERID)); // send a message to everyone's screen except the HOST (there is no 'current player' for the end round delegate) final String title = "Victory Achieved" + (winners.isEmpty() ? "" : " by " + MyFormatter.defaultNamedToTextList(winners, ", ", false)); // we send the bridge, because we can call this method from outside this delegate, which // means our local copy of m_bridge could be null. getDisplay(aBridge).reportMessageToAll(("<html>" + status + "</html>"), title, true, false, true); final boolean stopGame; if (HeadlessGameServer.headless()) { // a terrible dirty hack, but I can't think of a better way to do it right now. If we are headless, end the // game. stopGame = true; } else { // now tell the HOST, and see if they want to continue the game. String displayMessage = LocalizeHTML.localizeImgLinksInHTML(status); if (displayMessage.endsWith("</body>")) { displayMessage = displayMessage.substring(0, displayMessage.length() - "</body>".length()) + "</br><p>Do you want to continue?</p></body>"; } else { displayMessage = displayMessage + "</br><p>Do you want to continue?</p>"; } // this is currently the ONLY instance of JOptionPane that is allowed outside of the UI classes. maybe there is // a better way? stopGame = (JOptionPane.OK_OPTION != EventThreadJOptionPane.showConfirmDialog(null, ("<html>" + displayMessage + "</html>"), "Continue Game? (" + title + ")", JOptionPane.YES_NO_OPTION, new CountDownLatchHandler(true))); } if (stopGame) { aBridge.stopGameSequence(); } } } /** * if null, the game is not over yet. */ public Collection<PlayerID> getWinners() { if (!m_gameOver) { return null; } return m_winners; } private boolean isWW2V2() { return games.strategy.triplea.Properties.getWW2V2(getData()); } private boolean isWW2V3() { return games.strategy.triplea.Properties.getWW2V3(getData()); } private boolean isPacificTheater() { return games.strategy.triplea.Properties.getPacificTheater(getData()); } private boolean isTotalVictory() { return games.strategy.triplea.Properties.getTotalVictory(getData()); } private boolean isHonorableSurrender() { return games.strategy.triplea.Properties.getHonorableSurrender(getData()); } private boolean isProjectionOfPower() { return games.strategy.triplea.Properties.getProjectionOfPower(getData()); } private boolean isEconomicVictory() { return games.strategy.triplea.Properties.getEconomicVictory(getData()); } private boolean isTriggeredVictory() { return games.strategy.triplea.Properties.getTriggeredVictory(getData()); } public int getProduction(final PlayerID id) { int sum = 0; final Iterator<Territory> territories = getData().getMap().iterator(); while (territories.hasNext()) { final Territory current = territories.next(); if (current.getOwner().equals(id)) { sum += TerritoryAttachment.getProduction(current); } } return sum; } @Override public Class<? extends IRemote> getRemoteType() { return null; } } class EndRoundExtendedDelegateState implements Serializable { private static final long serialVersionUID = 8770361633528374127L; Serializable superState; // add other variables here: public boolean m_gameOver = false; public Collection<PlayerID> m_winners = new ArrayList<>(); }