Доказать прохождение уровней 1–4 ботами
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
package com.projectsacrifice.game;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/** Production-input acceptance coverage for the intended routes through levels 1-4. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class EarlyLevelPlaythroughTest {
|
||||||
|
@Test
|
||||||
|
public void levelOneProvesMovementAndJumpWithoutDeaths() {
|
||||||
|
for (int pass = 1; pass <= 3; pass++) {
|
||||||
|
LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelOne(), 20_000L);
|
||||||
|
assertTrue(passDiagnostic(pass, result), result.completed());
|
||||||
|
assertEquals("Pass " + pass + ": level 1 must not require a volunteer",
|
||||||
|
0, result.deaths());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void levelTwoBuildsTheIntendedThreeCorpseBridge() {
|
||||||
|
for (int pass = 1; pass <= 3; pass++) {
|
||||||
|
LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelTwo(), 30_000L);
|
||||||
|
assertTrue(passDiagnostic(pass, result), result.completed());
|
||||||
|
assertEquals("Pass " + pass + ": the intended bridge uses three volunteers",
|
||||||
|
3, result.deaths());
|
||||||
|
assertEquals("Pass " + pass + ": all three bridge bodies must remain",
|
||||||
|
3, result.corpseCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void levelThreeLeavesASawCorpseOnThePlateAndOpensTheDoor() {
|
||||||
|
for (int pass = 1; pass <= 3; pass++) {
|
||||||
|
LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelThree(), 25_000L);
|
||||||
|
assertTrue(passDiagnostic(pass, result), result.completed());
|
||||||
|
assertEquals("Pass " + pass + ": the saw solution requires one volunteer",
|
||||||
|
1, result.deaths());
|
||||||
|
assertEquals("Pass " + pass + ": the plate body must remain",
|
||||||
|
1, result.corpseCount());
|
||||||
|
assertTrue("Pass " + pass + ": the corpse must keep the pressure door open",
|
||||||
|
result.doorOpened());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void levelFourTogglesAndRidesTheLiftToTheUpperGallery() {
|
||||||
|
for (int pass = 1; pass <= 3; pass++) {
|
||||||
|
LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelFour(), 30_000L);
|
||||||
|
assertTrue(passDiagnostic(pass, result), result.completed());
|
||||||
|
assertEquals("Pass " + pass + ": the lift route must not require a volunteer",
|
||||||
|
0, result.deaths());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String passDiagnostic(int pass, LevelPlaytestBot.Result result) {
|
||||||
|
return "Consecutive pass " + pass + ": " + result.diagnostic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LevelPlaytestBot.Result run(LevelPlaytestBot.Scenario scenario, long timeoutMillis) {
|
||||||
|
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||||
|
final GameView[] view = new GameView[1];
|
||||||
|
activity.onActivity(mainActivity -> view[0] = mainActivity.gameViewForAutomation());
|
||||||
|
return new LevelPlaytestBot().run(
|
||||||
|
new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0]),
|
||||||
|
scenario,
|
||||||
|
timeoutMillis
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AndroidDriver implements LevelPlaytestBot.Driver {
|
||||||
|
private final Instrumentation instrumentation;
|
||||||
|
private final GameView gameView;
|
||||||
|
|
||||||
|
AndroidDriver(Instrumentation instrumentation, GameView gameView) {
|
||||||
|
this.instrumentation = instrumentation;
|
||||||
|
this.gameView = gameView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
final GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
||||||
|
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void waitForNextDecision(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new AssertionError("Early-level bot interrupted", interrupted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.projectsacrifice.game;
|
||||||
|
|
||||||
|
/** Declarative intended-route scenarios for the first four teaching levels. */
|
||||||
|
public final class EarlyLevelScenarios {
|
||||||
|
private EarlyLevelScenarios() {}
|
||||||
|
|
||||||
|
public static LevelPlaytestBot.Scenario levelOne() {
|
||||||
|
return LevelPlaytestBot.Scenario.builder(1, "Level 1: movement and jump")
|
||||||
|
.step("Approach the first ledge", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 500f)
|
||||||
|
.step("Jump over the first ledge", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 900f)
|
||||||
|
.step("Release jump before the upper ledge", 1_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 950f)
|
||||||
|
.step("Jump over the upper ledge", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_330f)
|
||||||
|
.step("Reach the relic", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
GameView.AutomationSnapshot::completed)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelPlaytestBot.Scenario levelTwo() {
|
||||||
|
return LevelPlaytestBot.Scenario.builder(2, "Level 2: three-corpse bridge")
|
||||||
|
.step("Place the first bridge body", 4_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.deaths() >= 1)
|
||||||
|
.step("Wait for the second volunteer", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
EarlyLevelScenarios::respawnedAtStart)
|
||||||
|
.step("Approach the first bridge body", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 470f)
|
||||||
|
.step("Jump to the middle of the spikes", 2_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 730f)
|
||||||
|
.step("Place the second bridge body", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.deaths() >= 2)
|
||||||
|
.step("Wait for the third volunteer", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
EarlyLevelScenarios::respawnedAtStart)
|
||||||
|
.step("Return to the corpse bridge", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 470f)
|
||||||
|
.step("Jump to the far side of the spikes", 2_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 750f)
|
||||||
|
.step("Step off the middle bridge body", 2_000L, LevelPlaytestBot.Input.left(),
|
||||||
|
snapshot -> snapshot.playerX() <= 690f)
|
||||||
|
.step("Place the third bridge body", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.deaths() >= 3)
|
||||||
|
.step("Wait for the crossing volunteer", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
EarlyLevelScenarios::respawnedAtStart)
|
||||||
|
.step("Approach the completed bridge", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 470f)
|
||||||
|
.step("Cross the corpse bridge", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 900f)
|
||||||
|
.step("Release jump before the final ledge", 2_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_030f)
|
||||||
|
.step("Jump over the final ledge", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_450f)
|
||||||
|
.step("Reach the relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
GameView.AutomationSnapshot::completed)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelPlaytestBot.Scenario levelThree() {
|
||||||
|
return LevelPlaytestBot.Scenario.builder(3, "Level 3: saw corpse pressure signal")
|
||||||
|
.step("Leave a volunteer at the saw", 4_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.deaths() >= 1)
|
||||||
|
.step("Prove the corpse opens the pressure door", 3_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
GameView.AutomationSnapshot::allDoorsOpen)
|
||||||
|
.step("Wait for the next volunteer", 2_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
EarlyLevelScenarios::respawnedAtStart)
|
||||||
|
.step("Approach the saw", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 390f)
|
||||||
|
.step("Jump over the saw and plate body", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 700f)
|
||||||
|
.step("Reach the relic through the open door", 5_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
GameView.AutomationSnapshot::completed)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelPlaytestBot.Scenario levelFour() {
|
||||||
|
return LevelPlaytestBot.Scenario.builder(4, "Level 4: toggle lift to gallery")
|
||||||
|
.step("Cross the toggle once and activate the lift", 3_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 800f)
|
||||||
|
.step("Jump onto the moving lift", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_050f && snapshot.playerY() < 690f)
|
||||||
|
.step("Ride the lift to its upper phase", 7_000L, LevelPlaytestBot.Input.none(),
|
||||||
|
snapshot -> snapshot.playerY() <= 330f)
|
||||||
|
.step("Move to the lift's right edge", 2_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_225f)
|
||||||
|
.step("Jump from the lift to the upper gallery", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||||
|
snapshot -> snapshot.playerX() >= 1_610f && snapshot.playerY() <= 400f)
|
||||||
|
.step("Reach the upper relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||||
|
GameView.AutomationSnapshot::completed)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean respawnedAtStart(GameView.AutomationSnapshot snapshot) {
|
||||||
|
return snapshot.playerAlive() && snapshot.playerX() < 250f;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user