Добавить вторую волну мира Foundations
This commit is contained in:
+88
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class FoundationsAdvancedPlaythroughTest {
|
||||
@Test public void remoteVolunteerUsesOneDeliveredBodyThreeTimes() {
|
||||
runThree(AdvancedFoundationsScenarios.levelTwentyThree(), 1, 1);
|
||||
}
|
||||
|
||||
@Test public void frozenStepUsesOneFrozenBodyThreeTimes() {
|
||||
runThree(AdvancedFoundationsScenarios.levelTwentyFour(), 1, 1);
|
||||
}
|
||||
|
||||
@Test public void livingWireUsesOneConductiveBodyThreeTimes() {
|
||||
runThree(AdvancedFoundationsScenarios.levelTwentyFive(), 1, 1);
|
||||
}
|
||||
|
||||
@Test public void secondHandCompletesOnTheOriginalTimerThreeTimes() {
|
||||
runThree(AdvancedFoundationsScenarios.levelTwentySixIntended(), 0, 0);
|
||||
}
|
||||
|
||||
@Test public void secondHandRecoversAfterTheFarGateClosesThreeTimes() {
|
||||
runThree(AdvancedFoundationsScenarios.levelTwentySixRecovery(), 0, 0);
|
||||
}
|
||||
|
||||
private static void runThree(LevelPlaytestBot.Scenario scenario,
|
||||
int expectedDeaths, int expectedCorpses) {
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
Driver driver = Driver.from(activity);
|
||||
for (int run = 1; run <= 3; run++) {
|
||||
LevelPlaytestBot.Result result = new LevelPlaytestBot().run(driver, scenario, 45_000L);
|
||||
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
|
||||
assertEquals("run=" + run, expectedDeaths, result.deaths());
|
||||
assertEquals("run=" + run, expectedCorpses, result.corpseCount());
|
||||
assertTrue("run=" + run + "; collectible", driver.snapshot().collectibleFound());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Driver implements LevelPlaytestBot.Driver {
|
||||
private final Instrumentation instrumentation;
|
||||
private final GameView view;
|
||||
|
||||
private Driver(Instrumentation instrumentation, GameView view) {
|
||||
this.instrumentation = instrumentation;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
static Driver from(ActivityScenario<MainActivity> activity) {
|
||||
GameView[] target = new GameView[1];
|
||||
activity.onActivity(current -> target[0] = current.gameViewForAutomation());
|
||||
return new Driver(InstrumentationRegistry.getInstrumentation(), target[0]);
|
||||
}
|
||||
|
||||
@Override public void startLevel(int oneBasedLevelNumber) {
|
||||
instrumentation.runOnMainSync(() -> {
|
||||
view.enableDeterministicAutomationClock();
|
||||
view.startLevelForAutomation(oneBasedLevelNumber);
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void setInput(boolean left, boolean right, boolean jump) {
|
||||
instrumentation.runOnMainSync(() -> view.setAutomationInput(left, right, jump));
|
||||
}
|
||||
|
||||
@Override public GameView.AutomationSnapshot snapshot() {
|
||||
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
|
||||
instrumentation.runOnMainSync(() -> value[0] = view.automationSnapshot());
|
||||
return value[0];
|
||||
}
|
||||
|
||||
@Override public void waitForNextDecision(long millis) {
|
||||
instrumentation.runOnMainSync(() ->
|
||||
view.advanceDeterministicAutomation(Math.max(16L, millis)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,10 @@ public final class LevelPaginationTest {
|
||||
assertEquals(2, view.levelPageIndexForAutomation());
|
||||
assertTrue(visible.contains("Held Open"));
|
||||
assertTrue(visible.contains("Clockwork Step"));
|
||||
assertTrue(visible.contains("Remote Volunteer"));
|
||||
assertTrue(visible.contains("Frozen Step"));
|
||||
assertTrue(visible.contains("Living Wire"));
|
||||
assertTrue(visible.contains("Second Hand"));
|
||||
assertFalse(visible.contains("Covered Path"));
|
||||
assertTrue(visible.contains("Page 3 of 3"));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
/** Production-input routes for Foundations levels 23-26. */
|
||||
public final class AdvancedFoundationsScenarios {
|
||||
private AdvancedFoundationsScenarios() {}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelTwentyThree() {
|
||||
return LevelPlaytestBot.Scenario.builder(23, "Level 23 - remote volunteer")
|
||||
.step("Launch one volunteer onto the remote plate", 4_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.deaths() == 1
|
||||
&& snapshot.corpseCount() == 1))
|
||||
.step("Observe the delivered body open the door", 2_500L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
atMost(1, GameView.AutomationSnapshot::allDoorsOpen))
|
||||
.step("Wait for the crossing volunteer", 2_000L,
|
||||
LevelPlaytestBot.Input.none(), atMost(1, AdvancedFoundationsScenarios::respawned))
|
||||
.step("Stage before the launch saw", 2_500L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
atMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(420f)))
|
||||
.step("Take off before the launch saw", 1_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& !snapshot.playerOnGround() && snapshot.playerY() <= 620f))
|
||||
.step("Clear the launch saw", 2_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive() && snapshot.playerX() >= 740f))
|
||||
.step("Approach the delivered body", 1_500L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(780f)))
|
||||
.step("Jump over the body and collect the cache", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.collectibleFound()))
|
||||
.step("Pass the remote gate", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(1_650f)))
|
||||
.step("Reach the relic", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelTwentyFour() {
|
||||
return LevelPlaytestBot.Scenario.builder(24, "Level 24 - frozen step")
|
||||
.step("Freeze one volunteer", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.deaths() == 1
|
||||
&& snapshot.corpseCount() == 1))
|
||||
.step("Wait for the climbing volunteer", 2_000L,
|
||||
LevelPlaytestBot.Input.none(), atMost(1, AdvancedFoundationsScenarios::respawned))
|
||||
.step("Approach the frozen block", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.playerX() >= 420f))
|
||||
.step("Jump toward the frozen block", 1_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerX() >= 670f))
|
||||
.step("Settle on the frozen block", 2_000L, LevelPlaytestBot.Input.none(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerOnGround()
|
||||
&& snapshot.playerY() >= 668f && snapshot.playerY() <= 690f))
|
||||
.step("Jump to the upper route", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 850f && snapshot.playerY() <= 500f))
|
||||
.step("Cross the ledge and collect the cache", 3_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.collectibleFound() && snapshot.playerX() >= 1_300f))
|
||||
.step("Jump to the final ledge", 2_500L, LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerX() >= 1_480f && snapshot.playerY() <= 520f))
|
||||
.step("Reach the upper relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelTwentyFive() {
|
||||
return LevelPlaytestBot.Scenario.builder(25, "Level 25 - living wire")
|
||||
.step("Power the relay with one volunteer", 4_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.deaths() == 1
|
||||
&& snapshot.corpseCount() == 1))
|
||||
.step("Observe the conductive body open the door", 2_000L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
atMost(1, GameView.AutomationSnapshot::allDoorsOpen))
|
||||
.step("Wait for the crossing volunteer", 2_000L,
|
||||
LevelPlaytestBot.Input.none(), atMost(1, AdvancedFoundationsScenarios::respawned))
|
||||
.step("Stage before the live relay", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(520f)))
|
||||
.step("Take off before the live relay", 1_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& !snapshot.playerOnGround() && snapshot.playerY() <= 680f))
|
||||
.step("Clear the conductive body and collect the cache", 3_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
atMost(1, snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerX() >= 1_080f && snapshot.collectibleFound()))
|
||||
.step("Pass the powered door", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(1_470f)))
|
||||
.step("Reach the relic", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
atMost(1, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelTwentySixIntended() {
|
||||
return timedRoute(false);
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelTwentySixRecovery() {
|
||||
return timedRoute(true);
|
||||
}
|
||||
|
||||
private static LevelPlaytestBot.Scenario timedRoute(boolean recovery) {
|
||||
LevelPlaytestBot.Scenario.Builder route = LevelPlaytestBot.Scenario.builder(26,
|
||||
recovery ? "Level 26 - far-side recovery" : "Level 26 - original timer");
|
||||
route.step("Start the second-hand timer", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
noDeaths(snapshot -> snapshot.allDoorsOpen()
|
||||
&& snapshot.firstMovingPlatformPowered()))
|
||||
.step("Approach the timed ferry", 2_000L, LevelPlaytestBot.Input.right(),
|
||||
noDeaths(LevelPlaytestBot.groundedAliveAtOrBeyond(800f)))
|
||||
.step("Wait for the outbound boarding phase", 3_500L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
noDeaths(snapshot -> MovingPlatformLaunchPolicy.readyToBoard(snapshot, 1_000f)))
|
||||
.step("Board the timed ferry", 2_500L, LevelPlaytestBot.Input.rightJump(),
|
||||
noDeaths(snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerOnGround()
|
||||
&& snapshot.playerOnMovingPlatform()))
|
||||
.step("Ride to the far launch phase", 4_000L, LevelPlaytestBot.Input.none(),
|
||||
noDeaths(snapshot -> MovingPlatformLaunchPolicy.ready(snapshot, 1_250f)))
|
||||
.step("Jump to the far floor", 3_000L, LevelPlaytestBot.Input.rightJump(),
|
||||
noDeaths(LevelPlaytestBot.groundedAliveAtOrBeyond(1_600f)));
|
||||
if (recovery) {
|
||||
route.step("Move beneath the high recovery relay", 2_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
noDeaths(LevelPlaytestBot.groundedAliveAtOrBeyond(1_650f)))
|
||||
.step("Wait for the far gate to close", 10_000L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
noDeaths(snapshot -> !snapshot.allDoorsOpen()))
|
||||
.step("Jump into the recovery relay", 2_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
noDeaths(GameView.AutomationSnapshot::allDoorsOpen));
|
||||
} else {
|
||||
route.step("Pass the door on the original timer", 3_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
noDeaths(LevelPlaytestBot.groundedAliveAtOrBeyond(2_030f)));
|
||||
}
|
||||
return route.step("Stage before the upper route", 2_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
noDeaths(LevelPlaytestBot.groundedAliveAtOrBeyond(2_050f)))
|
||||
.step("Jump onto the upper route", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
noDeaths(snapshot -> snapshot.playerAlive()
|
||||
&& snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 2_250f
|
||||
&& snapshot.playerY() <= 610f))
|
||||
.step("Collect the upper cache", 2_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
noDeaths(snapshot -> snapshot.playerAlive() && snapshot.collectibleFound()))
|
||||
.step("Reach the upper relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
noDeaths(GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static LevelPlaytestBot.SnapshotCondition atMost(
|
||||
int deaths, LevelPlaytestBot.SnapshotCondition condition) {
|
||||
return LevelPlaytestBot.whileDeathsAtMost(deaths, condition);
|
||||
}
|
||||
|
||||
private static LevelPlaytestBot.SnapshotCondition noDeaths(
|
||||
LevelPlaytestBot.SnapshotCondition condition) {
|
||||
return atMost(0, condition);
|
||||
}
|
||||
|
||||
private static boolean respawned(GameView.AutomationSnapshot snapshot) {
|
||||
return snapshot.playerAlive() && snapshot.playerX() < 250f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class FoundationsAdvancedContentTest {
|
||||
@Test
|
||||
public void secondFoundationsWaveMatchesMatrixAndPreservesRecoveryContracts() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(26, levels.size());
|
||||
|
||||
LevelDefinition remote = levels.get(22);
|
||||
assertIdentity(remote, 23, "foundations.remote-volunteer", "Remote Volunteer", 1, 50);
|
||||
assertTrue(remote.containsMechanic(LevelDefinition.HazardType.SAW));
|
||||
assertTrue(remote.containsMechanic(LevelDefinition.SwitchType.PRESSURE));
|
||||
assertFalse(remote.doors.isEmpty());
|
||||
|
||||
LevelDefinition frozen = levels.get(23);
|
||||
assertIdentity(frozen, 24, "foundations.frozen-step", "Frozen Step", 1, 48);
|
||||
assertTrue(frozen.containsMechanic(LevelDefinition.HazardType.FREEZER));
|
||||
assertTrue(frozen.goal.y < 700f);
|
||||
|
||||
LevelDefinition live = levels.get(24);
|
||||
assertIdentity(live, 25, "foundations.living-wire", "Living Wire", 1, 52);
|
||||
assertTrue(live.containsMechanic(LevelDefinition.HazardType.ELECTRIC));
|
||||
assertFalse(live.doors.isEmpty());
|
||||
|
||||
LevelDefinition timed = levels.get(25);
|
||||
assertIdentity(timed, 26, "foundations.borrowed-time", "Second Hand", 0, 55);
|
||||
assertTrue(timed.containsMechanic(LevelDefinition.SwitchType.TIMED));
|
||||
assertTrue(timed.containsMechanic(LevelDefinition.HazardType.PIT));
|
||||
assertTrue(timed.hasMovingPlatform());
|
||||
assertFalse(timed.doors.isEmpty());
|
||||
assertEquals(2, timed.switches.size());
|
||||
Set<String> signals = new HashSet<>();
|
||||
for (LevelDefinition.Switch item : timed.switches) signals.add(item.signalId);
|
||||
assertEquals(Set.of(timed.platforms.get(3).controlId), signals);
|
||||
}
|
||||
|
||||
private static void assertIdentity(LevelDefinition level, int number, String id, String title,
|
||||
int targetDeaths, int targetSeconds) {
|
||||
assertEquals(number, level.number);
|
||||
assertEquals(id, level.levelId);
|
||||
assertEquals("foundations", level.worldId);
|
||||
assertEquals(title, level.title);
|
||||
assertEquals(targetDeaths, level.targetDeaths);
|
||||
assertEquals(targetSeconds, level.targetSeconds);
|
||||
assertNotNull(level.collectible);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public final class FoundationsContentTest {
|
||||
@Test
|
||||
public void firstFoundationsWaveMatchesTheApprovedMatrixAndTeachesOneIdeaAtATime() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(22, levels.size());
|
||||
assertTrue(levels.size() >= 22);
|
||||
|
||||
LevelDefinition firstReturn = levels.get(18);
|
||||
assertIdentity(firstReturn, 19, "foundations.first-return", "First Return", 0, 28);
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class LevelCatalogTest {
|
||||
@Test
|
||||
public void productionCatalogContainsMechanicsAndChallengeTeachingLevels() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(22, levels.size());
|
||||
assertEquals(26, levels.size());
|
||||
Set<String> titles = new HashSet<>();
|
||||
for (int i = 0; i < levels.size(); i++) {
|
||||
LevelDefinition level = levels.get(i);
|
||||
|
||||
Reference in New Issue
Block a user