ES
games/satisfying-bounce / src / Scene / WaveGridScene.php · commit 8bdd6e4
<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * A checkerboard that ripples like cloth: every tile moves and swells with a travelling wave, so the flat grid looks curved and deep. On every * beat (twice a second) a note of the Hirajoshi scale rings and a bright wave of colour runs out from the middle. 4 seconds make a * whole number of waves and beats: it loops with no seam. */final class WaveGridScene extends VideoScene{    public const SECONDS = 24.0;    protected const LOOPS = true;    public const PERIOD = 4.0;    public const BEATS = 8;    private const COLS = 11;    private const ROWS = 17;    private const CELL = 25.0;    private int $lastBeat = -1;    private float $beatAge = 9.0;    protected function melodyNotes(): string    {        return 'C4';    }    protected function headingText(): string    {        return 'IT IS PERFECTLY FLAT';    }    protected function hookText(): string    {        return 'CAN YOU SEE THE WAVES?';    }    protected function captionText(): string    {        return $this->t('BEATS {n}', ['n' => max(0, $this->lastBeat)]);    }    protected function build(): void    {        $this->lastBeat = -1;        $this->beatAge = 9.0;    }    protected function tick(float $dt): void    {        $this->beatAge += $dt;        $n = (int)floor($this->time / self::PERIOD * self::BEATS);        if ($n > $this->lastBeat) {            $this->lastBeat = $n;            $this->beatAge = 0.0;            $this->play(Scale::freq(Scale::HIRAJOSHI, 3 + [0, 2, 4, 2, 5, 3, 1, 4][$n % 8], 57), 'marimba', 0.8, 0.3, 0.0);            if ($n % 4 === 0) {                $this->pad([Scale::freq(Scale::HIRAJOSHI, 0, 45), Scale::freq(Scale::HIRAJOSHI, 3, 45)], 3.6, 0.06);            }            $this->pulse = 1.0;            $this->tint = Gfx::hsv($n * 0.11);        }    }    protected function drawWorld(Renderer $r): void    {        $w = 2 * M_PI * $this->time / self::PERIOD;        $ox = (270 - self::COLS * self::CELL) / 2;        $oy = 262 - self::ROWS * self::CELL / 2;        for ($j = -1; $j <= self::ROWS; $j++) {            for ($i = -1; $i <= self::COLS; $i++) {                $x = $ox + ($i + 0.5) * self::CELL;                $y = $oy + ($j + 0.5) * self::CELL;                $dx = ($i - (self::COLS - 1) / 2) / 5.0;                $dy = ($j - (self::ROWS - 1) / 2) / 8.0;                $dist = sqrt($dx * $dx + $dy * $dy);                $wave = sin(2 * M_PI * 1.15 * $dist - $w) + 0.55 * sin(2.4 * $dx + 1.3 * $dy + 2 * $w) ;                $x += 3.4 * sin(1.1 * ($j + 0.5) + $w) + 2.2 * $wave * $dx;                $y += 3.4 * sin(1.1 * ($i + 0.5) - $w) + 2.2 * $wave * $dy;                $size = self::CELL * (0.86 + 0.11 * $wave);                $dark = (($i + $j) % 2 + 2) % 2 === 0;                $hue = 0.62 + 0.18 * sin($w + $dist * 2.2);                // the beat's wave of light runs out from the middle                $ring = abs($dist * 0.9 - $this->beatAge * 1.5);                $lit = $ring < 0.55 ? (1 - $ring / 0.55) * max(0.0, 1 - $this->beatAge / 1.1) : 0.0;                $rgb = $dark ? Gfx::hsv($hue, 0.7, 0.42 + 0.2 * $wave) : Gfx::hsv($hue + 0.12, 0.35, 0.92 + 0.08 * $wave);                $rgb = Gfx::mix($rgb, [255, 255, 255], $lit * 0.75);                $r->fillRect($x - $size / 2, $y - $size / 2, $size, $size, Gfx::color($rgb));            }        }    }}