Fixing several timing issues, introducing notification of game ending being sent to client. Performing cleanup of the games.

This commit is contained in:
marcins78@gmail.com
2011-09-13 19:17:30 +00:00
parent 53f3c10bc4
commit 782194bad8
10 changed files with 83 additions and 44 deletions

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}
}

View File

@@ -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));
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -509,6 +509,8 @@ public class ServerResource {
}
eventElem.setAttribute("opposingCardIds", sb.toString());
}
if (gameEvent.getMessage() != null)
eventElem.setAttribute("message", gameEvent.getMessage());
return eventElem;
}

View File

@@ -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(",");