257 lines
11 KiB
Java
257 lines
11 KiB
Java
package com.projectsacrifice.game;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
import android.app.Instrumentation;
|
|
|
|
import androidx.test.core.app.ActivityScenario;
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
import androidx.test.platform.app.InstrumentationRegistry;
|
|
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
|
|
/** Production-input acceptance coverage for levels 6, 7, 9 and 10. */
|
|
@RunWith(AndroidJUnit4.class)
|
|
public final class LateLevelPlaythroughTest {
|
|
private static final int CONSECUTIVE_RUNS = 3;
|
|
|
|
@Test
|
|
public void frozenVolunteerIsUsedToCompleteLevelSixThreeTimes() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.from(activity);
|
|
|
|
for (int run = 1; run <= CONSECUTIVE_RUNS; run++) {
|
|
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
|
|
driver,
|
|
LateLevelScenarios.levelSix(),
|
|
30_000L
|
|
);
|
|
|
|
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
|
|
assertEquals("run=" + run + "; intended route uses one frozen volunteer",
|
|
1, result.deaths());
|
|
assertEquals("run=" + run + "; frozen block must remain in the puzzle",
|
|
1, result.corpseCount());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void conductiveVolunteerKeepsLevelSevenDoorOpenThreeTimes() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.from(activity);
|
|
|
|
for (int run = 1; run <= CONSECUTIVE_RUNS; run++) {
|
|
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
|
|
driver,
|
|
LateLevelScenarios.levelSeven(),
|
|
25_000L
|
|
);
|
|
|
|
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
|
|
assertEquals("run=" + run + "; intended route uses one conductive volunteer",
|
|
1, result.deaths());
|
|
assertEquals("run=" + run + "; conductive body must remain in the relay",
|
|
1, result.corpseCount());
|
|
assertTrue("run=" + run + "; conductive relay must open the door",
|
|
result.doorOpened());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void weightAndPowerOpenLevelNineDoorThreeTimes() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.from(activity);
|
|
|
|
for (int run = 1; run <= CONSECUTIVE_RUNS; run++) {
|
|
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
|
|
driver,
|
|
LateLevelScenarios.levelNine(),
|
|
35_000L
|
|
);
|
|
|
|
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
|
|
assertEquals("run=" + run + "; weight and power require two volunteers",
|
|
2, result.deaths());
|
|
assertEquals("run=" + run + "; both signal bodies must remain",
|
|
2, result.corpseCount());
|
|
assertTrue("run=" + run + "; both signals must open the AND door",
|
|
result.doorOpened());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void masteryProcessionCompletesLevelTenThreeTimes() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.from(activity);
|
|
|
|
for (int run = 1; run <= CONSECUTIVE_RUNS; run++) {
|
|
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(
|
|
driver,
|
|
LateLevelScenarios.levelTen(),
|
|
55_000L
|
|
);
|
|
|
|
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
|
|
assertEquals("run=" + run + "; mastery route uses three volunteers",
|
|
3, result.deaths());
|
|
assertEquals("run=" + run + "; launch, freeze and relay bodies must remain",
|
|
3, result.corpseCount());
|
|
assertTrue("run=" + run + "; conductive relay must open the final door",
|
|
result.doorOpened());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void masteryProcessionSurvivesThrottledFinalTraversalDecisions() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.withFinalTraversalDelay(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());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void sawDeliveredBodyKeepsLevelTenLiftPoweredAfterSettling() {
|
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
|
GameDriver driver = GameDriver.from(activity);
|
|
driver.startLevel(10);
|
|
await(driver, 4_000L, false, true, false,
|
|
snapshot -> snapshot.deaths() == 1 && snapshot.corpseCount() == 1);
|
|
GameView.AutomationSnapshot engaged = await(driver, 5_000L, false, false, false,
|
|
GameView.AutomationSnapshot::firstMovingPlatformPowered);
|
|
driver.setInput(false, false, false);
|
|
driver.waitForNextDecision(750L);
|
|
|
|
GameView.AutomationSnapshot settled = driver.snapshot();
|
|
assertEquals("The body must settle when it engages the lift plate instead of sliding",
|
|
engaged.firstCorpseX(), settled.firstCorpseX(), 2f);
|
|
|
|
driver.waitForNextDecision(6_000L);
|
|
|
|
settled = driver.snapshot();
|
|
assertTrue("The saw-delivered body slid off the lift plate after settling; "
|
|
+ "platformY=" + settled.firstMovingPlatformY()
|
|
+ ", deaths=" + settled.deaths()
|
|
+ ", corpses=" + settled.corpseCount(),
|
|
settled.firstMovingPlatformPowered());
|
|
assertEquals("The stability wait must not consume another volunteer",
|
|
1, settled.deaths());
|
|
assertEquals("The delivered body must remain in the level",
|
|
1, settled.corpseCount());
|
|
}
|
|
}
|
|
|
|
private static GameView.AutomationSnapshot await(
|
|
GameDriver driver,
|
|
long timeoutMillis,
|
|
boolean moveLeft,
|
|
boolean moveRight,
|
|
boolean jump,
|
|
LevelPlaytestBot.SnapshotCondition condition
|
|
) {
|
|
long deadline = System.currentTimeMillis() + timeoutMillis;
|
|
GameView.AutomationSnapshot snapshot = driver.snapshot();
|
|
while (System.currentTimeMillis() < deadline) {
|
|
snapshot = driver.snapshot();
|
|
if (condition.reached(snapshot)) {
|
|
driver.setInput(false, false, false);
|
|
return snapshot;
|
|
}
|
|
driver.setInput(moveLeft, moveRight, jump);
|
|
driver.waitForNextDecision(32L);
|
|
}
|
|
throw new AssertionError("Timed out waiting for level 10 lift body; x="
|
|
+ snapshot.playerX() + ", y=" + snapshot.playerY()
|
|
+ ", deaths=" + snapshot.deaths()
|
|
+ ", corpses=" + snapshot.corpseCount()
|
|
+ ", platformY=" + snapshot.firstMovingPlatformY());
|
|
}
|
|
|
|
private static final class GameDriver implements LevelPlaytestBot.Driver {
|
|
private final Instrumentation instrumentation;
|
|
private final GameView gameView;
|
|
private final long minimumDecisionMillis;
|
|
private final boolean throttleFinalTraversalOnly;
|
|
private GameView.AutomationSnapshot lastSnapshot;
|
|
|
|
private GameDriver(Instrumentation instrumentation, GameView gameView,
|
|
long minimumDecisionMillis, boolean throttleFinalTraversalOnly) {
|
|
this.instrumentation = instrumentation;
|
|
this.gameView = gameView;
|
|
this.minimumDecisionMillis = minimumDecisionMillis;
|
|
this.throttleFinalTraversalOnly = throttleFinalTraversalOnly;
|
|
}
|
|
|
|
static GameDriver from(ActivityScenario<MainActivity> activity) {
|
|
return create(activity, 32L, false);
|
|
}
|
|
|
|
static GameDriver withFinalTraversalDelay(ActivityScenario<MainActivity> activity,
|
|
long minimumDecisionMillis) {
|
|
return create(activity, minimumDecisionMillis, true);
|
|
}
|
|
|
|
private static GameDriver create(ActivityScenario<MainActivity> activity,
|
|
long minimumDecisionMillis,
|
|
boolean throttleFinalTraversalOnly) {
|
|
GameView[] view = new GameView[1];
|
|
activity.onActivity(current -> view[0] = current.gameViewForAutomation());
|
|
return new GameDriver(InstrumentationRegistry.getInstrumentation(), view[0],
|
|
minimumDecisionMillis, throttleFinalTraversalOnly);
|
|
}
|
|
|
|
@Override
|
|
public void startLevel(int oneBasedLevelNumber) {
|
|
instrumentation.runOnMainSync(
|
|
() -> gameView.startLevelForAutomation(oneBasedLevelNumber));
|
|
}
|
|
|
|
@Override
|
|
public void setInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
|
instrumentation.runOnMainSync(
|
|
() -> gameView.setAutomationInput(moveLeft, moveRight, jump));
|
|
}
|
|
|
|
@Override
|
|
public GameView.AutomationSnapshot snapshot() {
|
|
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
|
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
|
|
lastSnapshot = value[0];
|
|
return lastSnapshot;
|
|
}
|
|
|
|
@Override
|
|
public void waitForNextDecision(long millis) {
|
|
try {
|
|
boolean inFinalTraversalWindow = throttleFinalTraversalOnly
|
|
&& lastSnapshot != null
|
|
&& lastSnapshot.levelNumber() == 10
|
|
&& lastSnapshot.deaths() == 3
|
|
&& lastSnapshot.playerX() >= 1_200f
|
|
&& lastSnapshot.playerX() <= 2_200f;
|
|
Thread.sleep(inFinalTraversalWindow
|
|
? Math.max(millis, minimumDecisionMillis) : millis);
|
|
} catch (InterruptedException interrupted) {
|
|
Thread.currentThread().interrupt();
|
|
throw new AssertionError("Late-level bot interrupted", interrupted);
|
|
}
|
|
}
|
|
}
|
|
}
|