# 第 3 课:随机抽卡——核心来了 > **本课目标**:把命令行时学过的 `rand()` + `if/else` **搬进 GUI**。点按钮 → 随机出"普通卡 / 稀有卡 / 超稀有卡",不同稀有度配不同颜色。 > > **预计时间**:40 分钟 > > **学完之后**:你能在回调函数里写随机和判断,让 GUI 程序"每次都不一样"。 --- ## 0. 上课前先理理 ### 一句话回顾旧知识 ```cpp std::srand(std::time(nullptr)); // 设一次种子 int r = std::rand() % 100; // 每次生成 0~99 的随机数 if (r < 70) { ... 普通 ... } else if (r < 95) { ... 稀有 ... } else { ... 超稀有 ... } ``` 这些你以前用 `std::cout` 打印结果。现在改用 `Fl_Box` 显示。 ### 概率设计 | 随机数 r | 稀有度 | 颜色 | 大约概率 | |---------|--------|------|---------| | 0 ~ 69 | 普通卡 | 灰色 | 70% | | 70 ~ 94 | 稀有卡 | 蓝色 | 25% | | 95 ~ 99 | 超稀有卡 | 红色 | 5% | ### 这一课认识 2 个新朋友 + 1 个老技巧的新用法 | 名字 | 干什么 | |-----------------|---------------------------------------------| | `sprintf` | 把数字"塞"进字符串里(GUI 不能直接 `cout`) | | `fl_rgb_color` | 用 R/G/B 三个数自定义任意颜色 | | `labelcolor` | (老朋友)配合上面两个,让稀有度看一眼区分 | --- ## 1. 起步:先编译空骨架 打开 `lesson_gui_3.cpp`。骨架里已经准备好了第 2 课学过的东西(窗口 + 按钮 + 回调 + 全局 `result_box`),但回调里还没写"抽卡"。 ```bash cmake -B build -DLESSON=3 cmake --build build && ./build/gacha3 ``` ### ✅ 检查点 1 - [ ] 窗口出来了,按钮 "抽一张!" 在上面 - [ ] 下面写着 "点击按钮抽卡吧!" - [ ] 点按钮文字会变成 "(写好步骤 4 后这里会变)"(提示当前回调没真正工作) --- ## 2. 步骤 1:让随机数能用起来 ### 🤔 先想一下 - 设随机种子 `srand()` 要放哪儿?回调里行不行? - 为什么不能每次点按钮都 `srand` 一下? > 💡 答案:种子只能设一次,否则每次都"重置",结果会重复甚至不变。所以 `srand` 放 `main` 里、`Fl::run()` 之前。 ### ✏️ 改两个地方 **(1) 加 include**(替换对应 TODO): ```cpp #include // rand, srand #include // time ``` **(2) 在 `main` 的第一行加**(替换对应 TODO): ```cpp std::srand(std::time(nullptr)); ``` ### ▶️ 编译运行 ```bash cmake --build build && ./build/gacha3 ``` ### ✅ 检查点 2 - [ ] 编译没报错 - [ ] 程序行为和之前一样(因为我们还没真正用 `rand`) --- ## 3. 步骤 2:在回调里掷骰子 ### ✏️ 在 `draw_callback` 函数里**第一行**加(替换对应 TODO): ```cpp int r = std::rand() % 100; ``` > 这一行还看不到效果。下一步把 `r` 显示在窗口上。 --- ## 4. 步骤 3:用 `sprintf` 把数字显示出来 ### 🤔 先想一下 - `result_box->label(...)` 只接受字符串。你怎么把整数 `r` 变成字符串? - 以前命令行用 `std::cout << "r=" << r`,但 `label` 没有 `<<`。怎么办? ### 💡 新工具:`sprintf` ```cpp char info[50]; // 准备一个 50 字节的盒子 std::sprintf(info, "本次点数:%d", r); // 把 "本次点数:" + r 的值 塞进盒子 info_box->label(info); // 把盒子的内容贴到 label 上 ``` `%d` 是占位符,运行时被 `r` 的值替换。 ### ✏️ 改 3 个地方 **(1) 顶上加**(替换对应 TODO): ```cpp #include // sprintf ``` **(2) 在全局区,`result_box` 下面加**(替换对应 TODO): ```cpp Fl_Box *info_box; ``` **(3) 在 `main` 里,紧挨 `result_box` 的创建代码下面加**(替换对应 TODO): ```cpp info_box = new Fl_Box(50, 200, 300, 50, ""); info_box->labelsize(14); ``` **(4) 在 `draw_callback` 里、`int r = ...` 那一行下面加**(替换对应 TODO): ```cpp char info[50]; std::sprintf(info, "本次点数:%d", r); info_box->label(info); ``` ### ▶️ 编译运行 ### ✅ 检查点 3 - [ ] 点按钮 → 下方多出一行 "本次点数:XX",每次 XX 都不同 - [ ] 多点几次,能看到 0~99 各种数值 --- ## 5. 步骤 4:根据 `r` 决定稀有度和颜色 ⭐核心⭐ 终于到抽卡的灵魂部分了。 ### ✏️ 把 `draw_callback` 里那行假提示 `result_box->label("(写好步骤 4 后这里会变)");` **整段替换**成: ```cpp if (r < 70) { result_box->label("☆ 普通卡 ☆"); result_box->labelcolor(fl_rgb_color(128, 128, 128)); // 灰色 } else if (r < 95) { result_box->label("★ 稀有卡 ★"); result_box->labelcolor(FL_BLUE); } else { result_box->label("★★★ 超稀有卡!! ★★★"); result_box->labelcolor(FL_RED); } ``` ### 💡 新工具:`fl_rgb_color(R, G, B)` 每个数 0~255,组合任意颜色: | 颜色 | RGB | |------|------| | 纯白 | `(255, 255, 255)` | | 灰 | `(128, 128, 128)` | | 金 | `(255, 200, 0)` | | 橙 | `(255, 128, 0)` | | 紫 | `(180, 100, 200)` | ### ▶️ 编译运行 ### ✅ 检查点 4 - [ ] 点按钮 → 看到三种结果之一,且颜色不一样 - [ ] 多点 20 次,能至少出现 1 次蓝色"稀有" - [ ] 多点 50 次以上,能出现红色"超稀有" 🎉 **第 3 课通关!** 你的程序"每次都不一样"了。 --- ## 6. 玩一玩 1. 把概率改成"超稀有更多":`r < 50` 普通,`r < 80` 稀有,其他超稀有。点 20 次试试感受。 2. 把超稀有改成金色:`fl_rgb_color(255, 200, 0)`。 3. 给"超稀有"再加一行祝贺:在那个 `else` 分支里加 `info_box->label("🎉 恭喜抽到超稀有!🎉");`。 4. **思考题**:如果把 `srand` 那一行从 `main` 删掉,多次运行程序会发生什么? --- ## 7. 课后挑战(选做) - **A. 加第四档"传说卡"**:`r >= 99`(约 1%),金色,更大字号。注意调整原来 95 的判断。 - **B. 抽卡计数**:用全局 `int total = 0;` 统计点击次数,每次抽完显示在 `info_box` 上(`"第 N 次:r=XX"`)。 - **C. 思考题**:`if (r < 70) ... else if (r < 95) ...` —— 为什么 95 那行不需要写 `r >= 70 && r < 95`? --- ## 8. 卡住了?常见小问题 | 现象 | 多半是 | |------|-------| | 每次结果都一样 | `srand` 没写、或者写在了回调里 | | 编译报 `rand` / `srand` / `time` 未定义 | 忘了 `#include ` 或 `` | | 编译报 `sprintf` 未定义 | 忘了 `#include ` | | 信息行显示乱码 | `char info[50]` 太小?改成 `info[100]` 试试 | | 颜色没变 | `labelcolor` 拼错;或 `fl_rgb_color` 写成了 `FL_rgb_color` | --- ## 9. 偷看老师答案 ```bash cmake -B build -DLESSON=3 -DUSE_SOLUTION=ON cmake --build build && ./build/gacha3 ``` --- ## 10. 下一课预告 第 4 课加一个 "十连抽" 按钮:一次抽 10 张全部显示。你会用上**老朋友 `for` 循环**和**字符串拼接**。 > 📝 学完记得在 `学习进度与状态-GUI.md` 里把第 3 课打勾。 **下一课**:`第04课-十连抽.md`