Fixing several timing issues, introducing notification of game ending being sent to client. Performing cleanup of the games.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ public class GameState {
|
||||
private Map<String, GameStateListener> _gameStateListeners = new HashMap<String, GameStateListener>();
|
||||
|
||||
private String _winnerPlayerId;
|
||||
private Set<String> _losers = new HashSet<String>();
|
||||
private String _winReason;
|
||||
private Map<String, String> _losers = new HashMap<String, String>();
|
||||
|
||||
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<String> allPlayers = new LinkedList<String>(_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<Token, Integer> tokenIntegerEntry : physicalCardMapEntry.getValue().entrySet())
|
||||
listener.addTokens(card, tokenIntegerEntry.getKey(), tokenIntegerEntry.getValue());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> 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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> losers = new HashSet<String>(_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<String> losers = new HashSet<String>(_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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GameEvent> _events = new LinkedList<GameEvent>();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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<String, GatheringParticipantCommunicationChannel> _communicationChannels = new HashMap<String, GatheringParticipantCommunicationChannel>();
|
||||
@@ -22,21 +25,19 @@ public class LotroGameMediator {
|
||||
private Map<String, Long> _decisionQuerySentTimes = new HashMap<String, Long>();
|
||||
|
||||
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<String> participantSet = new LinkedList<String>();
|
||||
Map<String, LotroDeck> decks = new HashMap<String, LotroDeck>();
|
||||
_communicationChannels = new HashMap<String, GatheringParticipantCommunicationChannel>();
|
||||
|
||||
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<String, Long> 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<String, Long> 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<String, Integer> 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<String, Integer> secondsLeft = new HashMap<String, Integer>();
|
||||
for (Map.Entry<String, Integer> playerClock : _playerClocks.entrySet()) {
|
||||
String player = playerClock.getKey();
|
||||
secondsLeft.put(player, _maxSecondsForGamePerPlayer - playerClock.getValue() - getCurrentUserPendingTime(player));
|
||||
Map<String, Integer> secondsLeft = new HashMap<String, Integer>();
|
||||
for (Map.Entry<String, Integer> 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) {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class LotroServer {
|
||||
synchronized (_finishedGamesTime) {
|
||||
LinkedHashMap<String, Date> copy = new LinkedHashMap<String, Date>(_finishedGamesTime);
|
||||
for (Map.Entry<String, Date> 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<String> 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);
|
||||
}
|
||||
|
||||
@@ -509,6 +509,8 @@ public class ServerResource {
|
||||
}
|
||||
eventElem.setAttribute("opposingCardIds", sb.toString());
|
||||
}
|
||||
if (gameEvent.getMessage() != null)
|
||||
eventElem.setAttribute("message", gameEvent.getMessage());
|
||||
|
||||
return eventElem;
|
||||
}
|
||||
|
||||
@@ -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("<b>" + message + "</b>");
|
||||
},
|
||||
|
||||
startSkirmish: function(element) {
|
||||
var cardId = element.getAttribute("cardId");
|
||||
var opposingCardIds = element.getAttribute("opposingCardIds").split(",");
|
||||
|
||||
Reference in New Issue
Block a user