Leaderboard: clear button and hygiene for migrated legacy data

A scratch test earlier in development wrote a fake 42s Beginner record into
the legacy ~/.config/minesweeper/leaderboard.ini, which the migration imported;
as a stored best it can never be beaten by slower real games, so the popover
showed a lower time than actually played.

- Add a 'Clear scores' button to the leaderboard popover
- Delete the legacy file after a successful migration so stale data can't
  be re-imported
This commit is contained in:
2026-08-01 17:09:08 +01:00
parent 6c69556b25
commit 62a09e4261
3 changed files with 25 additions and 0 deletions
+14
View File
@@ -3,6 +3,7 @@
#include <glibmm/keyfile.h> #include <glibmm/keyfile.h>
#include <glibmm/miscutils.h> #include <glibmm/miscutils.h>
#include <glib.h> #include <glib.h>
#include <glib/gstdio.h>
namespace { namespace {
std::string file_path() { std::string file_path() {
@@ -19,9 +20,11 @@ void Leaderboard::load() {
auto path = file_path(); auto path = file_path();
auto legacy = Glib::build_filename( auto legacy = Glib::build_filename(
Glib::get_user_config_dir(), "minesweeper", "leaderboard.ini"); Glib::get_user_config_dir(), "minesweeper", "leaderboard.ini");
bool migrated = false;
if (!Glib::file_test(path, Glib::FileTest::EXISTS) && if (!Glib::file_test(path, Glib::FileTest::EXISTS) &&
Glib::file_test(legacy, Glib::FileTest::EXISTS)) { Glib::file_test(legacy, Glib::FileTest::EXISTS)) {
path = legacy; path = legacy;
migrated = true;
} }
try { try {
@@ -32,6 +35,11 @@ void Leaderboard::load() {
times_[group.raw()] = kf->get_integer(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&) { } catch (const Glib::Error&) {
// No file yet or unreadable — start with an empty leaderboard // No file yet or unreadable — start with an empty leaderboard
} }
@@ -52,6 +60,12 @@ std::optional<int> Leaderboard::best_time(const std::string& difficulty) const {
return it->second; return it->second;
} }
void Leaderboard::clear() {
load();
times_.clear();
save();
}
void Leaderboard::save() { void Leaderboard::save() {
auto kf = Glib::KeyFile::create(); auto kf = Glib::KeyFile::create();
for (const auto& [difficulty, seconds] : times_) { for (const auto& [difficulty, seconds] : times_) {
+3
View File
@@ -15,6 +15,9 @@ public:
// Best time in seconds for the difficulty, if one exists. // Best time in seconds for the difficulty, if one exists.
std::optional<int> best_time(const std::string& difficulty) const; std::optional<int> best_time(const std::string& difficulty) const;
// Removes all recorded scores and rewrites the file.
void clear();
private: private:
void load(); void load();
void save(); void save();
+8
View File
@@ -178,6 +178,14 @@ void MainWindow::refresh_leaderboard() {
row->set_text(diff.name + ": " + time_str); row->set_text(diff.name + ": " + time_str);
box_leaderboard_.append(*row); box_leaderboard_.append(*row);
} }
auto* clear_btn = Gtk::make_managed<Gtk::Button>("Clear scores");
clear_btn->set_margin_top(8);
clear_btn->signal_clicked().connect([this]() {
leaderboard_.clear();
menu_leaderboard_.popdown(); // Rebuilds on next show
});
box_leaderboard_.append(*clear_btn);
} }
void MainWindow::setup_board() { void MainWindow::setup_board() {