feat: properly used timeouts and timers in Lobby

This commit is contained in:
Yannik Bretschneider 2021-06-07 16:44:54 +02:00
parent 11923b89a0
commit ad2dff3eb9

View File

@ -26,7 +26,9 @@ public class Lobby {
private Participant activePlayer; private Participant activePlayer;
private int badRequests; private int badRequests;
private PauseSegment pauseSegment; 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 * 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); var reqSegment = new RequestGameStateSegment(this.game);
this.pauseSegment = new PauseSegment(); this.pauseSegment = new PauseSegment();
var disconnectSegment = new DisconnectSegment(this); var disconnectSegment = new DisconnectSegment(this);
var gameStateSegment = new GameStateSegment(this.game); var gameStateSegment = new GameLogicSegment(this.game);
pipeline.addSegment(reqSegment) pipeline.addSegment(reqSegment)
.addSegment(pauseSegment) .addSegment(pauseSegment)
.addSegment(disconnectSegment) .addSegment(disconnectSegment)
.addSegment(gameStateSegment); .addSegment(gameStateSegment);
this.turnTimer = new TurnTimer( Logger.trace("Instantiating timers...");
partyConfig.maxRoundTime,
this::turnTimeout); 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)); this.connection.broadcastEvents(this.game.startGame(player1Characters, player2Characters));
updateTimer();
} }
/** /**
@ -88,6 +98,9 @@ public class Lobby {
requests.length, requests.length,
source.id.getName(), source.id.getName(),
source.type); source.type);
refreshTimeoutTimer(source);
if (activePlayer != source && source.type != ParticipantType.Spectator) { if (activePlayer != source && source.type != ParticipantType.Spectator) {
Logger.trace("Resetting bad requests as new participant sent data"); Logger.trace("Resetting bad requests as new participant sent data");
activePlayer = source; activePlayer = source;
@ -108,13 +121,23 @@ public class Lobby {
Logger.debug("Rejecting requests from participant '{}'", source.id.getName()); Logger.debug("Rejecting requests from participant '{}'", source.id.getName());
reject(source); reject(source);
} else { } else {
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()); accept(source, resultingEvents.get());
} }
updateTimer(); }
updateTurnTimer();
} }
/** /**
* Called by {@link LobbyConnection} when a client disconnects * Called by {@link LobbyConnection} when a client disconnects
*
* @param source the player disconnecting * @param source the player disconnecting
*/ */
public synchronized void handleDisconnect(Participant source) { public synchronized void handleDisconnect(Participant source) {
@ -123,6 +146,7 @@ public class Lobby {
/** /**
* Called by {@link LobbyConnection} when a client reconnects * Called by {@link LobbyConnection} when a client reconnects
*
* @param source the player reconnecting * @param source the player reconnecting
*/ */
public synchronized void handleReconnect(Participant source) { 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 * 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. * amount of time to do his moves.
*/ */
void updateTimer() { void updateTurnTimer() {
var currentActiveCharacterType = game.state.getActiveCharacter().type; var currentlyActiveParticipant =
Logger.trace("Updating turnTimer with current entityType {}", currentActiveCharacterType); getParticipantForEntityType(game.state.getActiveCharacter().type);
Logger.trace("Updating turnTimer...");
if (pauseSegment.isPaused()) { if (pauseSegment.isPaused()) {
Logger.trace("Game is paused, clearing turnTimer"); Logger.trace("Game is paused, clearing turnTimer");
turnTimer.clear(); turnTimeoutTimer.clear();
} else if (currentActiveCharacterType == EntityType.P1) {
} else if (currentlyActiveParticipant.isPresent()) {
var participant = currentlyActiveParticipant.get();
Logger.trace("Scheduling turnTimer for Player1"); Logger.trace("Scheduling turnTimer for Player1");
turnTimer.startTurnTimer(connection.getPlayer1()); turnTimeoutTimer.startTurnTimer(participant);
} else if (currentActiveCharacterType == EntityType.P2) {
Logger.trace("Scheduling turnTimer for Player2");
turnTimer.startTurnTimer(connection.getPlayer2());
} else { } 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) { private void accept(Participant source, List<Event> accepted) {
Logger.debug("Accepting requests from participant '{}', broadcasting events to all except source", Logger.debug("Accepting requests from participant '{}', broadcasting events to all except source",
source.id.getName()); source.id.getName());
@ -173,9 +225,9 @@ public class Lobby {
if (badRequests >= 5) { if (badRequests >= 5) {
connection.removeParticipant(source); connection.removeParticipant(source);
if (connection.hasPlayer1()) { if (connection.hasPlayer1()) {
generateWin(connection.getPlayer1()); triggerWin(connection.getPlayer1());
} else if (connection.hasPlayer2()) { } else if (connection.hasPlayer2()) {
generateWin(connection.getPlayer2()); triggerWin(connection.getPlayer2());
} }
} }
} }
@ -185,10 +237,12 @@ public class Lobby {
* *
* @param source soon to be timeouted player * @param source soon to be timeouted player
*/ */
public synchronized void soonTimeout(Participant source) { public synchronized void soonTimeout(Participant source, int timeLeft) {
connection.sendEvents( connection.sendEvents(
source, 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) { public synchronized void timeout(Participant source) {
connection.sendEvents( connection.sendEvents(
source, source,
new EventBuilder(EventType.TimeoutEvent).buildGameStateEvent()); new EventBuilder(EventType.TimeoutEvent).buildGameEvent());
connection.removeParticipant(source); connection.removeParticipant(source);
if (connection.hasPlayer1() && !connection.hasPlayer2()) { if (connection.hasPlayer1() && !connection.hasPlayer2()) {
generateWin(connection.getPlayer1()); triggerWin(connection.getPlayer1());
} else if (!connection.hasPlayer1() && connection.hasPlayer2()) { } else if (!connection.hasPlayer1() && connection.hasPlayer2()) {
generateWin(connection.getPlayer2()); triggerWin(connection.getPlayer2());
} else { } else {
throw new IllegalStateException("Spectator was time-outed which is impossible"); 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) { private synchronized void turnTimeout(Participant source) {
var nextTurnEvents = game.endTurn(); var nextTurnEvents = game.endTurn();
nextTurnEvents.add(game.getGameStateEvent()); nextTurnEvents.add(game.getGameStateEvent());
connection.broadcastEvents(nextTurnEvents.toArray(new Event[0])); 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 * The method triggers a winEvent for a {@link Participant}, and broadcasts said event. Afterwards it sends a
* terminates the connection. * DisconnectRequest to everyone, and terminates the connection.
* *
* @param winner is the {@link Participant} that won * @param winner is the {@link Participant} that won
*/ */
public synchronized void generateWin(Participant winner) { public synchronized void triggerWin(Participant winner) {
connection.broadcastEvents( connection.broadcastEvents(
new EventBuilder(EventType.WinEvent) new EventBuilder(EventType.WinEvent)
.withPlayerWon(winner.type.equals(ParticipantType.PlayerOne) ? 1 : 2) .withPlayerWon(winner.type.equals(ParticipantType.PlayerOne) ? 1 : 2)
@ -266,12 +320,12 @@ public class Lobby {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Lobby lobby = (Lobby) o; 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 @Override
public int hashCode() { public int hashCode() {
return Objects.hash(gameID, game, pipeline, activePlayer, badRequests, pauseSegment, turnTimer); return Objects.hash(gameID, game, pipeline, activePlayer, badRequests, pauseSegment, turnTimeoutTimer);
} }
@Override @Override
@ -283,7 +337,7 @@ public class Lobby {
", activePlayer=" + activePlayer + ", activePlayer=" + activePlayer +
", badRequests=" + badRequests + ", badRequests=" + badRequests +
", pauseSegment=" + pauseSegment + ", pauseSegment=" + pauseSegment +
", turnTimer=" + turnTimer + ", turnTimer=" + turnTimeoutTimer +
'}'; '}';
} }
} }