Добавить первую волну мира Foundations

This commit is contained in:
2026-08-19 10:51:25 +03:00
parent 4869649b47
commit b31c2ea720
18 changed files with 592 additions and 19 deletions
@@ -0,0 +1,84 @@
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 FoundationsPlaythroughTest {
@Test public void firstReturnCompletesDeathlessThreeTimes() {
runThree(FoundationsScenarios.levelNineteen(), 0, 0);
}
@Test public void coveredPathUsesExactlyTwoBodiesThreeTimes() {
runThree(FoundationsScenarios.levelTwenty(), 2, 2);
}
@Test public void heldOpenUsesExactlyOnePlateBodyThreeTimes() {
runThree(FoundationsScenarios.levelTwentyOne(), 1, 1);
}
@Test public void clockworkStepCompletesDeathlessThreeTimes() {
runThree(FoundationsScenarios.levelTwentyTwo(), 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, 30_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)));
}
}
}
@@ -0,0 +1,96 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
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 FoundationsRestartTest {
@Test
public void restartClearsCoveredPathBodiesAndClockworkFerryState() {
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
GameView[] target = new GameView[1];
activity.onActivity(current -> target[0] = current.gameViewForAutomation());
GameView view = target[0];
start(instrumentation, view, 20);
input(instrumentation, view, false, true, false);
GameView.AutomationSnapshot bridge = advanceUntil(instrumentation, view,
snapshot -> snapshot.deaths() == 1 && snapshot.corpseCount() == 1);
assertEquals(1, bridge.deaths());
instrumentation.runOnMainSync(() -> {
view.setAutomationInput(false, true, true);
view.restartLevelForAutomation();
});
assertCleanRestart(snapshot(instrumentation, view), 20);
start(instrumentation, view, 22);
input(instrumentation, view, false, true, false);
GameView.AutomationSnapshot ferry = advanceUntil(instrumentation, view,
GameView.AutomationSnapshot::firstMovingPlatformPowered);
assertTrue(ferry.firstMovingPlatformPowered());
instrumentation.runOnMainSync(() -> {
view.setAutomationInput(false, true, true);
view.restartLevelForAutomation();
});
GameView.AutomationSnapshot restartedFerry = snapshot(instrumentation, view);
assertCleanRestart(restartedFerry, 22);
assertFalse(restartedFerry.firstMovingPlatformPowered());
assertEquals(940f, restartedFerry.firstMovingPlatformX(), 0.5f);
}
}
private static void assertCleanRestart(GameView.AutomationSnapshot snapshot, int level) {
assertEquals(level, snapshot.levelNumber());
assertTrue(snapshot.playerAlive());
assertEquals(0, snapshot.deaths());
assertEquals(0, snapshot.corpseCount());
assertFalse(snapshot.collectibleFound());
assertFalse(snapshot.inputActive());
assertTrue(snapshot.elapsedMilliseconds() < 100L);
assertTrue(snapshot.playerX() >= 125f && snapshot.playerX() <= 135f);
}
private static void start(Instrumentation instrumentation, GameView view, int level) {
instrumentation.runOnMainSync(() -> {
view.enableDeterministicAutomationClock();
view.startLevelForAutomation(level);
});
}
private static void input(Instrumentation instrumentation, GameView view,
boolean left, boolean right, boolean jump) {
instrumentation.runOnMainSync(() -> view.setAutomationInput(left, right, jump));
}
private static GameView.AutomationSnapshot advanceUntil(
Instrumentation instrumentation, GameView view,
LevelPlaytestBot.SnapshotCondition condition) {
GameView.AutomationSnapshot current = snapshot(instrumentation, view);
for (int tick = 0; tick < 100; tick++) {
if (condition.reached(current)) return current;
instrumentation.runOnMainSync(() -> view.advanceDeterministicAutomation(96L));
current = snapshot(instrumentation, view);
}
throw new AssertionError("Foundations restart setup timed out; level="
+ current.levelNumber() + ", x=" + current.playerX()
+ ", deaths=" + current.deaths() + ", corpses=" + current.corpseCount());
}
private static GameView.AutomationSnapshot snapshot(
Instrumentation instrumentation, GameView view) {
GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
instrumentation.runOnMainSync(() -> value[0] = view.automationSnapshot());
return value[0];
}
}
@@ -24,7 +24,7 @@ public final class LevelPaginationTest {
GameView view = current.gameViewForAutomation();
view.setLanguageForAutomation(GameLanguage.EN);
assertEquals(0, view.levelPageIndexForAutomation());
assertEquals(2, view.levelPageCountForAutomation());
assertEquals(3, view.levelPageCountForAutomation());
assertTrue(view.visibleLocalizedTextForAutomation().contains("The Volunteer"));
assertFalse(view.visibleLocalizedTextForAutomation().contains("Steam Charge"));
});
@@ -39,10 +39,27 @@ public final class LevelPaginationTest {
assertTrue(visible.contains("Ashen Counterweight"));
assertTrue(visible.contains("The Whole Weight"));
assertTrue(visible.contains("Threefold Proof"));
assertTrue(visible.contains("First Return"));
assertTrue(visible.contains("Covered Path"));
assertFalse(visible.contains("Held Open"));
assertFalse(visible.contains("The Volunteer"));
assertTrue(visible.contains("Page 2 of 2"));
assertTrue(visible.contains("Page 2 of 3"));
});
tap(activity, 1_730f, 965f);
activity.onActivity(current -> {
GameView view = current.gameViewForAutomation();
List<String> visible = view.visibleLocalizedTextForAutomation();
assertEquals(2, view.levelPageIndexForAutomation());
assertTrue(visible.contains("Held Open"));
assertTrue(visible.contains("Clockwork Step"));
assertFalse(visible.contains("Covered Path"));
assertTrue(visible.contains("Page 3 of 3"));
});
tap(activity, 190f, 965f);
activity.onActivity(current ->
assertEquals(1, current.gameViewForAutomation().levelPageIndexForAutomation()));
tap(activity, 190f, 965f);
activity.onActivity(current ->
assertEquals(0, current.gameViewForAutomation().levelPageIndexForAutomation()));