Entrar
PT
games/satisfying-bounce / tests / BounceTest.php · commit 8bdd6e4
<?phpnamespace Tests;use App\Scene\CatBounceScene;use App\Scene\ConcentricScene;use App\Scene\FilmScene;use App\Scene\GapCircleScene;use App\Scene\MarbleScene;use App\Scene\MultiplyScene;use App\Scene\PlinkoScene;use App\Scene\RainPondScene;use App\Scene\ZoomRingsScene;use JumPHP\Physics2d\Ring;use JumPHP\Physics2d\World;use JumPHP\Shorts\Melody;use JumPHP\Shorts\Mixer;use JumPHP\Shorts\Recorder;use JumPHP\Shorts\Voice;use JumPHP\Testing\GameTestCase;final class BounceTest extends GameTestCase{    public function testTheCircleIsClosedOnThreeQuartersAndItsOpeningTurns(): void    {        $this->game->start(GapCircleScene::class, ['seed' => 3]);        $ring = $this->world()->rings()[0];        self::assertEqualsWithDelta(M_PI / 2, $ring->gap, 1e-9);        $angle = $ring->angle;        $this->game->run(30);        self::assertNotEqualsWithDelta($angle, $ring->angle, 0.1);    }    public function testTheBallBouncesInTheCircleAndEachBounceIsTheNextNote(): void    {        $this->game->start(GapCircleScene::class, ['seed' => 3, 'melody' => 'C4 E4 G4']);        $this->game->run(150);        $notes = array_values(array_filter($this->recorder()->notes(), fn (array $n) => $n['wave'] === 'pluck'));        self::assertGreaterThan(2, count($notes));        $expected = [Melody::frequency('C4'), Melody::frequency('E4'), Melody::frequency('G4')];        foreach (array_slice($notes, 0, 6) as $i => $note) {            self::assertEqualsWithDelta($expected[$i % 3], $note['freq'], 0.01);        }        $this->game->shot('gap');    }    public function testTheBallEventuallyFindsTheExitAndTheNextOpeningIsNarrower(): void    {        $this->game->start(GapCircleScene::class, ['seed' => 3]);        $first = $this->world()->rings()[0]->gap;        $ball = $this->world()->balls()[0];          // thrown straight up at the opening        $ball->x = 135.0;        $ball->y = 250.0;        $ball->vx = 0.0;        $ball->vy = -420.0;        $this->game->run(70);        self::assertGreaterThanOrEqual(1, $this->game->peek('escapes'));        $this->game->run(110);                       // the banner goes away, the next round starts        self::assertGreaterThan(0, $this->game->peek('round'));        self::assertLessThan($first, $this->world()->rings()[0]->gap);    }    public function testEachHitOnTheWallMakesANewBall(): void    {        $this->game->start(MultiplyScene::class, ['seed' => 5]);        self::assertCount(1, $this->world()->balls());        $this->game->run(60 * 3);        self::assertGreaterThan(4, count($this->world()->balls()));        $this->game->run(60 * 5);        self::assertSame(MultiplyScene::MAX_BALLS, count($this->world()->balls()), 'full, and the oldest balls fade so it never goes past');        $this->game->shot('multiply');    }    public function testTheRingsBreakOneAfterTheOtherFromTheInside(): void    {        $this->game->start(ConcentricScene::class, ['seed' => 3]);        $solid = fn () => count(array_filter($this->world()->rings(), fn (Ring $r) => $r->solid));        self::assertSame(ConcentricScene::RINGS, $solid());        $this->game->run(60 * 3);        self::assertLessThan(ConcentricScene::RINGS, $solid());        $broken = array_values(array_filter($this->world()->rings(), fn (Ring $r) => !$r->solid));        self::assertSame($this->world()->rings()[0], $broken[0], 'the inner ring breaks first');        $this->game->shot('rings');    }    public function testABrokenRingPlaysABellHigherThanTheBounces(): void    {        $this->game->start(ConcentricScene::class, ['seed' => 3, 'melody' => 'C4']);        $this->game->run(60 * 3);        $bells = array_filter($this->recorder()->notes(), fn (array $n) => $n['wave'] === 'bell');        self::assertNotEmpty($bells);        foreach ($bells as $bell) {            self::assertGreaterThan(Melody::frequency('C4') * 1.5, $bell['freq']);        }    }    public function testMarblesDropAndRingOnTheRamps(): void    {        $this->game->start(MarbleScene::class, ['seed' => 2]);        $this->game->run(60 * 5 + 20);        self::assertGreaterThan(10, $this->game->peek('dropped'));        self::assertLessThanOrEqual(MarbleScene::MAX_ALIVE, count($this->world()->balls()));        self::assertGreaterThan(10, count($this->recorder()->notes()));        $this->game->shot('marble');    }    public function testTheSameSeedMakesTheSameVideo(): void    {        $run = function (string $scene, int $seed): array {            $this->game->start($scene, ['seed' => $seed]);            $this->game->run(60);            return array_map(fn (array $n) => [$n['time'], $n['freq']], $this->recorder()->notes());        };        foreach ([GapCircleScene::class, MultiplyScene::class, ConcentricScene::class, MarbleScene::class, ZoomRingsScene::class, CatBounceScene::class] as $scene) {            $a = $run($scene, 21);            self::assertNotEmpty($a, $scene);            self::assertSame($a, $run($scene, 21), $scene);        }    }    public function testTheFilmSceneStartsTheSceneOfTheTemplate(): void    {        foreach (FilmScene::TEMPLATES as $template => $class) {            $this->game->start(FilmScene::class, ['template' => $template, 'seed' => 4]);            $this->game->run(2);            self::assertInstanceOf($class, $this->game->scene(), $template);        }    }    public function testTheNotesAreMixedIntoASoundTrackOfTheVideosLength(): void    {        $wav = Mixer::mix([['time' => 0.5, 'freq' => 440.0], ['time' => 1.0, 'freq' => 660.0]], 2.0);        self::assertSame('RIFF', substr($wav, 0, 4));        self::assertCount(2 * Voice::RATE, unpack('v*', substr($wav, 44)));    }    private function world(): World    {        return $this->game->peek('world');    }    private function recorder(): Recorder    {        return $this->game->peek('recorder');    }}