Стабилизировать бота уровня 10 при задержках
This commit is contained in:
@@ -107,19 +107,56 @@ public final class LateLevelPlaythroughTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void masteryProcessionSurvivesThrottledFinalRelayDecisions() {
|
||||||
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
GameDriver driver = GameDriver.withFinalRelayDelay(activity, 96L);
|
||||||
|
|
||||||
|
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
|
||||||
|
driver,
|
||||||
|
LateLevelScenarios.levelTen(),
|
||||||
|
65_000L
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(result.diagnostic(), result.completed());
|
||||||
|
assertEquals("throttling must not add accidental volunteers", 3, result.deaths());
|
||||||
|
assertEquals("all three intended bodies must remain", 3, result.corpseCount());
|
||||||
|
assertTrue("the conductive relay must still open the final door",
|
||||||
|
result.doorOpened());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class GameDriver implements LevelPlaytestBot.Driver {
|
private static final class GameDriver implements LevelPlaytestBot.Driver {
|
||||||
private final Instrumentation instrumentation;
|
private final Instrumentation instrumentation;
|
||||||
private final GameView gameView;
|
private final GameView gameView;
|
||||||
|
private final long minimumDecisionMillis;
|
||||||
|
private final boolean throttleFinalRelayOnly;
|
||||||
|
private GameView.AutomationSnapshot lastSnapshot;
|
||||||
|
|
||||||
private GameDriver(Instrumentation instrumentation, GameView gameView) {
|
private GameDriver(Instrumentation instrumentation, GameView gameView,
|
||||||
|
long minimumDecisionMillis, boolean throttleFinalRelayOnly) {
|
||||||
this.instrumentation = instrumentation;
|
this.instrumentation = instrumentation;
|
||||||
this.gameView = gameView;
|
this.gameView = gameView;
|
||||||
|
this.minimumDecisionMillis = minimumDecisionMillis;
|
||||||
|
this.throttleFinalRelayOnly = throttleFinalRelayOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GameDriver from(ActivityScenario<MainActivity> activity) {
|
static GameDriver from(ActivityScenario<MainActivity> activity) {
|
||||||
|
return create(activity, 32L, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GameDriver withFinalRelayDelay(ActivityScenario<MainActivity> activity,
|
||||||
|
long minimumDecisionMillis) {
|
||||||
|
return create(activity, minimumDecisionMillis, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameDriver create(ActivityScenario<MainActivity> activity,
|
||||||
|
long minimumDecisionMillis,
|
||||||
|
boolean throttleFinalRelayOnly) {
|
||||||
GameView[] view = new GameView[1];
|
GameView[] view = new GameView[1];
|
||||||
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
|
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
|
||||||
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0]);
|
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0],
|
||||||
|
minimumDecisionMillis, throttleFinalRelayOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -138,13 +175,22 @@ public final class LateLevelPlaythroughTest {
|
|||||||
public GameView.AutomationSnapshot snapshot() {
|
public GameView.AutomationSnapshot snapshot() {
|
||||||
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
||||||
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
|
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
|
||||||
return value[0];
|
lastSnapshot = value[0];
|
||||||
|
return lastSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForNextDecision(long millis) {
|
public void waitForNextDecision(long millis) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(millis);
|
boolean inFinalRelayWindow = throttleFinalRelayOnly
|
||||||
|
&& lastSnapshot != null
|
||||||
|
&& lastSnapshot.levelNumber() == 10
|
||||||
|
&& lastSnapshot.deaths() == 3
|
||||||
|
&& lastSnapshot.playerX() >= 1_200f
|
||||||
|
&& lastSnapshot.playerX() <= 1_700f;
|
||||||
|
Thread.sleep(inFinalRelayWindow
|
||||||
|
? Math.max(millis, minimumDecisionMillis)
|
||||||
|
: millis);
|
||||||
} catch (InterruptedException interrupted) {
|
} catch (InterruptedException interrupted) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
throw new AssertionError("Late-level bot interrupted", interrupted);
|
throw new AssertionError("Late-level bot interrupted", interrupted);
|
||||||
|
|||||||
@@ -155,10 +155,10 @@ final class LateLevelScenarios {
|
|||||||
snapshot -> snapshot.playerX() >= 730f)
|
snapshot -> snapshot.playerX() >= 730f)
|
||||||
.step("Clear the lift body again", 2_000L,
|
.step("Clear the lift body again", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 900f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
||||||
.step("Prepare the frozen-block jump", 2_000L,
|
.step("Prepare the frozen-block jump", 2_000L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 740f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
|
||||||
.step("Jump over the frozen block", 2_500L,
|
.step("Jump over the frozen block", 2_500L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
||||||
@@ -185,25 +185,42 @@ final class LateLevelScenarios {
|
|||||||
snapshot -> snapshot.playerX() >= 730f)
|
snapshot -> snapshot.playerX() >= 730f)
|
||||||
.step("Clear the retained lift body", 2_000L,
|
.step("Clear the retained lift body", 2_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 900f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 860f)
|
||||||
.step("Prepare the final frozen-block jump", 2_000L,
|
.step("Prepare the final frozen-block jump", 2_000L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 740f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerY() >= 720f)
|
||||||
.step("Clear the frozen block", 2_500L,
|
.step("Clear the frozen block", 2_500L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_240f)
|
||||||
.step("Approach the powered relay", 2_500L,
|
.step("Approach the powered relay", 2_500L,
|
||||||
LevelPlaytestBot.Input.right(),
|
LevelPlaytestBot.Input.right(),
|
||||||
snapshot -> snapshot.playerX() >= 1_390f)
|
snapshot -> snapshot.playerX() >= 1_320f)
|
||||||
.step("Clear the powered relay", 2_500L,
|
.step("Take off before the final relay", 1_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 700f)
|
||||||
|
.step("Clear the powered relay", 2_000L,
|
||||||
|
LevelPlaytestBot.Input.right(),
|
||||||
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_650f)
|
snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 1_650f)
|
||||||
.step("Approach the lift", 2_000L, LevelPlaytestBot.Input.right(),
|
.step("Wait for the lift at floor height", 6_000L,
|
||||||
snapshot -> snapshot.playerX() >= 1_730f)
|
LevelPlaytestBot.Input.none(),
|
||||||
.step("Board the moving lift", 3_000L,
|
snapshot -> snapshot.playerAlive()
|
||||||
|
&& snapshot.playerY() >= 770f
|
||||||
|
&& !Float.isNaN(snapshot.firstMovingPlatformY())
|
||||||
|
&& snapshot.firstMovingPlatformY() >= 740f)
|
||||||
|
.step("Take off toward the moving lift", 1_000L,
|
||||||
LevelPlaytestBot.Input.rightJump(),
|
LevelPlaytestBot.Input.rightJump(),
|
||||||
snapshot -> snapshot.playerX() >= 1_790f
|
snapshot -> snapshot.playerAlive() && snapshot.playerY() <= 670f)
|
||||||
&& snapshot.playerY() <= 670f)
|
.step("Board the moving lift", 2_000L,
|
||||||
|
LevelPlaytestBot.Input.right(),
|
||||||
|
GameView.AutomationSnapshot::playerOnMovingPlatform)
|
||||||
|
.step("Brake toward the lift center", 1_000L,
|
||||||
|
LevelPlaytestBot.Input.left(),
|
||||||
|
snapshot -> snapshot.playerOnMovingPlatform()
|
||||||
|
&& snapshot.playerX() <= 1_900f)
|
||||||
|
.step("Center on the moving lift", 1_000L,
|
||||||
|
LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerOnMovingPlatform()
|
||||||
|
&& snapshot.playerX() >= 1_920f)
|
||||||
.step("Ride the lift to the upper door", 5_000L,
|
.step("Ride the lift to the upper door", 5_000L,
|
||||||
LevelPlaytestBot.Input.none(),
|
LevelPlaytestBot.Input.none(),
|
||||||
snapshot -> snapshot.playerY() <= 310f)
|
snapshot -> snapshot.playerY() <= 310f)
|
||||||
|
|||||||
@@ -53,11 +53,23 @@ public final class GameView extends View implements Runnable {
|
|||||||
private final float playerY;
|
private final float playerY;
|
||||||
private final int deaths;
|
private final int deaths;
|
||||||
private final int corpseCount;
|
private final int corpseCount;
|
||||||
|
private final float firstMovingPlatformY;
|
||||||
|
private final boolean playerOnMovingPlatform;
|
||||||
|
|
||||||
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
||||||
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
||||||
long elapsedMilliseconds,
|
long elapsedMilliseconds,
|
||||||
float playerX, float playerY, int deaths, int corpseCount) {
|
float playerX, float playerY, int deaths, int corpseCount) {
|
||||||
|
this(completed, playerAlive, allDoorsOpen, visiblyPaused, inputActive, levelNumber,
|
||||||
|
elapsedMilliseconds, playerX, playerY, deaths, corpseCount,
|
||||||
|
Float.NaN, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutomationSnapshot(boolean completed, boolean playerAlive, boolean allDoorsOpen,
|
||||||
|
boolean visiblyPaused, boolean inputActive, int levelNumber,
|
||||||
|
long elapsedMilliseconds,
|
||||||
|
float playerX, float playerY, int deaths, int corpseCount,
|
||||||
|
float firstMovingPlatformY, boolean playerOnMovingPlatform) {
|
||||||
this.completed = completed;
|
this.completed = completed;
|
||||||
this.playerAlive = playerAlive;
|
this.playerAlive = playerAlive;
|
||||||
this.allDoorsOpen = allDoorsOpen;
|
this.allDoorsOpen = allDoorsOpen;
|
||||||
@@ -69,6 +81,8 @@ public final class GameView extends View implements Runnable {
|
|||||||
this.playerY = playerY;
|
this.playerY = playerY;
|
||||||
this.deaths = deaths;
|
this.deaths = deaths;
|
||||||
this.corpseCount = corpseCount;
|
this.corpseCount = corpseCount;
|
||||||
|
this.firstMovingPlatformY = firstMovingPlatformY;
|
||||||
|
this.playerOnMovingPlatform = playerOnMovingPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean completed() { return completed; }
|
public boolean completed() { return completed; }
|
||||||
@@ -82,6 +96,8 @@ public final class GameView extends View implements Runnable {
|
|||||||
public float playerY() { return playerY; }
|
public float playerY() { return playerY; }
|
||||||
public int deaths() { return deaths; }
|
public int deaths() { return deaths; }
|
||||||
public int corpseCount() { return corpseCount; }
|
public int corpseCount() { return corpseCount; }
|
||||||
|
public float firstMovingPlatformY() { return firstMovingPlatformY; }
|
||||||
|
public boolean playerOnMovingPlatform() { return playerOnMovingPlatform; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Player {
|
private static final class Player {
|
||||||
@@ -1128,6 +1144,17 @@ public final class GameView extends View implements Runnable {
|
|||||||
if (level != null) {
|
if (level != null) {
|
||||||
for (LevelDefinition.Door door : level.doors) allOpen &= doorOpen(door);
|
for (LevelDefinition.Door door : level.doors) allOpen &= doorOpen(door);
|
||||||
}
|
}
|
||||||
|
float firstMovingPlatformY = Float.NaN;
|
||||||
|
for (PlatformState state : platformStates) {
|
||||||
|
if (state.definition.isMoving()) {
|
||||||
|
firstMovingPlatformY = state.y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean playerOnMovingPlatform = player.alive
|
||||||
|
&& player.supportIndex >= 0
|
||||||
|
&& player.supportIndex < platformStates.size()
|
||||||
|
&& platformStates.get(player.supportIndex).definition.isMoving();
|
||||||
return new AutomationSnapshot(
|
return new AutomationSnapshot(
|
||||||
screen == Screen.COMPLETE,
|
screen == Screen.COMPLETE,
|
||||||
player.alive,
|
player.alive,
|
||||||
@@ -1139,7 +1166,9 @@ public final class GameView extends View implements Runnable {
|
|||||||
player.x,
|
player.x,
|
||||||
player.y,
|
player.y,
|
||||||
deaths,
|
deaths,
|
||||||
corpses.size()
|
corpses.size(),
|
||||||
|
firstMovingPlatformY,
|
||||||
|
playerOnMovingPlatform
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,16 @@ public final class LevelPlaytestBotTest {
|
|||||||
assertTrue(result.doorOpened());
|
assertTrue(result.doorOpened());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void automationSnapshotDefaultsToNoMovingPlatform() {
|
||||||
|
GameView.AutomationSnapshot snapshot = new GameView.AutomationSnapshot(
|
||||||
|
false, true, false, false, false,
|
||||||
|
1, 0L, 130f, 760f, 0, 0);
|
||||||
|
|
||||||
|
assertTrue(Float.isNaN(snapshot.firstMovingPlatformY()));
|
||||||
|
assertFalse(snapshot.playerOnMovingPlatform());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class ScriptedDriver implements LevelPlaytestBot.Driver {
|
private static final class ScriptedDriver implements LevelPlaytestBot.Driver {
|
||||||
private int startedLevel;
|
private int startedLevel;
|
||||||
private long nowMillis;
|
private long nowMillis;
|
||||||
|
|||||||
Reference in New Issue
Block a user