data ??= (array)$engine->storage->readJson('progress.json', []) + ['unlocked' => 1, 'best' => []]; } public function unlocked(Engine $engine): int { return max(1, min(Levels::count(), (int)$this->data($engine)['unlocked'])); } /** Best percentage saved on a level (0-based), null: never won. */ public function best(Engine $engine, int $level): ?int { $best = $this->data($engine)['best'][(string)$level] ?? null; return $best === null ? null : (int)$best; } public function won(Engine $engine, int $level, int $percent): void { $data = $this->data($engine); $data['best'][(string)$level] = max((int)($data['best'][(string)$level] ?? 0), $percent); $data['unlocked'] = max((int)$data['unlocked'], min(Levels::count(), $level + 2)); $this->data = $data; $engine->storage->writeJson('progress.json', $data); } /** 1 to 3 stars for a rescue percentage against what the level asked. */ public static function stars(int $percent, int $needPercent): int { return $percent >= 100 ? 3 : ($percent >= ($needPercent + 100) / 2 ? 2 : 1); } }