Добавить общую систему весовых объектов

This commit is contained in:
2026-08-19 07:40:11 +03:00
parent 19bb64edfb
commit 259fcd316b
32 changed files with 1017 additions and 34 deletions
@@ -37,6 +37,7 @@ public final class LevelPaginationTest {
assertEquals(1, view.levelPageIndexForAutomation());
assertTrue(visible.contains("Steam Charge"));
assertTrue(visible.contains("Ashen Counterweight"));
assertTrue(visible.contains("The Whole Weight"));
assertFalse(visible.contains("The Volunteer"));
assertTrue(visible.contains("Page 2 of 2"));
});
@@ -0,0 +1,42 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public final class WeightedObjectIntegrationTest {
@Test
public void productionInputPushesSharedMassOntoPlateAndSaveCapturesEveryObject() {
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
activity.onActivity(current -> {
GameView view = current.gameViewForAutomation();
view.enableDeterministicAutomationClock();
view.startLevelForAutomation(17);
GameView.AutomationSnapshot initial = view.automationSnapshot();
assertEquals(5, initial.physicalObjectCount());
assertEquals("COUNTERWEIGHT", initial.firstPhysicalObjectType());
assertEquals(3, initial.firstPhysicalObjectMass());
assertFalse(initial.allDoorsOpen());
view.setAutomationInput(false, true, false);
view.advanceDeterministicAutomation(3_000L);
GameView.AutomationSnapshot pushed = view.automationSnapshot();
assertTrue(pushed.firstPhysicalObjectX() >= 920f);
assertTrue(pushed.allDoorsOpen());
GameSessionSnapshot saved = view.sessionSnapshotForAutomation();
assertEquals(5, saved.objects().size());
assertEquals(LevelDefinition.PhysicalObjectType.COUNTERWEIGHT,
saved.objects().get(0).type());
});
}
}
}
@@ -0,0 +1,69 @@
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 WeightedObjectPlaythroughTest {
@Test
public void allFiveObjectTypesOpenTheMassNineGateThreeTimes() {
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, WeightedObjectScenarios.levelSeventeen(), 50_000L);
assertTrue("run=" + run + "; " + result.diagnostic(), result.completed());
assertEquals("weighted route is deathless", 0, result.deaths());
assertEquals("weighted route does not create bodies", 0, result.corpseCount());
}
}
}
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.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[] snapshot = new GameView.AutomationSnapshot[1];
instrumentation.runOnMainSync(() -> snapshot[0] = view.automationSnapshot());
return snapshot[0];
}
@Override public void waitForNextDecision(long millis) {
try {
Thread.sleep(Math.max(32L, millis));
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Weighted-object bot interrupted", interrupted);
}
}
}
}
@@ -0,0 +1,52 @@
package com.projectsacrifice.game;
/** Production-input route for the weighted-object teaching puzzle. */
final class WeightedObjectScenarios {
private WeightedObjectScenarios() {}
static LevelPlaytestBot.Scenario levelSeventeen() {
return LevelPlaytestBot.Scenario.builder(17, "Level 17 - shared mass gate")
.step("Observe all authored weight types", 1_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.physicalObjectCount() == 5
&& snapshot.firstPhysicalObjectMass() == 3
&& "COUNTERWEIGHT".equals(snapshot.firstPhysicalObjectType())
&& !snapshot.allDoorsOpen())
.step("Approach the counterweight", 2_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(0,
LevelPlaytestBot.groundedAliveAtOrBeyond(520f)))
.step("Push the missing mass onto the plate", 3_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(0,
snapshot -> snapshot.playerAlive()
&& snapshot.firstPhysicalObjectX() >= 920f
&& snapshot.allDoorsOpen()))
.step("Release input while object mass holds the gate", 1_000L,
LevelPlaytestBot.Input.none(),
snapshot -> snapshot.allDoorsOpen()
&& snapshot.firstPhysicalObjectX() >= 920f)
.step("Take off over the counterweight", 1_000L,
LevelPlaytestBot.Input.rightJump(),
LevelPlaytestBot.whileDeathsAtMost(0,
snapshot -> snapshot.playerAlive()
&& !snapshot.playerOnGround()
&& snapshot.playerY() <= 700f))
.step("Land beyond the counterweight", 2_500L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(0,
snapshot -> snapshot.playerAlive()
&& snapshot.playerOnGround()
&& snapshot.playerX() >= 1_080f))
.step("Jump over the weighted row", 2_500L,
LevelPlaytestBot.Input.rightJump(),
LevelPlaytestBot.whileDeathsAtMost(0,
snapshot -> snapshot.playerAlive()
&& snapshot.playerX() >= 1_650f))
.step("Pass the mass-nine door and reach the relic", 5_000L,
LevelPlaytestBot.Input.right(),
LevelPlaytestBot.whileDeathsAtMost(0,
GameView.AutomationSnapshot::completed))
.build();
}
}