balls that faded away */ private array $ghosts = []; private int $born = 0; protected function melodyNotes(): string { // the opening of Fur Elise (Beethoven, public domain), then a rising arpeggio return 'E5 D#5 E5 D#5 E5 B4 D5 C5 A4 C4 E4 A4 B4 E4 G#4 B4 C5 E4 E5 D#5 E5 D#5 E5 B4 D5 C5 A4'; } protected function headingText(): string { return 'IT KEEPS MULTIPLYING'; } protected function hookText(): string { return 'EVERY HIT MAKES A NEW BALL'; } protected function captionText(): string { return $this->t('BALLS {n} BOUNCES {m}', ['n' => count($this->world->balls()), 'm' => $this->bounces]); } protected function build(): void { $this->world->reset($this->seed + $this->round * 7919); $this->world->gravity = 150; $this->world->ballsCollide = true; $this->world->maxSpeed = 260; $this->ghosts = []; $this->born = 0; $this->world->addRing(new Ring(self::CX, self::CY, 118, thickness: 4, spin: 0.3, color: '#7fd8ff')); $a = $this->world->random->between(0, 2 * M_PI); $this->world->addBall(new Ball(self::CX, self::CY, cos($a) * 120, sin($a) * 120, 9, self::hue(0.9), 80)); } protected function tick(float $dt): void { foreach ($this->ghosts as $i => &$g) { $g['age'] += $dt * 2.2; } unset($g); $this->ghosts = array_values(array_filter($this->ghosts, fn (array $g) => $g['age'] < 1)); } protected function onHit(array $hit): void { if ($hit['type'] !== 'ring') { return; } $this->bounces++; /** @var Ring $ring */ $ring = $hit['body']; /** @var Ball $ball */ $ball = $hit['ball']; $ring->data['flash'] = 1.0; $this->squash($ball, (float)$hit['x'], (float)$hit['y']); $this->burst((float)$hit['x'], (float)$hit['y'], $ball->color, 5, 70); $this->note(1.0, 0.4, 0.26, 0.085); $rand = $this->world->random; $a = $rand->between(0, 2 * M_PI); $this->born++; $this->world->addBall(new Ball($ball->x - ($ball->x - $ring->x) * 0.25, $ball->y - ($ball->y - $ring->y) * 0.25, cos($a) * 100, sin($a) * 100, 9, self::hue(0.9 + $this->born * 0.061), 80)); $balls = $this->world->balls(); if (count($balls) > self::MAX_BALLS) { $old = $balls[0]; $this->ghosts[] = ['x' => $old->x, 'y' => $old->y, 'r' => $old->radius, 'rgb' => Gfx::rgb($old->color), 'age' => 0.0]; $this->world->removeBall($old); } } protected function drawWorld(Renderer $r): void { foreach ($this->world->rings() as $ring) { $this->drawRing($r, $ring); } foreach ($this->ghosts as $g) { Gfx::arc($r, $g['x'], $g['y'], $g['r'] * (1 + $g['age'] * 1.5), 0, 2 * M_PI, 1.2 * (1 - $g['age']), Gfx::mix(Gfx::BG_BOTTOM, $g['rgb'], 1 - $g['age'])); } foreach ($this->world->balls() as $ball) { $this->drawBall($r, $ball); } } }