<?phpnamespace App\Scene;use App\Service\Art;use App\Service\Audio;use App\Service\Fonts;use App\Service\Progress;use App\Sim\Levels;use JumPHP\Renderer\Renderer;use JumPHP\Scene\AbstractScene;/** The map of the levels: a grid of tiles, locked until the one before is won. */final class LevelScene extends AbstractScene{ private const COLS = 6; private const TW = 72; private const TH = 42; private int $selected = 0; private float $time = 0; public function __construct(private Art $art, private Fonts $fonts, private Audio $audio, private Progress $progress) { } public function enter(array $params = []): void { $this->art->load($this->engine); $this->audio->load($this->sound()); $this->selected = max(0, min(Levels::count() - 1, (int)($params['level'] ?? $this->progress->unlocked($this->engine) - 1))); $this->time = 0; if ($this->sound()->getCurrentMusic() !== 'title') { $this->sound()->playMusic('title'); } } /** @return array{0:float,1:float} */ private function tileXY(int $i): array { return [240 - self::COLS * (self::TW + 6) / 2 + 3 + ($i % self::COLS) * (self::TW + 6), 44 + intdiv($i, self::COLS) * (self::TH + 4)]; } public function update(float $dt): void { $this->time += $dt; $a = $this->actions(); $n = Levels::count(); $m = $this->engine->input->getMouse(); if ($m->isPressed(1)) { if ($m->y > 236 && $m->x < 110) { $this->scenes()->switchTo(TitleScene::class); return; } for ($i = 0; $i < $n; $i++) { [$x, $y] = $this->tileXY($i); if ($m->x >= $x && $m->x < $x + self::TW && $m->y >= $y && $m->y < $y + self::TH) { $this->selected = $i; $this->start(); return; } } } $move = ($a->isPressed('right') ? 1 : 0) - ($a->isPressed('left') ? 1 : 0) + self::COLS * (($a->isPressed('down') ? 1 : 0) - ($a->isPressed('up') ? 1 : 0)); if ($move !== 0 && $this->selected + $move >= 0 && $this->selected + $move < $n) { $this->selected += $move; $this->sound()->playSfx('menu_move'); } if ($a->isPressed('confirm')) { $this->start(); } elseif ($a->isPressed('back')) { $this->scenes()->switchTo(TitleScene::class); } } private function start(): void { if ($this->selected >= $this->progress->unlocked($this->engine)) { $this->sound()->playSfx('deny'); return; } $this->sound()->playSfx('menu_confirm'); $this->scenes()->switchTo(PlayScene::class, ['level' => $this->selected]); } public function render(Renderer $r): void { $sdl = $this->sdl(); $e = $this->engine; $this->art->draw($r, 'bg-night-sky', null, 0, 0); $r->fillRect(0, 232, 480, 38, [8, 10, 32]); foreach (['far' => 6, 'near' => 14] as $layer => $speed) { $off = -fmod($this->time * $speed, 512); $this->art->draw($r, "bg-night-$layer", null, round($off), 38); $this->art->draw($r, "bg-night-$layer", null, round($off) + 512, 38); } $r->fillRect(0, 0, 480, 270, [8, 4, 20, 100]); $this->fonts->text($e, $r, 'CHOOSE A LEVEL', 240, 12, 1.8, [255, 210, 60], 'center'); $open = $this->progress->unlocked($e); foreach (Levels::all() as $i => $def) { [$x, $y] = $this->tileXY($i); $on = $i === $this->selected; $locked = $i >= $open; $y += $on ? sin($this->time * 5) * 1.2 : 0; $r->fillRect($x, $y, self::TW, self::TH, $locked ? [26, 20, 48, 235] : ($on ? [120, 70, 40, 235] : [48, 36, 92, 235])); $this->art->draw($r, 'bg-' . $def['theme'] . '-sky', [200, 60, self::TW - 4, 18], $x + 2, $y + 2); $this->art->draw($r, 'bg-' . $def['theme'] . '-near', [100, 184, self::TW - 4, 18], $x + 2, $y + 2); if ($locked) { $r->fillRect($x + 2, $y + 2, self::TW - 4, 18, [10, 6, 24, 190]); $this->art->icon($r, $e, 'lock', $x + self::TW / 2 - 6, $y + 5, '-dim'); } else { $this->fonts->print($e, $r, (string)($i + 1), $x + self::TW / 2, $y + 2, 1.3, [255, 255, 255], 'center', [20, 12, 40]); } $r->drawRect($x, $y, self::TW, self::TH, $on ? [255, 220, 120] : [92, 74, 148]); $this->fonts->text($e, $r, $def['name'], $x + self::TW / 2, $y + 20, 0.62, $locked ? [120, 110, 150] : [255, 255, 255], 'center'); $best = $this->progress->best($e, $i); if ($best !== null) { $stars = Progress::stars($best, (int)($def['need'] * 100 / $def['total'])); for ($s = 0; $s < 3; $s++) { $this->art->icon($r, $e, 'star', $x + self::TW / 2 - 18 + $s * 13, $y + 30, $s < $stars ? '-gold' : '-dim'); } } } $this->art->draw($r, 'btn-wide', [0, 0, 100, 22], 6, 242); $this->fonts->text($e, $r, 'BACK', 56, 246, 1.1, [255, 255, 255], 'center'); }}