Files
chouka/第03课-随机抽卡.md
2026-05-16 12:05:04 +08:00

247 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 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 <cstdlib> // rand, srand
#include <ctime> // 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 <cstdio> // 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 <cstdlib>``<ctime>` |
| 编译报 `sprintf` 未定义 | 忘了 `#include <cstdio>` |
| 信息行显示乱码 | `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`