Добавить первую волну мира Physics
This commit is contained in:
@@ -24,7 +24,7 @@ public final class LevelPaginationTest {
|
||||
GameView view = current.gameViewForAutomation();
|
||||
view.setLanguageForAutomation(GameLanguage.EN);
|
||||
assertEquals(0, view.levelPageIndexForAutomation());
|
||||
assertEquals(3, view.levelPageCountForAutomation());
|
||||
assertEquals(4, view.levelPageCountForAutomation());
|
||||
assertTrue(view.visibleLocalizedTextForAutomation().contains("The Volunteer"));
|
||||
assertFalse(view.visibleLocalizedTextForAutomation().contains("Steam Charge"));
|
||||
});
|
||||
@@ -43,7 +43,7 @@ public final class LevelPaginationTest {
|
||||
assertTrue(visible.contains("Covered Path"));
|
||||
assertFalse(visible.contains("Held Open"));
|
||||
assertFalse(visible.contains("The Volunteer"));
|
||||
assertTrue(visible.contains("Page 2 of 3"));
|
||||
assertTrue(visible.contains("Page 2 of 4"));
|
||||
});
|
||||
|
||||
tap(activity, 1_730f, 965f);
|
||||
@@ -62,9 +62,25 @@ public final class LevelPaginationTest {
|
||||
assertTrue(visible.contains("Minimum Cost"));
|
||||
assertTrue(visible.contains("Foundation Relay"));
|
||||
assertFalse(visible.contains("Covered Path"));
|
||||
assertTrue(visible.contains("Page 3 of 3"));
|
||||
assertTrue(visible.contains("Page 3 of 4"));
|
||||
});
|
||||
|
||||
tap(activity, 1_730f, 965f);
|
||||
activity.onActivity(current -> {
|
||||
GameView view = current.gameViewForAutomation();
|
||||
List<String> visible = view.visibleLocalizedTextForAutomation();
|
||||
assertEquals(3, view.levelPageIndexForAutomation());
|
||||
assertTrue(visible.contains("Weight Class"));
|
||||
assertTrue(visible.contains("Stack Order"));
|
||||
assertTrue(visible.contains("Launch Window"));
|
||||
assertTrue(visible.contains("Press Cycle"));
|
||||
assertFalse(visible.contains("Foundation Relay"));
|
||||
assertTrue(visible.contains("Page 4 of 4"));
|
||||
});
|
||||
|
||||
tap(activity, 190f, 965f);
|
||||
activity.onActivity(current ->
|
||||
assertEquals(2, current.gameViewForAutomation().levelPageIndexForAutomation()));
|
||||
tap(activity, 190f, 965f);
|
||||
activity.onActivity(current ->
|
||||
assertEquals(1, current.gameViewForAutomation().levelPageIndexForAutomation()));
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
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 PhysicsWaveOnePlaythroughTest {
|
||||
@Test public void weightClassOpensFromObjectMassThreeTimes() {
|
||||
try (ActivityScenario<MainActivity> activity = ActivityScenario.launch(MainActivity.class)) {
|
||||
Driver driver = Driver.from(activity);
|
||||
for (int run = 1; run <= 3; run++) {
|
||||
assertAccepted(run, new LevelPlaytestBot().run(
|
||||
driver, PhysicsWaveOneScenarios.levelThirtyOne(), 55_000L),
|
||||
driver, 0, 0);
|
||||
}
|
||||
driver.restart();
|
||||
GameView.AutomationSnapshot restarted = driver.snapshot();
|
||||
assertEquals(31, restarted.levelNumber());
|
||||
assertEquals(650f, restarted.firstPhysicalObjectX(), 0f);
|
||||
assertEquals(780f, restarted.firstPhysicalObjectY(), 0f);
|
||||
assertEquals(0, restarted.deaths());
|
||||
assertEquals(0, restarted.corpseCount());
|
||||
assertFalse(restarted.allDoorsOpen());
|
||||
assertFalse(restarted.collectibleFound());
|
||||
assertFalse(restarted.inputActive());
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void stackOrderClimbsSupportedObjectsThreeTimes() {
|
||||
runThree(PhysicsWaveOneScenarios.levelThirtyTwo(), 0, 0);
|
||||
}
|
||||
|
||||
@Test public void launchWindowUsesOneSawVolunteerAndLiftThreeTimes() {
|
||||
runThree(PhysicsWaveOneScenarios.levelThirtyThree(), 1, 1);
|
||||
}
|
||||
|
||||
@Test public void pressCycleLeavesOneCrushedBodyThreeTimes() {
|
||||
runThree(PhysicsWaveOneScenarios.levelThirtyFour(), 1, 1);
|
||||
}
|
||||
|
||||
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, 55_000L);
|
||||
assertAccepted(run, result, driver, expectedDeaths, expectedCorpses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertAccepted(int run, LevelPlaytestBot.Result result, Driver driver,
|
||||
int expectedDeaths, int expectedCorpses) {
|
||||
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)));
|
||||
}
|
||||
|
||||
void restart() {
|
||||
instrumentation.runOnMainSync(view::restartLevelForAutomation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -906,8 +906,9 @@ public final class GameView extends View implements Runnable {
|
||||
object.vy += GRAVITY * dt;
|
||||
object.grounded = false;
|
||||
if (object.vx != 0f) movePhysicalObjectHorizontal(object, object.vx * dt);
|
||||
float previousY = object.y;
|
||||
object.y += object.vy * dt;
|
||||
resolvePhysicalObjectVertical(object);
|
||||
resolvePhysicalObjectVertical(object, previousY);
|
||||
if (object.grounded) object.vx *= (float) Math.pow(0.04, dt);
|
||||
if (Math.abs(object.vx) < 3f) object.vx = 0f;
|
||||
}
|
||||
@@ -949,7 +950,7 @@ public final class GameView extends View implements Runnable {
|
||||
return Math.abs(object.x - before) + 0.01f >= Math.abs(amount);
|
||||
}
|
||||
|
||||
private void resolvePhysicalObjectVertical(PhysicalObjectState object) {
|
||||
private void resolvePhysicalObjectVertical(PhysicalObjectState object, float previousY) {
|
||||
object.supportIndex = -1;
|
||||
RectF current = physicalObjectRect(object);
|
||||
for (int i = 0; i < platformStates.size(); i++) {
|
||||
@@ -987,6 +988,7 @@ public final class GameView extends View implements Runnable {
|
||||
object.vy = 0f;
|
||||
current = physicalObjectRect(object);
|
||||
}
|
||||
PhysicalObjectStackResolver.settleFalling(object, previousY, physicalObjects);
|
||||
}
|
||||
|
||||
private void updateCorpses(float dt) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
/** Resolves a falling authored object onto the first crossed object beneath it. */
|
||||
final class PhysicalObjectStackResolver {
|
||||
private static final float CONTACT_EPSILON = 1.5f;
|
||||
|
||||
private PhysicalObjectStackResolver() {}
|
||||
|
||||
static boolean settleFalling(PhysicalObjectState moving, float previousY,
|
||||
Iterable<PhysicalObjectState> objects) {
|
||||
if (moving == null || objects == null || moving.vy < 0f) return false;
|
||||
float previousBottom = previousY + moving.height;
|
||||
float currentBottom = moving.y + moving.height;
|
||||
PhysicalObjectState nearest = null;
|
||||
float nearestTop = Float.POSITIVE_INFINITY;
|
||||
for (PhysicalObjectState support : objects) {
|
||||
if (support == null || support == moving) continue;
|
||||
if (moving.x >= support.x + support.width
|
||||
|| moving.x + moving.width <= support.x) continue;
|
||||
if (previousBottom > support.y + CONTACT_EPSILON) continue;
|
||||
if (currentBottom + CONTACT_EPSILON < support.y) continue;
|
||||
if (moving.y >= support.y) continue;
|
||||
if (support.y < nearestTop) {
|
||||
nearest = support;
|
||||
nearestTop = support.y;
|
||||
}
|
||||
}
|
||||
if (nearest == null) return false;
|
||||
moving.y = nearest.y - moving.height;
|
||||
moving.vy = 0f;
|
||||
moving.grounded = true;
|
||||
moving.supportIndex = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
/** Production-input routes for the first Physics-world authoring wave. */
|
||||
public final class PhysicsWaveOneScenarios {
|
||||
private PhysicsWaveOneScenarios() {}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelThirtyOne() {
|
||||
return LevelPlaytestBot.Scenario.builder(31, "Level 31 - weight class")
|
||||
.step("Observe the mass-three object", 1_000L, LevelPlaytestBot.Input.none(),
|
||||
snapshot -> snapshot.physicalObjectCount() == 1
|
||||
&& snapshot.firstPhysicalObjectMass() == 3
|
||||
&& !snapshot.allDoorsOpen())
|
||||
.step("Approach the counterweight", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, LevelPlaytestBot.groundedAliveAtOrBeyond(520f)))
|
||||
.step("Push mass onto the plate", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, snapshot -> snapshot.firstPhysicalObjectX() >= 920f
|
||||
&& snapshot.allDoorsOpen()))
|
||||
.step("Release while mass holds the door", 1_000L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(0, GameView.AutomationSnapshot::allDoorsOpen))
|
||||
.step("Take off over the weight", 1_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(0, snapshot -> !snapshot.playerOnGround()
|
||||
&& snapshot.playerY() <= 690f))
|
||||
.step("Land beyond the plate", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, LevelPlaytestBot.groundedAliveAtOrBeyond(1_200f)))
|
||||
.step("Collect the cache beyond the door", 3_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, snapshot -> snapshot.collectibleFound()
|
||||
&& snapshot.playerX() >= 1_550f))
|
||||
.step("Reach the relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelThirtyTwo() {
|
||||
return LevelPlaytestBot.Scenario.builder(32, "Level 32 - stack order")
|
||||
.step("Observe the supported three-object stack", 1_000L,
|
||||
LevelPlaytestBot.Input.none(),
|
||||
snapshot -> snapshot.physicalObjectCount() == 3
|
||||
&& snapshot.firstPhysicalObjectY() >= 799f
|
||||
&& snapshot.firstPhysicalObjectY() <= 801f)
|
||||
.step("Stage before the stack", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, LevelPlaytestBot.groundedAliveAtOrBeyond(480f)))
|
||||
.step("Jump onto the lower object", 2_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(0, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 600f && snapshot.playerY() <= 700f))
|
||||
.step("Climb to the top object", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(0, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 720f && snapshot.playerY() <= 550f))
|
||||
.step("Jump from the stack to the gallery", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(0, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 1_030f && snapshot.playerY() <= 500f
|
||||
&& snapshot.collectibleFound()))
|
||||
.step("Cross the high gallery", 5_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(0, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelThirtyThree() {
|
||||
return LevelPlaytestBot.Scenario.builder(33, "Level 33 - launch window")
|
||||
.step("Read one saw launch", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, snapshot -> snapshot.deaths() == 1
|
||||
&& snapshot.corpseCount() == 1))
|
||||
.step("Observe the delivered body", 3_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, snapshot -> snapshot.firstCorpseX() >= 900f))
|
||||
.step("Wait for the lift runner", 2_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, PhysicsWaveOneScenarios::respawned))
|
||||
.step("Stage before the launch saw", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(420f)))
|
||||
.step("Take off before the launch saw", 1_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(1, snapshot -> !snapshot.playerOnGround()
|
||||
&& snapshot.playerY() <= 620f))
|
||||
.step("Clear the launch saw", 2_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, snapshot -> snapshot.playerX() >= 760f))
|
||||
.step("Stage before the delivered body", 1_500L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(820f)))
|
||||
.step("Clear the delivered body", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(1_200f)))
|
||||
.step("Approach the lift", 1_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(1_240f)))
|
||||
.step("Wait for the low lift phase", 4_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, snapshot -> snapshot.firstMovingPlatformY() >= 770f))
|
||||
.step("Board the lift", 2_500L, LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(1, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerOnMovingPlatform()))
|
||||
.step("Ride to the launch window", 5_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerOnMovingPlatform()
|
||||
&& snapshot.firstMovingPlatformY() <= 430f))
|
||||
.step("Jump onto the high gallery", 3_000L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(1, snapshot -> snapshot.playerOnGround()
|
||||
&& snapshot.playerX() >= 1_750f && snapshot.playerY() <= 400f))
|
||||
.step("Collect the upper cache", 2_500L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, snapshot -> snapshot.collectibleFound()))
|
||||
.step("Reach the upper relic", 4_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static LevelPlaytestBot.Scenario levelThirtyFour() {
|
||||
return LevelPlaytestBot.Scenario.builder(34, "Level 34 - press cycle")
|
||||
.step("Leave one volunteer in the press", 4_000L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, snapshot -> snapshot.deaths() == 1
|
||||
&& snapshot.corpseCount() == 1
|
||||
&& snapshot.firstCorpseCrushed()
|
||||
&& snapshot.firstCorpseActivatesPressure()))
|
||||
.step("Wait for the cycle runner", 2_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, PhysicsWaveOneScenarios::respawned))
|
||||
.step("Approach the occupied press", 2_500L,
|
||||
LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, LevelPlaytestBot.groundedAliveAtOrBeyond(500f)))
|
||||
.step("Observe the press close", 5_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, GameView.AutomationSnapshot::firstHazardActive))
|
||||
.step("Wait for the full rest", 5_000L, LevelPlaytestBot.Input.none(),
|
||||
aliveAtMost(1, snapshot -> !snapshot.firstHazardActive()))
|
||||
.step("Jump through the resting press", 2_500L,
|
||||
LevelPlaytestBot.Input.rightJump(),
|
||||
aliveAtMost(1, snapshot -> snapshot.playerX() >= 930f))
|
||||
.step("Collect the press cache", 3_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, snapshot -> snapshot.collectibleFound()))
|
||||
.step("Reach the relic", 5_000L, LevelPlaytestBot.Input.right(),
|
||||
aliveAtMost(1, GameView.AutomationSnapshot::completed))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static LevelPlaytestBot.SnapshotCondition aliveAtMost(
|
||||
int deaths, LevelPlaytestBot.SnapshotCondition condition) {
|
||||
return LevelPlaytestBot.whileDeathsAtMost(deaths,
|
||||
snapshot -> snapshot.playerAlive() && condition.reached(snapshot));
|
||||
}
|
||||
|
||||
private static boolean respawned(GameView.AutomationSnapshot snapshot) {
|
||||
return snapshot.playerAlive() && snapshot.playerX() < 250f;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public final class FoundationsFinaleContentTest {
|
||||
@Test
|
||||
public void finalFoundationsWaveMatchesTheApprovedMatrixAndClosesTheWorld() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(30, levels.size());
|
||||
assertTrue(levels.size() >= 30);
|
||||
|
||||
LevelDefinition keys = levels.get(26);
|
||||
assertIdentity(keys, 27, "foundations.two-keys", "Two Keys", 2, 65);
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class LevelCatalogTest {
|
||||
@Test
|
||||
public void productionCatalogContainsMechanicsAndChallengeTeachingLevels() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(30, levels.size());
|
||||
assertEquals(34, levels.size());
|
||||
Set<String> titles = new HashSet<>();
|
||||
for (int i = 0; i < levels.size(); i++) {
|
||||
LevelDefinition level = levels.get(i);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.projectsacrifice.game;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class PhysicalObjectStackResolverTest {
|
||||
@Test
|
||||
public void fallingObjectSettlesOnTheHighestCrossedObject() {
|
||||
PhysicalObjectState lower = object(650f, 800f, 160f, 80f);
|
||||
PhysicalObjectState middle = object(690f, 721f, 140f, 80f)
|
||||
.velocity(0f, 60f);
|
||||
PhysicalObjectState distant = object(1_200f, 700f, 100f, 100f);
|
||||
|
||||
boolean settled = PhysicalObjectStackResolver.settleFalling(
|
||||
middle, 720f, Arrays.asList(lower, middle, distant));
|
||||
|
||||
assertTrue(settled);
|
||||
assertEquals(720f, middle.weightedY(), 0f);
|
||||
assertEquals(0f, middle.velocityY(), 0f);
|
||||
assertTrue(middle.grounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void risingOrNonCrossingObjectIsNotTeleportedOntoAStack() {
|
||||
PhysicalObjectState lower = object(650f, 800f, 160f, 80f);
|
||||
PhysicalObjectState rising = object(690f, 700f, 120f, 80f)
|
||||
.velocity(0f, -40f);
|
||||
PhysicalObjectState beside = object(300f, 721f, 100f, 80f)
|
||||
.velocity(0f, 60f);
|
||||
|
||||
assertFalse(PhysicalObjectStackResolver.settleFalling(
|
||||
rising, 704f, Arrays.asList(lower, rising)));
|
||||
assertFalse(PhysicalObjectStackResolver.settleFalling(
|
||||
beside, 720f, Arrays.asList(lower, beside)));
|
||||
}
|
||||
|
||||
private static PhysicalObjectState object(float x, float y, float width, float height) {
|
||||
return new PhysicalObjectState(new LevelDefinition.PhysicalObject(
|
||||
LevelDefinition.PhysicalObjectType.BOX,
|
||||
new LevelDefinition.Box(x, y, width, height), 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
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.List;
|
||||
|
||||
public final class PhysicsWaveOneContentTest {
|
||||
@Test
|
||||
public void firstPhysicsWaveMatchesTheExecutableMatrix() {
|
||||
List<LevelDefinition> levels = LevelCatalog.all();
|
||||
assertEquals(34, levels.size());
|
||||
|
||||
LevelDefinition mass = levels.get(30);
|
||||
assertIdentity(mass, 31, "physics.mass-basics", "Weight Class", 0, 45);
|
||||
assertFalse(mass.objects.isEmpty());
|
||||
assertTrue(mass.containsMechanic(LevelDefinition.SwitchType.PRESSURE));
|
||||
assertFalse(mass.doors.isEmpty());
|
||||
|
||||
LevelDefinition stack = levels.get(31);
|
||||
assertIdentity(stack, 32, "physics.stack-order", "Stack Order", 0, 50);
|
||||
assertTrue(stack.objects.size() >= 3);
|
||||
assertTrue("The authored staircase must contain an object supported by another",
|
||||
hasAuthoredObjectStack(stack));
|
||||
|
||||
LevelDefinition launch = levels.get(32);
|
||||
assertIdentity(launch, 33, "physics.launch-window", "Launch Window", 1, 55);
|
||||
assertTrue(launch.containsMechanic(LevelDefinition.HazardType.SAW));
|
||||
assertTrue(launch.hasMovingPlatform());
|
||||
|
||||
LevelDefinition crusher = levels.get(33);
|
||||
assertIdentity(crusher, 34, "physics.crusher-rhythm", "Press Cycle", 1, 55);
|
||||
assertTrue(crusher.containsMechanic(LevelDefinition.HazardType.CRUSHER));
|
||||
assertTrue(crusher.containsMechanic(LevelDefinition.SwitchType.PRESSURE));
|
||||
}
|
||||
|
||||
private static boolean hasAuthoredObjectStack(LevelDefinition level) {
|
||||
for (LevelDefinition.PhysicalObject upper : level.objects) {
|
||||
for (LevelDefinition.PhysicalObject lower : level.objects) {
|
||||
if (upper == lower) continue;
|
||||
boolean horizontalOverlap = upper.box.x < lower.box.right()
|
||||
&& upper.box.right() > lower.box.x;
|
||||
if (horizontalOverlap
|
||||
&& Math.abs(upper.box.bottom() - lower.box.y) <= 0.01f) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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("physics", level.worldId);
|
||||
assertEquals(title, level.title);
|
||||
assertEquals(targetDeaths, level.targetDeaths);
|
||||
assertEquals(targetSeconds, level.targetSeconds);
|
||||
assertNotNull(level.collectible);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user