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..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 @@ -28,10 +28,16 @@ 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 useTimebank = false; + private int timebank = 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 +85,65 @@ 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; + } + + /** + * 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. * @@ -102,6 +167,13 @@ public final void execute() { gameManagerProvider.get().execute(this); this.hasBeenExecuted = true; this.hasNeverBeenExecuted = false; + 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; } /** @@ -179,8 +251,4 @@ final boolean hasNeverBeenExecuted() { final public void setLastExecutionTimeMs(long ms) { this.lastExecutionTimeMs = ms; } - - public long getLastExectionTimeMs() { - return lastExecutionTimeMs; - } } 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..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 @@ -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; @@ -73,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; @@ -193,10 +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(); } - dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, player.hasNeverBeenExecuted() ? firstTurnMaxTime : turnMaxTime); + int timelimit = player.hasNeverBeenExecuted() ? firstTurnMaxTime : turnMaxTime; + if (useTimebank) timelimit = player.getRemainingTimebank(); + player.setTimelimit(timelimit); + dumpNextPlayerInfos(player.getIndex(), nbrOutputLines, timelimit + (useTimebank ? 0 : SOFT_TIMELIMIT_EXTRA)); // READ PLAYER OUTPUTS iCmd = InputCommand.parse(s.nextLine()); @@ -441,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 > 30000 for all players combined + */ + 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. * @@ -474,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; } /** @@ -491,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; } /**