ES
games/satisfying-bounce / tests / ZoomTest.php · commit 8bdd6e4
<?phpnamespace Tests;use App\Render\ZoomView;use App\Scene\ZoomRingsScene;use JumPHP\Physics2d\Ring;use JumPHP\Physics2d\World;use JumPHP\Testing\GameTestCase;/** The infinite zoom: the camera, and the rings that go on for ever. */final class ZoomTest extends GameTestCase{    public function testTheCameraGlidesInLogSpaceWithAnEase(): void    {        $view = new ZoomView(0, 0, 135, 240);        $view->zoomTo(0.25, 1.0);        $view->update(0.5);        self::assertEqualsWithDelta(0.5, $view->scale, 1e-9, 'half way in time is the geometric mean: x1 -> x0.25 passes x0.5');        $view->update(0.1);        self::assertGreaterThan(0.25, $view->scale);        self::assertTrue($view->isZooming());        $view->update(1.0);        self::assertSame(0.25, $view->scale);        self::assertFalse($view->isZooming());        self::assertEqualsWithDelta(135.0, $view->x(0), 1e-9);        self::assertEqualsWithDelta(10.0, $view->len(40), 1e-9);    }    public function testTheEaseStartsAndEndsSlowly(): void    {        self::assertSame(0.0, ZoomView::ease(0));        self::assertSame(1.0, ZoomView::ease(1));        self::assertLessThan(0.05, ZoomView::ease(0.2));        self::assertGreaterThan(0.95, ZoomView::ease(0.8));    }    public function testAWorldFlaggedAsLeftARingForgetsItWhenTheRingIsRemoved(): void    {        $world = new World();        $world->gravity = 0;        $ring = $world->addRing(new Ring(0, 0, 10, gap: 6.0));        $ball = $world->addBall(new \JumPHP\Physics2d\Ball(0, 0, 100, 0, 2));        $world->update(0.3);        self::assertSame('escape', $world->hits()[0]['type']);        $world->removeRing($ring);        $again = $world->addRing(new Ring(0, 0, 100));          // PHP may give it the id of the one just freed        $ball->x = 98;        $ball->y = 0;        $ball->vx = 100;        $world->update(0.1);        $types = array_column($world->hits(), 'type');        self::assertContains('ring', $types, 'the new ring is not skipped as one the ball already left');        self::assertNotNull($again);    }    public function testTheFirstEscapeAndZoomComeInTheHook(): void    {        foreach ([1, 2, 3, 4, 5] as $seed) {            $this->game->start(ZoomRingsScene::class, ['seed' => $seed]);            $this->advance(60 * 3);            self::assertGreaterThanOrEqual(1, $this->game->peek('passed'), "seed $seed: out of the first ring in 3 s");        }    }    public function testTheBallGetsOutOfRingAfterRingAndNewRingsKeepComing(): void    {        $this->game->start(ZoomRingsScene::class, ['seed' => 3]);        $world = $this->world();        $first = $world->rings()[0];        $this->game->run(20);        $this->advance(60 * 9);        self::assertGreaterThanOrEqual(3, $this->game->peek('passed'));        self::assertNotContains($first, $world->rings(), 'the ring it left is gone');        self::assertCount(ZoomRingsScene::AHEAD, $world->rings(), 'and a new one was made outside');        $radii = array_map(fn (Ring $r) => $r->radius, $world->rings());        $min = min($radii);        $view = $this->game->peek('view');        self::assertGreaterThan(ZoomRingsScene::R0 * 0.99, $view->len($min));        self::assertLessThan(ZoomRingsScene::R0 * ZoomRingsScene::RATIO * 1.01, $view->len($min), 'the ring it is in is about the same size on screen (the camera follows it)');    }    public function testThePhysicsNeverSeesTheCameraAndTheBallKeepsItsSizeOnScreen(): void    {        $this->game->start(ZoomRingsScene::class, ['seed' => 3]);        $view = $this->game->peek('view');        $sizes = [];        $this->game->run(20);        for ($i = 0; $i < 60; $i++) {            $this->advance(10);            $ball = $this->world()->balls()[0];            $sizes[] = $view->len($ball->radius);        }        foreach ($sizes as $s) {            self::assertEqualsWithDelta(ZoomRingsScene::BALL, $s, 0.01);        }        self::assertGreaterThan(0, $this->game->peek('passed'));    }    public function testTheWorldIsRebasedSoTheNumbersStaySmallHoweverFarItGoes(): void    {        $this->game->start(ZoomRingsScene::class, ['seed' => 3]);        $this->advance(60 * 20);        $this->game->run(1);        $passed = $this->game->peek('passed');        self::assertGreaterThanOrEqual(5, $passed);        foreach ($this->world()->rings() as $ring) {            self::assertLessThan(ZoomRingsScene::R0 * 12, $ring->radius);        }        $ball = $this->world()->balls()[0];        self::assertLessThan(2000, abs($ball->x - 135) + abs($ball->y - 250));        $this->game->shot('zoom');    }    public function testEachPassageRingsABellAndBouncesPlayTheMarimba(): void    {        $this->game->start(ZoomRingsScene::class, ['seed' => 3]);        $this->advance(60 * 6);        $waves = array_count_values(array_column($this->recorder()->notes(), 'wave'));        self::assertGreaterThanOrEqual($this->game->peek('passed'), $waves['bell'] ?? 0);        self::assertGreaterThan(2, $waves['marimba'] ?? 0);    }    private function advance(int $frames): void    {        $scene = $this->game->scene();        for ($i = 0; $i < $frames; $i++) {            $scene->update(1 / 60);        }    }    private function world(): World    {        return $this->game->peek('world');    }    private function recorder(): \JumPHP\Shorts\Recorder    {        return $this->game->peek('recorder');    }}