feat: properly used timeouts and timers in Lobby
This commit is contained in:
		@ -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<Participant> 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<Event> 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 +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user