diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java index 8b4320c89..ab2a9aaf1 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java @@ -34,4 +34,6 @@ public interface GameStateListener { public void addTokens(PhysicalCard card, Token token, int count); public void removeTokens(PhysicalCard card, Token token, int count); + + public void sendMessage(String message); } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java index 2266a7a77..41b9b6e32 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java @@ -40,7 +40,8 @@ public class GameState { private Map _gameStateListeners = new HashMap(); private String _winnerPlayerId; - private Set _losers = new HashSet(); + private String _winReason; + private Map _losers = new HashMap(); private int _nextCardId = 0; @@ -64,16 +65,22 @@ public class GameState { } } - public void setWinnerPlayerId(String winnerPlayerId) { + public void setWinnerPlayerId(String winnerPlayerId, String reason) { _winnerPlayerId = winnerPlayerId; + _winReason = reason; + for (GameStateListener listener : getAllGameStateListeners()) + listener.sendMessage(winnerPlayerId + " is the winner due to: " + reason); } - public String setLoserPlayerId(String loserPlayerId) { - _losers.add(loserPlayerId); + public String setLoserPlayerId(String loserPlayerId, String reason) { + _losers.put(loserPlayerId, reason); + for (GameStateListener listener : getAllGameStateListeners()) + listener.sendMessage(loserPlayerId + " lost due to: " + reason); + if (_losers.size() + 1 == _playerOrder.getAllPlayers().size()) { List allPlayers = new LinkedList(_playerOrder.getAllPlayers()); - allPlayers.removeAll(_losers); - _winnerPlayerId = allPlayers.get(0); + allPlayers.removeAll(_losers.keySet()); + setWinnerPlayerId(allPlayers.get(0), "Last remaining player in game"); return allPlayers.get(0); } return null; @@ -167,6 +174,12 @@ public class GameState { for (Map.Entry tokenIntegerEntry : physicalCardMapEntry.getValue().entrySet()) listener.addTokens(card, tokenIntegerEntry.getKey(), tokenIntegerEntry.getValue()); } + + for (Map.Entry playerLoseReason : _losers.entrySet()) + listener.sendMessage(playerLoseReason.getKey() + " lost due to: " + playerLoseReason.getValue()); + + if (_winnerPlayerId != null) + listener.sendMessage(_winnerPlayerId + " is the winner due to: " + _winReason); } private boolean isZonePublic(Zone zone) { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/LotroGame.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/LotroGame.java index 1ae0329b8..4ae76d398 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/LotroGame.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/LotroGame.java @@ -18,7 +18,7 @@ public interface LotroGame { public void checkWinLoseConditions(); - public void playerWon(String currentPlayerId); + public void playerWon(String currentPlayerId, String reason); - public void playerLost(String currentPlayerId); + public void playerLost(String currentPlayerId, String reason); } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/CorruptRingBearerEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/CorruptRingBearerEffect.java index 491481ad4..34ae46aa2 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/CorruptRingBearerEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/CorruptRingBearerEffect.java @@ -6,6 +6,6 @@ import com.gempukku.lotro.logic.timing.UnrespondableEffect; public class CorruptRingBearerEffect extends UnrespondableEffect { @Override public void doPlayEffect(LotroGame game) { - game.getGameState().setLoserPlayerId(game.getGameState().getCurrentPlayerId()); + game.getGameState().setLoserPlayerId(game.getGameState().getCurrentPlayerId(), "The Ring-Bearer is corrupted by a card effect"); } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java index 114aa727e..d784c58ec 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java @@ -76,8 +76,8 @@ public class DefaultLotroGame implements LotroGame { } @Override - public void playerWon(String currentPlayerId) { - _gameState.setWinnerPlayerId(currentPlayerId); + public void playerWon(String currentPlayerId, String reason) { + _gameState.setWinnerPlayerId(currentPlayerId, reason); if (_gameResultListener != null) { Set losers = new HashSet(_gameState.getPlayerOrder().getAllPlayers()); losers.remove(currentPlayerId); @@ -86,8 +86,8 @@ public class DefaultLotroGame implements LotroGame { } @Override - public void playerLost(String currentPlayerId) { - String winner = _gameState.setLoserPlayerId(currentPlayerId); + public void playerLost(String currentPlayerId, String reason) { + String winner = _gameState.setLoserPlayerId(currentPlayerId, reason); if (winner != null && _gameResultListener != null) { Set losers = new HashSet(_gameState.getPlayerOrder().getAllPlayers()); losers.remove(winner); @@ -126,18 +126,18 @@ public class DefaultLotroGame implements LotroGame { if (gameState != null && gameState.getCurrentPhase() != Phase.GAME_SETUP) { // Ring-bearer death if (!Filters.canSpot(gameState, getModifiersQuerying(), Filters.keyword(Keyword.RING_BEARER))) { - playerLost(getGameState().getCurrentPlayerId()); + playerLost(getGameState().getCurrentPlayerId(), "The Ring-Bearer is dead"); } // Ring-bearer corruption PhysicalCard ringBearer = Filters.findFirstActive(getGameState(), getModifiersQuerying(), Filters.keyword(Keyword.RING_BEARER)); int ringBearerResistance = ringBearer.getBlueprint().getResistance(); if (getGameState().getBurdens() >= ringBearerResistance) { - playerLost(getGameState().getCurrentPlayerId()); + playerLost(getGameState().getCurrentPlayerId(), "The Ring-Bearer is corrupted"); } // Fellowship in regroup at the last site if (getGameState().getCurrentPhase() == Phase.REGROUP && getGameState().getCurrentSiteNumber() == 9) { - playerWon(getGameState().getCurrentPlayerId()); + playerWon(getGameState().getCurrentPlayerId(), "Surviving to Regroup phase on site 9"); } } } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/GatheringParticipantCommunicationChannel.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/GatheringParticipantCommunicationChannel.java index 055ddfd05..6a3300c4e 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/GatheringParticipantCommunicationChannel.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/GatheringParticipantCommunicationChannel.java @@ -1,5 +1,6 @@ package com.gempukku.lotro; +import static com.gempukku.lotro.GameEvent.Type.*; import com.gempukku.lotro.common.Phase; import com.gempukku.lotro.common.Token; import com.gempukku.lotro.communication.GameStateListener; @@ -9,8 +10,6 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; -import static com.gempukku.lotro.GameEvent.Type.*; - public class GatheringParticipantCommunicationChannel implements GameStateListener { private List _events = new LinkedList(); private String _self; @@ -100,6 +99,7 @@ public class GatheringParticipantCommunicationChannel implements GameStateListen _events.add(new GameEvent(REMOVE_TOKENS).card(card).token(token).count(count)); } + @Override public void sendMessage(String message) { _events.add(new GameEvent(MESSAGE).message(message)); } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroGameMediator.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroGameMediator.java index 9b1f3fd26..466c64c7b 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroGameMediator.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroGameMediator.java @@ -12,7 +12,10 @@ import com.gempukku.lotro.logic.timing.DefaultLotroGame; import com.gempukku.lotro.logic.timing.GameResultListener; import com.gempukku.lotro.logic.vo.LotroDeck; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; public class LotroGameMediator { private Map _communicationChannels = new HashMap(); @@ -22,21 +25,19 @@ public class LotroGameMediator { private Map _decisionQuerySentTimes = new HashMap(); private final int _maxSecondsForGamePerPlayer = 60 * 30; // 30 minutes - private final int _channelInactivityTimeoutPeriod = 30; // 30 seconds - private final int _playerDecisionTimeoutPeriod = 60 * 5; // 5 minutes + private final int _channelInactivityTimeoutPeriod = 1000 * 30; // 30 seconds + private final int _playerDecisionTimeoutPeriod = 1000 * 60 * 5; // 5 minutes public LotroGameMediator(LotroFormat lotroFormat, LotroGameParticipant[] participants, LotroCardBlueprintLibrary library, GameResultListener gameResultListener) { if (participants.length < 1) throw new IllegalArgumentException("Game can't have less than one participant"); - List participantSet = new LinkedList(); Map decks = new HashMap(); _communicationChannels = new HashMap(); for (LotroGameParticipant participant : participants) { String participantId = participant.getPlayerId(); - participantSet.add(participantId); decks.put(participantId, participant.getDeck()); _playerClocks.put(participantId, 0); } @@ -88,16 +89,25 @@ public class LotroGameMediator { // Channel is stale (user no longer connected to game, to save memory, we remove the channel // User can always reconnect and establish a new channel GatheringParticipantCommunicationChannel channel = playerChannels.getValue(); - _lotroGame.removeGameStateListener(playerId, channel); - if (currentTime > channel.getLastConsumed().getTime() + _channelInactivityTimeoutPeriod) + if (currentTime > channel.getLastConsumed().getTime() + _channelInactivityTimeoutPeriod) { + _lotroGame.removeGameStateListener(playerId, channel); _communicationChannels.remove(playerId); + } } - for (Map.Entry playerDecision : _decisionQuerySentTimes.entrySet()) { - String playerId = playerDecision.getKey(); - long decisionSent = playerDecision.getValue(); - if (currentTime > decisionSent + _playerDecisionTimeoutPeriod) - _lotroGame.playerLost(playerId); + if (_lotroGame.getGameState() != null && _lotroGame.getGameState().getWinnerPlayerId() == null) { + for (Map.Entry playerDecision : _decisionQuerySentTimes.entrySet()) { + String playerId = playerDecision.getKey(); + long decisionSent = playerDecision.getValue(); + if (currentTime > decisionSent + _playerDecisionTimeoutPeriod) + _lotroGame.playerLost(playerId, "Player decision timed-out"); + } + } + + for (Map.Entry playerClock : _playerClocks.entrySet()) { + String player = playerClock.getKey(); + if (_maxSecondsForGamePerPlayer - playerClock.getValue() - getCurrentUserPendingTime(player) < 0) + _lotroGame.playerLost(player, "Player run out of time"); } } @@ -126,22 +136,25 @@ public class LotroGameMediator { public synchronized void processCommunicationChannel(String participantId, ParticipantCommunicationVisitor visitor) { GatheringParticipantCommunicationChannel communicationChannel = _communicationChannels.get(participantId); - if (communicationChannel != null) + if (communicationChannel != null) { for (GameEvent gameEvent : communicationChannel.consumeGameEvents()) visitor.visitGameEvent(gameEvent); - String warning = _userFeedback.consumeWarning(participantId); - if (warning != null) - visitor.visitWarning(warning); - AwaitingDecision awaitingDecision = _userFeedback.getAwaitingDecision(participantId); - if (awaitingDecision != null) - visitor.visitAwaitingDecision(_lotroGame.getActionStack().getTopmostAction(), awaitingDecision); + String warning = _userFeedback.consumeWarning(participantId); + if (warning != null) + visitor.visitWarning(warning); + AwaitingDecision awaitingDecision = _userFeedback.getAwaitingDecision(participantId); + if (awaitingDecision != null) + visitor.visitAwaitingDecision(_lotroGame.getActionStack().getTopmostAction(), awaitingDecision); - Map secondsLeft = new HashMap(); - for (Map.Entry playerClock : _playerClocks.entrySet()) { - String player = playerClock.getKey(); - secondsLeft.put(player, _maxSecondsForGamePerPlayer - playerClock.getValue() - getCurrentUserPendingTime(player)); + Map secondsLeft = new HashMap(); + for (Map.Entry playerClock : _playerClocks.entrySet()) { + String player = playerClock.getKey(); + secondsLeft.put(player, _maxSecondsForGamePerPlayer - playerClock.getValue() - getCurrentUserPendingTime(player)); + } + visitor.visitClock(secondsLeft); + } else { + visitor.visitWarning("Your browser was inactive for too long, please refresh your browser window to continue playing"); } - visitor.visitClock(secondsLeft); } public synchronized void singupUserForGame(String participantId, ParticipantCommunicationVisitor visitor) { diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroServer.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroServer.java index 309628803..2d23451f8 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroServer.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/LotroServer.java @@ -92,7 +92,7 @@ public class LotroServer { synchronized (_finishedGamesTime) { LinkedHashMap copy = new LinkedHashMap(_finishedGamesTime); for (Map.Entry finishedGame : copy.entrySet()) { - if (finishedGame.getValue().getTime() > currentTime + _timeToGameDeath) { + if (currentTime > finishedGame.getValue().getTime() + _timeToGameDeath) { String gameId = finishedGame.getKey(); log.debug("Removing stale game: " + gameId); _runningGames.remove(gameId); @@ -123,6 +123,7 @@ public class LotroServer { new GameResultListener() { @Override public void gameFinished(String winnerPlayerId, Set loserPlayerIds) { + log.debug("Game finished, winner is: " + winnerPlayerId); synchronized (_finishedGamesTime) { _finishedGamesTime.put(gameId, new Date()); } @@ -193,7 +194,7 @@ public class LotroServer { while (_running) { cleanup(); try { - Thread.sleep(100); + Thread.sleep(1000); } catch (InterruptedException e) { log.error("Cleaning task interrupted", e); } diff --git a/gemp-lotr/gemp-lotr-web/src/main/java/com/gempukku/lotro/server/ServerResource.java b/gemp-lotr/gemp-lotr-web/src/main/java/com/gempukku/lotro/server/ServerResource.java index 60270b39d..7d6de9171 100644 --- a/gemp-lotr/gemp-lotr-web/src/main/java/com/gempukku/lotro/server/ServerResource.java +++ b/gemp-lotr/gemp-lotr-web/src/main/java/com/gempukku/lotro/server/ServerResource.java @@ -509,6 +509,8 @@ public class ServerResource { } eventElem.setAttribute("opposingCardIds", sb.toString()); } + if (gameEvent.getMessage() != null) + eventElem.setAttribute("message", gameEvent.getMessage()); return eventElem; } diff --git a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gameUi.js b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gameUi.js index 3599f6a3c..cb2295dcd 100644 --- a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gameUi.js +++ b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gameUi.js @@ -332,6 +332,8 @@ var GempLotrGameUI = Class.extend({ this.addTokens(gameEvent); } else if (eventType == "REMOVE_TOKENS") { this.removeTokens(gameEvent); + } else if (eventType == "MESSAGE") { + this.message(gameEvent); } } if (gameEvents.length > 0) @@ -393,6 +395,12 @@ var GempLotrGameUI = Class.extend({ cardData.tokens[token] -= count; }, + message: function(element) { + var message = element.getAttribute("message"); + if (this.chatBox != null) + this.chatBox.appendMessage("" + message + ""); + }, + startSkirmish: function(element) { var cardId = element.getAttribute("cardId"); var opposingCardIds = element.getAttribute("opposingCardIds").split(",");