05 | 持久化撤销提示非核心功能

本文目录

  • [🧩 第 5 篇:持久化、撤销、提示 ------ 那些"非核心"功能](#🧩 第 5 篇:持久化、撤销、提示 —— 那些"非核心"功能)
    • [💾 1. 持久化系统:5 个文件](#💾 1. 持久化系统:5 个文件)
      • [1.1 存什么?](#1.1 存什么?)
      • [1.2 安全措施:版本号 + 校验和 + 原子写入](#1.2 安全措施:版本号 + 校验和 + 原子写入)
      • [1.3 Validate 兜底(Fix #44, #46, #82)](#44, #46, #82))
    • [↩️ 2. 撤销系统(Z 键,最多 10 步)](#↩️ 2. 撤销系统(Z 键,最多 10 步))
      • [2.1 设计](#2.1 设计)
      • [2.2 快照大小](#2.2 快照大小)
      • [2.3 撤销](#2.3 撤销)
    • [💡 3. Hint 提示系统(H 键)](#💡 3. Hint 提示系统(H 键))
      • [3.1 算法](#3.1 算法)
    • [🔊 4. 音效系统](#🔊 4. 音效系统)
    • [😊 5. Smiley Face(笑脸重置按钮)](#😊 5. Smiley Face(笑脸重置按钮))
    • [🔧 6. Toast 通知](#🔧 6. Toast 通知)
    • [📊 7. 五层防护体系](#📊 7. 五层防护体系)
    • [🔜 下篇预告](#🔜 下篇预告)
    • [📚 系列目录](#📚 系列目录)

🎯 系列第 5 篇 · 核心算法只占项目代码的 20%,剩下的 80% 全在本文。

📖 上一篇中英文双语的工程实现


🧩 第 5 篇:持久化、撤销、提示 ------ 那些"非核心"功能


"你的扫雷能跑了吗?"

"能跑了。"

"能保存记录吗?能撤销吗?能提示吗?能调音量吗?能切语言吗?......"

"......"

"能跑"和"好用"之间,差了 80% 的工作量。


💾 1. 持久化系统:5 个文件

1.1 存什么?

文件 内容 触发时机
records.dat 每难度最好成绩 每局胜利
stats.dat 总局/胜/胜率/连胜 每局结束
history.dat 最近 5 局 每局结束
settings.dat 音量/主题/语言 设置变更
savegame.dat 完整游戏棋盘 Ctrl+S / Ctrl+L

全部用二进制格式 ------ C 的 fwrite/fread 直接读写内存结构体,零序列化开销。

1.2 安全措施:版本号 + 校验和 + 原子写入

c 复制代码
#define RECORD_FILE_VERSION 2

typedef struct {
    unsigned int version;
    unsigned int checksum;       // XOR 校验
    double bestTime[3];
} RecordFile;

写入(Fix #49, #85)

c 复制代码
void SaveRecord(void) {
    RecordFile rf = {
        .version = RECORD_FILE_VERSION,
        .checksum = 0
    };
    memcpy(rf.bestTime, record.bestTime, sizeof(record.bestTime));
    rf.checksum = CalcChecksum(&rf, sizeof(rf));

    // 🔑 Write-to-temp-then-rename(原子写入)
    FILE* f = fopen("records.dat.tmp", "wb");
    fwrite(&rf, sizeof(rf), 1, f);
    fflush(f);    // Fix #85:刷入内核
    fclose(f);
    remove("records.dat");
    rename("records.dat.tmp", "records.dat");  // 原子操作
}

读取

c 复制代码
if (rf.version != RECORD_FILE_VERSION ||
    CalcChecksum(&rf, sizeof(rf)) != 0) {
    TraceLog(WARNING, "存档损坏或版本不匹配,已重置");
    ResetToDefault();
}

1.3 Validate 兜底(Fix #44, #46, #82)

校验和过了就能放心?不,还有校验和测不到的语义错误

c 复制代码
static void ValidateStats(GL_Stats* s) {
    for (int d = 0; d < DIFFICULTY_COUNT; d++) {
        if (isnan(s->totalTime[d]) || isinf(s->totalTime[d]))
            s->totalTime[d] = 0.0;
        if (s->totalTime[d] > 8640000000.0) s->totalTime[d] = 0.0; // Fix #44
        if (s->gamesWon[d] > s->gamesPlayed[d])
            s->gamesWon[d] = s->gamesPlayed[d];
        if (s->bestStreak[d] < s->currentStreak[d])   // Fix #82
            s->bestStreak[d] = s->currentStreak[d];
    }
}

🧪 实际被测试注入过的异常值:1e300INFINITYNaN-1gamesWon > gamesPlayed ------ 全部拦截,游戏正常运作。


↩️ 2. 撤销系统(Z 键,最多 10 步)

2.1 设计

c 复制代码
typedef struct {
    GL_Cell board[MAX_ROWS][MAX_COLS];  // 完整棋盘快照
    int flagCount;
    bool firstClick;
    double elapsedTime;
} GL_GameSnapshot;

typedef struct {
    GL_GameSnapshot snapshots[10];
    int count;
} GL_UndoStack;

策略:每次玩家操作前,把当前状态完整拷贝一份。

2.2 快照大小

复制代码
16 × 30 × 24 = 11,520 字节/快照
× 10 = 115 KB
栈上分配,零堆操作。

2.3 撤销

c 复制代码
GL_GameSnapshot prev = GL_UndoStackPop(&undoStack);
memcpy(&game, &prev, sizeof(Game));  // 全量恢复

💡 如果撤销到的状态是 firstClick == true(还没点过),下一轮点击会重新布雷 ------ 坐标不同,绝对安全


💡 3. Hint 提示系统(H 键)

3.1 算法

c 复制代码
int GL_FindSafeHint(const GL_Game* game, int* outRow, int* outCol) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            if (!board[r][c].isRevealed) continue;
            int flags = 0, unknowns = 0;
            int lastR = -1, lastC = -1;

            for (8个邻居) {
                if (isFlagged) flags++;
                if (!revealed && !flagged) {
                    unknowns++;
                    lastR = nr; lastC = nc;
                }
            }

            // 🎯 旗子=雷数 → 剩下的未翻格都安全
            if (flags == adj && unknowns == 1) {
                *outRow = lastR; *outCol = lastC; return 1;
            }
        }
    }
    return 0;  // 没有数学上确定的提示
}

⚠️ 局限:只能找到"数学上确定的"安全格(旗子数 = 雷数时剩余的格)。纯推理的"概率安全格"不提示。


🔊 4. 音效系统

c 复制代码
revealSound = LoadSound("assets/audio/Boom.mp3");
if (revealSound.frameCount == 0)
    TraceLog(WARNING, "加载 Boom.mp3 失败");
动作 音效
🔨 翻开格子 Boom.mp3
🚩 标旗 flag.mp3
❌ 取消旗 unflag.mp3
🏆 胜利 Victory.mp3
💀 失败 lose.mp3

🔑 拖拽节流:拖拽时每 50ms 最多播放一次音效,防止拖过一片雷区时 50 个音效叠在一起。

S 键切换 + 持久化

c 复制代码
if (soundOn && sound.frameCount > 0) PlaySound(sound);

😊 5. Smiley Face(笑脸重置按钮)

绘制代替文字按钮,四种状态:

复制代码
游戏中(微笑)  → 正常圆圈眼睛 + 弧线嘴
按钮按下(惊讶)→ 大圆圈眼睛 + O 形嘴
胜利(墨镜)    → 8_8 眼睛 + 大弧度笑嘴
失败(死亡)    → X_X 眼睛 + 向下弧线嘴

DrawCircle + DrawLine 绘制,零纹理加载。


🔧 6. Toast 通知

复制代码
┌──────────────────────────────────┐
│  💬 游戏已保存 (ESC to close)     │   ← 2 秒后自动消失
└──────────────────────────────────┘
c 复制代码
static char toastMessage[64];
static double toastExpireTime;

void ShowToast(const char* msg) {
    strncpy(toastMessage, msg, sizeof(toastMessage) - 1);
    toastExpireTime = GetTime() + 2.0;  // 2 秒
}

📊 7. 五层防护体系

复制代码
1️⃣ 校验和 ─────── 文件完整性(防篡改/损坏)
2️⃣ 版本号 ─────── 格式演进(防不兼容)
3️⃣ Validate ───── 语义正确性(防不一致)
4️⃣ 计算截断 ───── 浮点 UB 防护(防 crash)
5️⃣ 显示 clamp ─── UI 溢出保护(防布局溢出)

🔜 下篇预告

第 6 篇:200 个单元测试:C 项目也能 TDD

Google Test + C 混合编译、test_fixes vs test_game_logic 双层架构、INT_MAX/NaN/Inf 边界值测试、ESC 八状态矩阵。


📚 系列目录

# 标题 状态
1 项目概览与扫雷核心算法 ✅ 已发布
2 Raylib 渲染架构 ✅ 已发布
3 222 个 Bug 修复教会我的事 ✅ 已发布
4 中英文双语的工程实现 ✅ 已发布
5 持久化、撤销、提示 ← 本文 ✅ 已发布
6 200 个单元测试:C 项目也能 TDD 📝 待发布
7 960×640 → 1280×720:全局缩放重构实录 📝 待发布

👍 如果你觉得有帮助,点赞 + 收藏 + 关注,三连支持作者继续写下去 🚀

相关推荐
sprlightning14 小时前
LHDC V5 算法深度解析
算法·fft·lhdcv5·imdct·mdct·rice·fac
csdn_aspnet14 小时前
C# 第k个最小元素(K’th Smallest Element)
算法·c#·排序算法
BioRunYiXue15 小时前
RACE技术全攻略:从全长克隆到靶基因验证
java·javascript·网络·人工智能·科技·算法·eclipse
ZPC821015 小时前
joy 及sensor_msgs
算法
Three_ST15 小时前
沐神-动手学习深度学习-习题答案4.4模型选择,欠拟合,过拟合
人工智能·python·深度学习·学习·算法
CIO_Alliance15 小时前
AI深度系列(2)| CNN卷积池化感受野原理:从局部感知到全局视野
人工智能·深度学习·线性代数·算法·计算机视觉·企业ai转型·企业cio联盟
探物 AI15 小时前
机器人清理墙角蜘蛛网,背后需要哪些算法?——从视觉分割到贴墙力控
算法·机器人
青 春 记 忆15 小时前
LeetCode 226. 翻转二叉树|Python 解法详解
python·算法·leetcode
民乐团扒谱机16 小时前
【微科普】压缩感知(CS):违反了奈奎斯特采样定理?不先采集也能还原信号?大白话讲透稀疏采样、L1重建与OMP,一文吃透附代码
python·神经网络·线性代数·算法·数学建模·压缩感知·奈奎斯特
88号技师16 小时前
2026年SCI-分数牛顿衍生优化算法Fractional Newton-derived optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法