Устранить softlock восьмого уровня
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
/** Black-box level-8 scenarios driven only through the production input boundary. */
|
||||
final class LevelEightScenario {
|
||||
private static final long DECISION_MILLIS = 32L;
|
||||
|
||||
static final class Result {
|
||||
private final boolean completed;
|
||||
private final int deaths;
|
||||
private final String diagnostic;
|
||||
|
||||
Result(boolean completed, int deaths, String diagnostic) {
|
||||
this.completed = completed;
|
||||
this.deaths = deaths;
|
||||
this.diagnostic = diagnostic;
|
||||
}
|
||||
|
||||
boolean completed() { return completed; }
|
||||
int deaths() { return deaths; }
|
||||
String diagnostic() { return diagnostic; }
|
||||
}
|
||||
|
||||
Result recoverAfterClockExpires(LevelPlaytestBot.Driver driver, long timeoutMillis) {
|
||||
long deadline = driver.currentTimeMillis() + timeoutMillis;
|
||||
driver.startLevel(8);
|
||||
GameView.AutomationSnapshot snapshot = crossPit(driver, deadline);
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Cross the pit before the clock expires", snapshot);
|
||||
}
|
||||
|
||||
snapshot = await(driver, deadline, "Wait for the far-side trap",
|
||||
LevelPlaytestBot.Input.none(),
|
||||
current -> current.playerX() >= 1_510f && !current.allDoorsOpen());
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Wait for the far-side trap", snapshot);
|
||||
}
|
||||
|
||||
snapshot = await(driver, deadline, "Return beneath the recovery relay",
|
||||
LevelPlaytestBot.Input.left(),
|
||||
current -> current.playerX() <= 1_680f && current.playerY() >= 779f);
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Return beneath the recovery relay", snapshot);
|
||||
}
|
||||
|
||||
snapshot = pulseLeftJumpThenAwait(driver, deadline, "Jump into the recovery relay",
|
||||
current -> current.allDoorsOpen());
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Jump into the recovery relay", snapshot);
|
||||
}
|
||||
|
||||
snapshot = await(driver, deadline, "Land after rearming the clock",
|
||||
LevelPlaytestBot.Input.none(), current -> current.playerY() >= 779f);
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Land after rearming the clock", snapshot);
|
||||
}
|
||||
|
||||
return finishFromFarSide(driver, deadline, snapshot, "Finish after recovery");
|
||||
}
|
||||
|
||||
Result completeIntendedRoute(LevelPlaytestBot.Driver driver, long timeoutMillis) {
|
||||
long deadline = driver.currentTimeMillis() + timeoutMillis;
|
||||
driver.startLevel(8);
|
||||
GameView.AutomationSnapshot snapshot = crossPit(driver, deadline);
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Cross the pit before the clock expires", snapshot);
|
||||
}
|
||||
return finishFromFarSide(driver, deadline, snapshot, "Finish on the original timer");
|
||||
}
|
||||
|
||||
private GameView.AutomationSnapshot crossPit(LevelPlaytestBot.Driver driver, long deadline) {
|
||||
GameView.AutomationSnapshot snapshot = await(driver, deadline, "Trigger the left timer",
|
||||
LevelPlaytestBot.Input.right(), GameView.AutomationSnapshot::allDoorsOpen);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
long timerStartedAt = snapshot.elapsedMilliseconds();
|
||||
|
||||
snapshot = await(driver, deadline, "Approach the pit",
|
||||
LevelPlaytestBot.Input.right(), current -> current.playerX() >= 760f);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
|
||||
snapshot = await(driver, deadline, "Wait for the moving platform",
|
||||
LevelPlaytestBot.Input.none(),
|
||||
current -> current.elapsedMilliseconds() >= timerStartedAt + 2_250L);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
|
||||
driver.setInput(false, true, true);
|
||||
driver.waitForNextDecision(64L);
|
||||
driver.setInput(false, true, false);
|
||||
snapshot = await(driver, deadline, "Land on the moving platform",
|
||||
LevelPlaytestBot.Input.right(),
|
||||
current -> current.elapsedMilliseconds() >= timerStartedAt + 3_200L);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
|
||||
driver.setInput(false, true, true);
|
||||
driver.waitForNextDecision(64L);
|
||||
driver.setInput(false, true, false);
|
||||
snapshot = await(driver, deadline, "Jump from the moving platform to the far floor",
|
||||
LevelPlaytestBot.Input.right(), current -> current.playerX() >= 1_510f);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
snapshot = awaitFarFloorLandingOnOriginalTimer(driver, deadline);
|
||||
if (failed(snapshot, deadline, driver)) return snapshot;
|
||||
return await(driver, deadline, "Move under the relay after landing",
|
||||
LevelPlaytestBot.Input.right(),
|
||||
current -> current.playerX() >= 1_600f
|
||||
&& current.playerY() >= 779f && current.allDoorsOpen());
|
||||
}
|
||||
|
||||
private GameView.AutomationSnapshot awaitFarFloorLandingOnOriginalTimer(
|
||||
LevelPlaytestBot.Driver driver, long deadline
|
||||
) {
|
||||
boolean moveLeft = true;
|
||||
GameView.AutomationSnapshot snapshot = driver.snapshot();
|
||||
while (driver.currentTimeMillis() < deadline && snapshot.playerAlive()
|
||||
&& !(snapshot.playerX() >= 1_510f && snapshot.playerX() < 1_580f
|
||||
&& snapshot.playerY() >= 779f && snapshot.allDoorsOpen())) {
|
||||
driver.setInput(moveLeft, !moveLeft, false);
|
||||
moveLeft = !moveLeft;
|
||||
driver.waitForNextDecision(DECISION_MILLIS);
|
||||
snapshot = driver.snapshot();
|
||||
}
|
||||
driver.setInput(false, false, false);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private Result finishFromFarSide(LevelPlaytestBot.Driver driver, long deadline,
|
||||
GameView.AutomationSnapshot snapshot, String step) {
|
||||
snapshot = await(driver, deadline, "Reach the open-door jump",
|
||||
LevelPlaytestBot.Input.right(),
|
||||
current -> current.playerX() >= 1_730f && current.allDoorsOpen());
|
||||
if (failed(snapshot, deadline, driver)) {
|
||||
return failure("Reach the open-door jump", snapshot);
|
||||
}
|
||||
|
||||
driver.setInput(false, true, true);
|
||||
driver.waitForNextDecision(64L);
|
||||
driver.setInput(false, true, false);
|
||||
snapshot = await(driver, deadline, step, LevelPlaytestBot.Input.right(),
|
||||
GameView.AutomationSnapshot::completed);
|
||||
driver.setInput(false, false, false);
|
||||
if (!snapshot.completed()) return failure(step, snapshot);
|
||||
return new Result(true, snapshot.deaths(), step + " completed; " + state(snapshot));
|
||||
}
|
||||
|
||||
private GameView.AutomationSnapshot pulseLeftJumpThenAwait(
|
||||
LevelPlaytestBot.Driver driver, long deadline, String step,
|
||||
LevelPlaytestBot.SnapshotCondition completion
|
||||
) {
|
||||
driver.setInput(true, false, true);
|
||||
driver.waitForNextDecision(64L);
|
||||
driver.setInput(true, false, false);
|
||||
return await(driver, deadline, step, LevelPlaytestBot.Input.left(), completion);
|
||||
}
|
||||
|
||||
private GameView.AutomationSnapshot await(
|
||||
LevelPlaytestBot.Driver driver, long deadline, String step,
|
||||
LevelPlaytestBot.Input input, LevelPlaytestBot.SnapshotCondition completion
|
||||
) {
|
||||
GameView.AutomationSnapshot snapshot = driver.snapshot();
|
||||
while (driver.currentTimeMillis() < deadline && snapshot.playerAlive()
|
||||
&& !snapshot.completed() && !completion.reached(snapshot)) {
|
||||
setInput(driver, input);
|
||||
driver.waitForNextDecision(DECISION_MILLIS);
|
||||
snapshot = driver.snapshot();
|
||||
}
|
||||
if (driver.currentTimeMillis() >= deadline || !snapshot.playerAlive()) {
|
||||
driver.setInput(false, false, false);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static void setInput(LevelPlaytestBot.Driver driver, LevelPlaytestBot.Input input) {
|
||||
if (input == LevelPlaytestBot.Input.right()) {
|
||||
driver.setInput(false, true, false);
|
||||
} else if (input == LevelPlaytestBot.Input.left()) {
|
||||
driver.setInput(true, false, false);
|
||||
} else if (input == LevelPlaytestBot.Input.rightJump()) {
|
||||
driver.setInput(false, true, true);
|
||||
} else {
|
||||
driver.setInput(false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean failed(GameView.AutomationSnapshot snapshot, long deadline,
|
||||
LevelPlaytestBot.Driver driver) {
|
||||
return !snapshot.completed()
|
||||
&& (!snapshot.playerAlive() || driver.currentTimeMillis() >= deadline);
|
||||
}
|
||||
|
||||
private static Result failure(String step, GameView.AutomationSnapshot snapshot) {
|
||||
return new Result(false, snapshot.deaths(), step + " failed; " + state(snapshot));
|
||||
}
|
||||
|
||||
private static String state(GameView.AutomationSnapshot snapshot) {
|
||||
return "x=" + snapshot.playerX()
|
||||
+ ", y=" + snapshot.playerY()
|
||||
+ ", alive=" + snapshot.playerAlive()
|
||||
+ ", doorOpen=" + snapshot.allDoorsOpen()
|
||||
+ ", completed=" + snapshot.completed()
|
||||
+ ", elapsedMs=" + snapshot.elapsedMilliseconds()
|
||||
+ ", deaths=" + snapshot.deaths();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user