From ad2dff3eb9f3f2145db2b09be101dc68e5c9ed76 Mon Sep 17 00:00:00 2001 From: Yannik Bretschneider Date: Mon, 7 Jun 2021 16:44:54 +0200 Subject: [PATCH] feat: properly used timeouts and timers in Lobby --- .../marvelous/server/lobby/Lobby.java | 120 +++++++++++++----- 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java index d8e5917..551b25a 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java @@ -26,7 +26,9 @@ public class Lobby { private Participant activePlayer; private int badRequests; private PauseSegment pauseSegment; - private final TurnTimer turnTimer; + private final TurnTimeoutTimer turnTimeoutTimer; + private final TimeoutTimer timeoutTimer; + private final LifetimeTimer lifetimeTimer; /** * The {@link Lobby} is where the magic happens. In this class is a whole {@link GameInstance game} is processed. To @@ -61,20 +63,28 @@ public class Lobby { var reqSegment = new RequestGameStateSegment(this.game); this.pauseSegment = new PauseSegment(); var disconnectSegment = new DisconnectSegment(this); - var gameStateSegment = new GameStateSegment(this.game); + var gameStateSegment = new GameLogicSegment(this.game); pipeline.addSegment(reqSegment) .addSegment(pauseSegment) .addSegment(disconnectSegment) .addSegment(gameStateSegment); - this.turnTimer = new TurnTimer( - partyConfig.maxRoundTime, - this::turnTimeout); + Logger.trace("Instantiating timers..."); + + this.turnTimeoutTimer = new TurnTimeoutTimer(partyConfig.maxRoundTime, this::turnTimeout); + updateTurnTimer(); + + this.timeoutTimer = new TimeoutTimer(partyConfig.maxResponseTime, this::soonTimeout, this::timeout); + refreshTimeoutTimer(connection.getPlayer1()); + refreshTimeoutTimer(connection.getPlayer2()); + + this.lifetimeTimer = new LifetimeTimer( + partyConfig.maxGameTime, + () -> triggerWin(getParticipantForEntityType(game.state.getCurrentOvertimeWinner()).get())); this.connection.broadcastEvents(this.game.startGame(player1Characters, player2Characters)); - updateTimer(); } /** @@ -88,6 +98,9 @@ public class Lobby { requests.length, source.id.getName(), source.type); + + refreshTimeoutTimer(source); + if (activePlayer != source && source.type != ParticipantType.Spectator) { Logger.trace("Resetting bad requests as new participant sent data"); activePlayer = source; @@ -108,13 +121,23 @@ public class Lobby { Logger.debug("Rejecting requests from participant '{}'", source.id.getName()); reject(source); } else { - accept(source, resultingEvents.get()); + if (game.state.isWon()) { // If game was won in the current turn + Logger.info("Game is won, terminating lobby"); + var events = resultingEvents.get(); + events.add(new EventBuilder(EventType.DisconnectEvent).buildGameEvent()); + accept(source, events); + connection.terminate(); + return; + } else { // If not (normally) + accept(source, resultingEvents.get()); + } } - updateTimer(); + updateTurnTimer(); } /** * Called by {@link LobbyConnection} when a client disconnects + * * @param source the player disconnecting */ public synchronized void handleDisconnect(Participant source) { @@ -123,6 +146,7 @@ public class Lobby { /** * Called by {@link LobbyConnection} when a client reconnects + * * @param source the player reconnecting */ public synchronized void handleReconnect(Participant source) { @@ -133,23 +157,51 @@ public class Lobby { * This method is called at the end of receiveRequests, to start a timer. The active player has now a specific * amount of time to do his moves. */ - void updateTimer() { - var currentActiveCharacterType = game.state.getActiveCharacter().type; - Logger.trace("Updating turnTimer with current entityType {}", currentActiveCharacterType); + void updateTurnTimer() { + var currentlyActiveParticipant = + getParticipantForEntityType(game.state.getActiveCharacter().type); + Logger.trace("Updating turnTimer..."); if (pauseSegment.isPaused()) { Logger.trace("Game is paused, clearing turnTimer"); - turnTimer.clear(); - } else if (currentActiveCharacterType == EntityType.P1) { + turnTimeoutTimer.clear(); + + } else if (currentlyActiveParticipant.isPresent()) { + var participant = currentlyActiveParticipant.get(); Logger.trace("Scheduling turnTimer for Player1"); - turnTimer.startTurnTimer(connection.getPlayer1()); - } else if (currentActiveCharacterType == EntityType.P2) { - Logger.trace("Scheduling turnTimer for Player2"); - turnTimer.startTurnTimer(connection.getPlayer2()); + turnTimeoutTimer.startTurnTimer(participant); + } else { - turnTimer.clear(); + Logger.trace("Currently active participant was NPC, clearing TurnTimer"); + turnTimeoutTimer.clear(); } } + /** + * Returns an {@link Optional} of the {@link Participant} for the given {@link EntityType}, or an empty {@link + * Optional} if it was an NPC + */ + Optional getParticipantForEntityType(EntityType type) { + if (type == EntityType.P1) { + return Optional.of(connection.getPlayer1()); + } else if (type == EntityType.P2) { + Logger.trace("Scheduling turnTimer for Player2"); + return Optional.of(connection.getPlayer2()); + } else { + return Optional.empty(); + } + } + + /** Method meant for updating a TurnTimer. Said TurnTimer will be refreshed with the given participant. */ + void refreshTimeoutTimer(Participant participant) { + if (participant.type == ParticipantType.Spectator) { + Logger.trace("Tried to refresh timeoutTimer for Spectator, ignoring..."); + } else { + Logger.debug("Refreshing timeoutTimer for Participant '{}'", participant.id.getName()); + timeoutTimer.refreshTimer(participant); + } + + } + private void accept(Participant source, List accepted) { Logger.debug("Accepting requests from participant '{}', broadcasting events to all except source", source.id.getName()); @@ -173,9 +225,9 @@ public class Lobby { if (badRequests >= 5) { connection.removeParticipant(source); if (connection.hasPlayer1()) { - generateWin(connection.getPlayer1()); + triggerWin(connection.getPlayer1()); } else if (connection.hasPlayer2()) { - generateWin(connection.getPlayer2()); + triggerWin(connection.getPlayer2()); } } } @@ -185,10 +237,12 @@ public class Lobby { * * @param source soon to be timeouted player */ - public synchronized void soonTimeout(Participant source) { + public synchronized void soonTimeout(Participant source, int timeLeft) { connection.sendEvents( source, - new EventBuilder(EventType.TimeoutWarningEvent).buildGameStateEvent()); + new EventBuilder(EventType.TimeoutWarningEvent) + .withTimeLeft(timeLeft) + .buildGameEvent()); } /** @@ -199,33 +253,33 @@ public class Lobby { public synchronized void timeout(Participant source) { connection.sendEvents( source, - new EventBuilder(EventType.TimeoutEvent).buildGameStateEvent()); + new EventBuilder(EventType.TimeoutEvent).buildGameEvent()); connection.removeParticipant(source); if (connection.hasPlayer1() && !connection.hasPlayer2()) { - generateWin(connection.getPlayer1()); + triggerWin(connection.getPlayer1()); } else if (!connection.hasPlayer1() && connection.hasPlayer2()) { - generateWin(connection.getPlayer2()); + triggerWin(connection.getPlayer2()); } else { throw new IllegalStateException("Spectator was time-outed which is impossible"); } } - /** Skips the current turn, and starts a new one. Exclusively called in the {@link TurnTimer}. */ + /** Skips the current turn, and starts a new one. Exclusively called in the {@link TurnTimeoutTimer}. */ private synchronized void turnTimeout(Participant source) { var nextTurnEvents = game.endTurn(); nextTurnEvents.add(game.getGameStateEvent()); connection.broadcastEvents(nextTurnEvents.toArray(new Event[0])); - updateTimer(); + updateTurnTimer(); } /** - * The method generates a Win event for a {@link Participant}. Afterwards it sends a Disconnect to everyone, and - * terminates the connection. + * The method triggers a winEvent for a {@link Participant}, and broadcasts said event. Afterwards it sends a + * DisconnectRequest to everyone, and terminates the connection. * * @param winner is the {@link Participant} that won */ - public synchronized void generateWin(Participant winner) { + public synchronized void triggerWin(Participant winner) { connection.broadcastEvents( new EventBuilder(EventType.WinEvent) .withPlayerWon(winner.type.equals(ParticipantType.PlayerOne) ? 1 : 2) @@ -266,12 +320,12 @@ public class Lobby { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Lobby lobby = (Lobby) o; - return badRequests == lobby.badRequests && Objects.equals(gameID, lobby.gameID) && Objects.equals(game, lobby.game) && Objects.equals(pipeline, lobby.pipeline) && Objects.equals(activePlayer, lobby.activePlayer) && Objects.equals(pauseSegment, lobby.pauseSegment) && Objects.equals(turnTimer, lobby.turnTimer); + return badRequests == lobby.badRequests && Objects.equals(gameID, lobby.gameID) && Objects.equals(game, lobby.game) && Objects.equals(pipeline, lobby.pipeline) && Objects.equals(activePlayer, lobby.activePlayer) && Objects.equals(pauseSegment, lobby.pauseSegment) && Objects.equals(turnTimeoutTimer, lobby.turnTimeoutTimer); } @Override public int hashCode() { - return Objects.hash(gameID, game, pipeline, activePlayer, badRequests, pauseSegment, turnTimer); + return Objects.hash(gameID, game, pipeline, activePlayer, badRequests, pauseSegment, turnTimeoutTimer); } @Override @@ -283,7 +337,7 @@ public class Lobby { ", activePlayer=" + activePlayer + ", badRequests=" + badRequests + ", pauseSegment=" + pauseSegment + - ", turnTimer=" + turnTimer + + ", turnTimer=" + turnTimeoutTimer + '}'; } }