250619-第21个工作日-周四:授课老师出差广州。自习今天。

1,

​
PS D:\software\BEST_01\1_c语言\第五章节\2048> .\a.exe
[DEBUG] main loop: currentState = 0
[DEBUG] 进入 STATE_MENU...
[DEBUG] 当前菜单状态:ctx->currentState=0
​
===== 2048 主菜单 =====
​
1. 开始游戏
​
2. 游戏说明
​
3. # 退出程序
​
   请输入选项 (1-3):
​
 我发现2048小游戏,2048小游戏也就是a.exe一旦手动运行这一个动作就相当于1级页面默认启用了循环且当前无任何交互和展示信息。而接下来的交互自动进入了二级页面也是自带了循环语句,二级页面是否显示内容, 必须是只能由三级循环页面的函数进行展示,并且这个三级页面的功能函数里面也要必须有开启循环的代码关键字,和用户输入交互进行提示判断switch。     三级页面的当前循环所展示的所有内容,必须是只能由四级循环页面的函数进行展示,并且这个功能函数里面也要必须有开启循环的代码关键字while,和用户输入交互进行提示判断.    解释并告诉我它是什么?(业务规则)

回答: 在无意中描述出了一种 “层级嵌套状态机 + 控制流循环机制”,这其实是一种典型的游戏或应用中的 多层页面状态控制逻辑

控制器按钮 控制的页面(函数) 是否自带循环 是否交互
电源开关 main() ❌(只分发)
菜单按钮 show_main_menu() ✅(输入 1-3)
开始按钮 start_game() ✅(输入wasd)
移动处理逻辑 process_move() ❌(逻辑函数)
术语 含义
一级页面 main() 入口主循环,是整个程序的大脑,监控全局状态(比如 ctx->currentState)。它负责判断当前在哪个大状态下:菜单、游戏、退出等。
二级页面 show_main_menu(ctx),表示的是主菜单模块,它本身包含一个小循环,用于不断提示用户输入,并根据输入决定是否进入游戏、查看说明或退出。
三级页面 start_game(ctx),即游戏主循环,它是一个更深层的状态处理函数,内部有 while (1) 监控用户输入(w/a/s/d/q),每一步都刷新棋盘。
四级页面(嵌套功能层) process_move()slide_and_merge() 等,每次滑动调用的内部算法逻辑,不负责控制循环,只负责处理“这一步的业务”

1.1 ,具备了“分层状态驱动程序”的设计思维!为什么要这么写?(逻辑目的)

回答:专业术语补充你所实现的思维结构,在计算机科学里常被称为:

  • FSM(Finite State Machine,有限状态机)

  • Event Loop + State Dispatcher(事件循环 + 状态分发器)

  • Hierarchical Control Flow(分层控制流)

  • 这些都是 嵌入式系统、游戏引擎、图形界面系统 等常见的架构设计模式。

目的编号 目的名称 说明
🎯 1 逻辑清晰,职责分明(Separation of Concerns) 每一层只做自己该做的事,避免混乱。例如 main() 只负责流程调度,start_game() 只负责游戏逻辑。这样便于维护。
🧩 2 模块化、易扩展(Modularity & Scalability) 如果你将来要加一个“排行榜页面”或“暂停功能”,你只需要新增一个状态 + 新的函数即可,原有结构无需大改。
🔁 3 循环控制局部化(Scoped Loops) 每一层函数内部控制自己的循环,例如 start_game() 内部处理 WASD 移动,这样不会影响其他功能,避免“循环混战”。
🎮 4 符合“有限状态机”模型(Finite State Machine) 游戏天生是状态驱动的系统。比如:主菜单 → 游戏中 → 游戏结束 → 返回主菜单,每一个都是状态迁移。代码结构应该反映这一点。
📟 5 避免陷入“菜单/页面地狱” 如果所有逻辑都塞进一个大循环,你将难以理解和维护(比如 switch 嵌套 switch 嵌套 if ),越改越乱。分层能避免“页面地狱”。
🧪 6 便于测试与调试(Testability & Debugging) 你可以单独测试 show_main_menu()start_game(),不需要整套流程一起跑,提高效率,也方便打 debug 日志。
📚 7 符合 MVC / MVVM 等现代编程思想 分层状态设计其实是 Model-View-Controller 设计模式的简化实现。C 语言里没有 OOP,但你已经实现了类似结构。
👥 8 为多人协作做准备(Team Collaboration Ready) 如果团队开发,每个人可以维护不同的页面或状态逻辑,不会互相踩脚。比如我写菜单,你写游戏,我不需要看你代码你也能独立开发。

1.2 ,它还有别的写法吗?(扩展思考)

回答:

项目规模 推荐方案
入门 / 小项目 原始 switch + while 模式
中等复杂 函数指针数组 + 状态枚举✅ 优点:避免了冗长的 switch-case,维护性更强。 ❌ 缺点:对于初学者稍微抽象一些。
大型工程 状态结构表 / 栈式页面导航。方案四:基于栈的“页面导航”机制(仿 GUI 系统)✅ 优点:可回退页面(像手机返回键),适合 GUI 模拟。 ❌ 缺点:逻辑更复杂,容易栈溢出,维护代价高。
模拟 GUI / 操作系统 事件驱动模型,事件驱动的调度系统(Event Loop Dispatcher)适合多人协作和系统模块化。✅ 优点:便于系统扩展和消息队列管理(适合异步UI) ❌ 缺点:不太适合纯 C 的控制台项目,更偏向事件系统架构。

1.9,写一个状态枚举驱动模板,让我下次写菜单类系统直接套! 回答:你只需记住三个步骤:

  1. 定义状态枚举:如 STATE_MENUSTATE_STARTSTATE_HELP

  2. 为每个状态写一个 handle_XXX(AppContext*) 函数

  3. 把这些函数放进函数指针数组,用 ctx->current 来调用。 强!

2,c语言项目中,可以在vscode的console的打印日志,统一抓取一份日志文件吗? 怎么操作?

回答:封装在utils.c的代码和头文件中。

​
void log_debug(const char *format, ...) {
    va_list args;
​
    // 打印到控制台
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
​
    // 打开日志文件(追加模式)
    FILE *logFile = fopen("game.log", "a");
    if (logFile) {
        // 添加时间戳
        time_t now = time(NULL);
        struct tm *tm_info = localtime(&now);
        char time_buf[32];
        strftime(time_buf, sizeof(time_buf), "[%Y-%m-%d %H:%M:%S] ", tm_info);
        fprintf(logFile, "%s", time_buf);
​
        // 写入日志内容
        va_start(args, format);
        vfprintf(logFile, format, args);
        va_end(args);
​
        fclose(logFile);
    }
}

3,你的困惑其实是——“明明原来的 can_move() 逻辑也没错,为什么一加日志就好了?”

这是一个典型的**C语言项目中“调试副作用”现象

回答:加了日志后你才知道:原来 逻辑没错,状态走通,只是你没看到。
| 项 | 原因                                       |
| - | ---------------------------------------- |
| ✅ | 原 `can_move()` 没有问题                      |
| ✅ | 你在调试时缺少“内部状态观察窗口”                        |
| ✅ | 日志输出提供了**状态可视化 + I/O延迟**,恰好让状态同步及时       |
| ✅ | `reset_board()` 等副作用函数**未被再次调用**可能是偶然关键点 |
 建议你以后开发中这样做:
所有判断分支都加日志,尤其是 return 前 
状态变化都用结构体变量打印(如当前棋盘、ctx状态) 
为关键状态加断点 or 触发日志路径验证 
封装一个专属 print_grid(),用于调试局面可视化

style="display: none !important;">

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐