From 3c9d893ea9a131e676f72777d14d76653c176a66 Mon Sep 17 00:00:00 2001 From: eulerscheZahl Date: Tue, 24 Mar 2026 16:54:13 +0100 Subject: [PATCH 1/3] add soft-timeout --- .../gameengine/core/AbstractPlayer.java | 19 +++++++++++++++++++ .../gameengine/core/GameManager.java | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java index 81a1d70f..2dffdc8b 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java @@ -28,10 +28,14 @@ public static class TimeoutException extends Exception { private List inputs = new ArrayList<>(); private List outputs; private boolean timeout; + private int timelimit; private int score; private boolean hasBeenExecuted; private boolean hasNeverBeenExecuted = true; private long lastExecutionTimeMs = -1; + private int timelimitsExceeded = 0; + private boolean timelimitExceededLastTurn = false; + private final int MAX_SOFT_TIMELIMIT_EXCEEDS = 2; /** * Returns a string that will be converted into the real nickname by the viewer. @@ -79,6 +83,10 @@ void setScore(int score) { this.score = score; } + void setTimelimit(int timelimit) { + this.timelimit = timelimit; + } + /** * Adds a new line to the input to send to the player on execute. * @@ -102,6 +110,9 @@ public final void execute() { gameManagerProvider.get().execute(this); this.hasBeenExecuted = true; this.hasNeverBeenExecuted = false; + this.timelimitExceededLastTurn = getLastExectionTimeMs() > this.timelimit; + if (this.timelimitExceededLastTurn) this.timelimitsExceeded++; + if (this.timelimitsExceeded > MAX_SOFT_TIMELIMIT_EXCEEDS) this.timeout = true; } /** @@ -183,4 +194,12 @@ final public void setLastExecutionTimeMs(long ms) { public long getLastExectionTimeMs() { return lastExecutionTimeMs; } + + public int getTimelimitsExceeded() { + return timelimitsExceeded; + } + + public boolean hasTimelimitExceededLastTurn() { + return timelimitExceededLastTurn; + } } diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java index 619da988..7a8fc067 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java @@ -39,6 +39,7 @@ abstract public class GameManager { private static final int GAME_DURATION_SOFT_QUOTA = 25_000; private static final int MAX_TURN_TIME = GAME_DURATION_SOFT_QUOTA; private static final int MIN_TURN_TIME = 50; + private static final int SOFT_TIMELIMIT_EXTRA = 50; protected List players; private int maxTurns = 200; @@ -196,7 +197,9 @@ protected void execute(T player, int nbrOutputLines) { if (nbrOutputLines > 0) { addTurnTime(); } - dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, player.hasNeverBeenExecuted() ? firstTurnMaxTime : turnMaxTime); + int timelimit = player.hasNeverBeenExecuted() ? firstTurnMaxTime : turnMaxTime; + player.setTimelimit(timelimit); + dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, timelimit + SOFT_TIMELIMIT_EXTRA); // READ PLAYER OUTPUTS iCmd = InputCommand.parse(s.nextLine()); From f25c329942d357b2343743a9fc67b3f5d8121c63 Mon Sep 17 00:00:00 2001 From: eulerscheZahl Date: Thu, 26 Mar 2026 18:37:20 +0100 Subject: [PATCH 2/3] add timebank --- .../gameengine/core/AbstractPlayer.java | 29 ++++++++++++++ .../gameengine/core/GameManager.java | 40 +++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java index 2dffdc8b..81c1c3db 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java @@ -34,6 +34,8 @@ public static class TimeoutException extends Exception { private boolean hasNeverBeenExecuted = true; private long lastExecutionTimeMs = -1; private int timelimitsExceeded = 0; + private boolean useTimebank = false; + private int timebank = 0; private boolean timelimitExceededLastTurn = false; private final int MAX_SOFT_TIMELIMIT_EXCEEDS = 2; @@ -83,10 +85,36 @@ void setScore(int score) { this.score = score; } + /** + * Set the player's time limit for the next turn + * + * @param timelimit + * The time limit in milliseconds + */ void setTimelimit(int timelimit) { this.timelimit = timelimit; } + /** + * Set the player's timebank for the whole game + * + * @param timebank + * the timebank in milliseconds + */ + void setTimebank(int timebank) { + this.useTimebank = true; + this.timebank = timebank; + } + + /** + * Get the remaining timebank + * + * @return the remaining timebank in milliseconds + */ + public int getRemainingTimebank() { + return timebank; + } + /** * Adds a new line to the input to send to the player on execute. * @@ -110,6 +138,7 @@ public final void execute() { gameManagerProvider.get().execute(this); this.hasBeenExecuted = true; this.hasNeverBeenExecuted = false; + if (this.useTimebank) this.timebank -= getLastExectionTimeMs(); this.timelimitExceededLastTurn = getLastExectionTimeMs() > this.timelimit; if (this.timelimitExceededLastTurn) this.timelimitsExceeded++; if (this.timelimitsExceeded > MAX_SOFT_TIMELIMIT_EXCEEDS) this.timeout = true; diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java index 7a8fc067..cab6bff7 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java @@ -74,6 +74,8 @@ abstract public class GameManager { private int totalViewDataBytesSent = 0; private int totalGameSummaryBytes = 0; private int totalTurnTime = 0; + private boolean useTurntime = false; + private boolean useTimebank = false; private boolean viewWarning, summaryWarning; private boolean monitoringRequested; @@ -194,12 +196,13 @@ protected void execute(T player, int nbrOutputLines) { } dumpInfos(); dumpNextPlayerInput(player.getInputs().toArray(new String[0])); - if (nbrOutputLines > 0) { + if (nbrOutputLines > 0 && !useTimebank) { addTurnTime(); } int timelimit = player.hasNeverBeenExecuted() ? firstTurnMaxTime : turnMaxTime; + if (useTimebank) timelimit = player.getRemainingTimebank(); player.setTimelimit(timelimit); - dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, timelimit + SOFT_TIMELIMIT_EXTRA); + dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, timelimit + (useTimebank ? 0 : SOFT_TIMELIMIT_EXTRA)); // READ PLAYER OUTPUTS iCmd = InputCommand.parse(s.nextLine()); @@ -444,6 +447,29 @@ public boolean isGameEnd() { return this.gameEnd; } + /** + * Set the timeout delay for each player for the whole game. This will disable the time limit per turn + * + * @param timebank + * Duration in milliseconds + * @throws IllegalArgumentException + * if timebank < 1000 or > 25000 + */ + public void setTimebank(int timebank) { + if (useTurntime) { + throw new UnsupportedOperationException("Use either setTimebank or setTurnMaxTime & setFirstTurnMaxTime, not both"); + } else if (timebank < MIN_TURN_TIME) { + throw new IllegalArgumentException("Invalid time bank: use at least 1000ms"); + } else if (timebank * players.size() > GAME_DURATION_HARD_QUOTA) { + throw new IllegalArgumentException("Invalid timebank: stay under " + GAME_DURATION_HARD_QUOTA + "ms total, " + (GAME_DURATION_HARD_QUOTA / players.size()) + " per player"); + } + totalTurnTime += timebank * players.size(); + this.useTimebank = true; + for (T player : players) { + player.setTimebank(timebank); + } + } + /** * Set the maximum amount of turns. Default: 400. * @@ -477,12 +503,15 @@ public int getMaxTurns() { * if turnMaxTime < 50 or > 25000 */ public void setTurnMaxTime(int turnMaxTime) throws IllegalArgumentException { - if (turnMaxTime < MIN_TURN_TIME) { + if (useTimebank) { + throw new UnsupportedOperationException("Use either setTimebank or setTurnMaxTime & setFirstTurnMaxTime, not both"); + } else if (turnMaxTime < MIN_TURN_TIME) { throw new IllegalArgumentException("Invalid turn max time : stay above 50ms"); } else if (turnMaxTime > MAX_TURN_TIME) { throw new IllegalArgumentException("Invalid turn max time : stay under 25s"); } this.turnMaxTime = turnMaxTime; + this.useTurntime = true; } /** @@ -494,12 +523,15 @@ public void setTurnMaxTime(int turnMaxTime) throws IllegalArgumentException { * if firstTurnMaxTime < 50 or > 25000 */ public void setFirstTurnMaxTime(int firstTurnMaxTime) throws IllegalArgumentException { - if (firstTurnMaxTime < MIN_TURN_TIME) { + if (useTurntime) { + throw new UnsupportedOperationException("Use either setTimebank or setTurnMaxTime & setFirstTurnMaxTime, not both"); + } else if (firstTurnMaxTime < MIN_TURN_TIME) { throw new IllegalArgumentException("Invalid turn max time : stay above 50ms"); } else if (firstTurnMaxTime > MAX_TURN_TIME) { throw new IllegalArgumentException("Invalid turn max time : stay under 25s"); } this.firstTurnMaxTime = firstTurnMaxTime; + this.useTurntime = true; } /** From e738bc2f55d4ff1fa34ba67bba3e4cf8104f20dd Mon Sep 17 00:00:00 2001 From: eulerscheZahl Date: Fri, 27 Mar 2026 05:57:20 +0100 Subject: [PATCH 3/3] add Javadoc, don't allow negative timebank --- .../gameengine/core/AbstractPlayer.java | 48 +++++++++++++------ .../gameengine/core/GameManager.java | 2 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java index 81c1c3db..a2f24f89 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/AbstractPlayer.java @@ -34,7 +34,7 @@ public static class TimeoutException extends Exception { private boolean hasNeverBeenExecuted = true; private long lastExecutionTimeMs = -1; private int timelimitsExceeded = 0; - private boolean useTimebank = false; + private boolean useTimebank = false; private int timebank = 0; private boolean timelimitExceededLastTurn = false; private final int MAX_SOFT_TIMELIMIT_EXCEEDS = 2; @@ -115,6 +115,35 @@ public int getRemainingTimebank() { return timebank; } + /** + * Get the execution time of the last successful execution + * Will still return the previous value in case of a timeout + * + * @return The execution time of the last execution in milliseconds + */ + public long getLastExectionTimeMs() { + return lastExecutionTimeMs; + } + + /** + * Get how often the player has already exceeded the time limit in this game + * + * @return how often the player has already exceeded the time limit in this game + */ + public int getTimelimitsExceeded() { + return timelimitsExceeded; + } + + /** + * Get whether the player exceeded the time limit in the last turn + * + * @return true, if the player exceeded the time limit in the last turn + */ + public boolean hasTimelimitExceededLastTurn() { + return timelimitExceededLastTurn; + } + + /** * Adds a new line to the input to send to the player on execute. * @@ -138,7 +167,10 @@ public final void execute() { gameManagerProvider.get().execute(this); this.hasBeenExecuted = true; this.hasNeverBeenExecuted = false; - if (this.useTimebank) this.timebank -= getLastExectionTimeMs(); + if (this.useTimebank) { + if (hasTimedOut()) this.timebank = 0; + else this.timebank -= getLastExectionTimeMs(); + } this.timelimitExceededLastTurn = getLastExectionTimeMs() > this.timelimit; if (this.timelimitExceededLastTurn) this.timelimitsExceeded++; if (this.timelimitsExceeded > MAX_SOFT_TIMELIMIT_EXCEEDS) this.timeout = true; @@ -219,16 +251,4 @@ final boolean hasNeverBeenExecuted() { final public void setLastExecutionTimeMs(long ms) { this.lastExecutionTimeMs = ms; } - - public long getLastExectionTimeMs() { - return lastExecutionTimeMs; - } - - public int getTimelimitsExceeded() { - return timelimitsExceeded; - } - - public boolean hasTimelimitExceededLastTurn() { - return timelimitExceededLastTurn; - } } diff --git a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java index cab6bff7..47b3e726 100644 --- a/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java +++ b/engine/core/src/main/java/com/codingame/gameengine/core/GameManager.java @@ -453,7 +453,7 @@ public boolean isGameEnd() { * @param timebank * Duration in milliseconds * @throws IllegalArgumentException - * if timebank < 1000 or > 25000 + * if timebank < 1000 or > 30000 for all players combined */ public void setTimebank(int timebank) { if (useTurntime) {