#include "leaderboard.hpp" #include #include #include #include #include namespace { std::string file_path() { return Glib::build_filename( Glib::get_user_config_dir(), "nomines", "leaderboard.ini"); } } // namespace void Leaderboard::load() { if (loaded_) return; loaded_ = true; // Migrate scores from pre-1.0 builds that stored them under minesweeper/ auto path = file_path(); auto legacy = Glib::build_filename( Glib::get_user_config_dir(), "minesweeper", "leaderboard.ini"); bool migrated = false; if (!Glib::file_test(path, Glib::FileTest::EXISTS) && Glib::file_test(legacy, Glib::FileTest::EXISTS)) { path = legacy; migrated = true; } try { auto kf = Glib::KeyFile::create(); kf->load_from_file(path); for (const auto& group : kf->get_groups()) { if (kf->has_key(group, "best")) { times_[group.raw()] = kf->get_integer(group, "best"); } } if (migrated) { // Stale legacy files can hold outdated/experimental data; drop it // so it can never be re-imported after the current file is gone. g_remove(legacy.c_str()); } } catch (const Glib::Error&) { // No file yet or unreadable — start with an empty leaderboard } } bool Leaderboard::record(const std::string& difficulty, int seconds) { load(); auto it = times_.find(difficulty); if (it != times_.end() && it->second <= seconds) return false; times_[difficulty] = seconds; save(); return true; } std::optional Leaderboard::best_time(const std::string& difficulty) const { auto it = times_.find(difficulty); if (it == times_.end()) return std::nullopt; return it->second; } void Leaderboard::clear() { load(); times_.clear(); save(); } void Leaderboard::save() { auto kf = Glib::KeyFile::create(); for (const auto& [difficulty, seconds] : times_) { kf->set_integer(difficulty, "best", seconds); } try { auto dir = Glib::path_get_dirname(file_path()); g_mkdir_with_parents(dir.c_str(), 0700); kf->save_to_file(file_path()); } catch (const Glib::Error&) { // Best-effort persistence; a failed save is not fatal } }