Release 1.0.0: rename to NoMines, add leaderboard, theme support and release metadata
- Rename app/binary/app-id to nomines (io.github.bemagri.nomines), config dir moves to ~/.config/nomines with legacy path fallback - Add persistent best-time leaderboard (Glib::KeyFile) - Theme-aware board following system light/dark preference - Beveled 3D cells, confetti win animation, keyboard shortcuts, right-click chording, per-difficulty window sizing - Add AppStream metainfo, validate desktop file and metainfo via meson tests - Fix license metadata to GPL-3.0-or-later and real homepage
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
#include "leaderboard.hpp"
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <glibmm/keyfile.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <glib.h>
|
||||
|
||||
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");
|
||||
if (!Glib::file_test(path, Glib::FileTest::EXISTS) &&
|
||||
Glib::file_test(legacy, Glib::FileTest::EXISTS)) {
|
||||
path = legacy;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
} 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<int> 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::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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user