t('BOUNCES {n} ESCAPES {m}', ['n' => $this->bounces, 'm' => $this->escapes]); } public function enter(array $params = []): void { $this->gap = self::GAP; $this->escapes = 0; parent::enter($params); } protected function build(): void { $this->world->reset($this->seed + $this->round * 7919); $this->world->gravity = 420; $this->world->restitution = 1.045; // each bounce gives a little energy: it climbs until it finds the exit $rand = $this->world->random; $this->gap = $this->round === 0 ? self::GAP : max(self::MIN_GAP, $this->gap * 0.85); $this->world->addRing(new Ring(self::CX, self::CY, 112, gap: $this->gap, angle: -M_PI / 2, spin: min(2.4, 0.9 + 0.15 * $this->round) * ($this->round % 2 ? -1 : 1), thickness: 4, color: self::hue($this->round * 0.13))); $this->world->addBall(new Ball(self::CX + $rand->between(-20, 20), self::CY + 60, $rand->between(-110, 110), 0, 7, '#ff8fc0', 120)); } protected function onHit(array $hit): void { /** @var Ball $ball */ $ball = $hit['ball']; if ($hit['type'] === 'escape') { $this->escapes++; $this->banner = $this->t('FOUND THE EXIT!'); $this->roundEnd = $this->time + 1.7; $this->burst($ball->x, $ball->y, '#ffe27a', 34, 130); foreach ([0, 2, 4] as $k) { $this->recorder->note($this->time + $k * 0.07, Scale::freq(Scale::MAJOR_PENTA, 10 + $k), 1.0, 'bell', 0.2); } return; } $this->bounces++; /** @var Ring $ring */ $ring = $hit['body']; $ring->data['flash'] = 1.0; $ring->color = self::hue($this->round * 0.13 + $ring->hits * 0.02); $ball->color = self::hue($ring->hits * 0.02 + 0.55); $this->squash($ball, (float)$hit['x'], (float)$hit['y']); $this->burst((float)$hit['x'], (float)$hit['y'], $ring->color, min(14, 4 + (int)($hit['speed'] / 70)), 80); $this->note(); } protected function drawWorld(Renderer $r): void { foreach ($this->world->rings() as $ring) { $this->drawRing($r, $ring); } foreach ($this->world->balls() as $ball) { $this->drawBall($r, $ball); } } }