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

7.1 KiB
Raw Blame History

第 3 课:随机抽卡——核心来了

本课目标:把命令行时学过的 rand() + if/else 搬进 GUI。点按钮 → 随机出"普通卡 / 稀有卡 / 超稀有卡",不同稀有度配不同颜色。

预计时间40 分钟

学完之后:你能在回调函数里写随机和判断,让 GUI 程序"每次都不一样"。


0. 上课前先理理

一句话回顾旧知识

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),但回调里还没写"抽卡"。

cmake -B build -DLESSON=3
cmake --build build && ./build/gacha3

检查点 1

  • 窗口出来了,按钮 "抽一张!" 在上面
  • 下面写着 "点击按钮抽卡吧!"
  • 点按钮文字会变成 "(写好步骤 4 后这里会变)"(提示当前回调没真正工作)

2. 步骤 1:让随机数能用起来

🤔 先想一下

  • 设随机种子 srand() 要放哪儿?回调里行不行?
  • 为什么不能每次点按钮都 srand 一下?

💡 答案:种子只能设一次,否则每次都"重置",结果会重复甚至不变。所以 srandmain 里、Fl::run() 之前。

✏️ 改两个地方

(1) 加 include(替换对应 TODO):

#include <cstdlib>   // rand, srand
#include <ctime>     // time

(2) 在 main 的第一行加(替换对应 TODO):

std::srand(std::time(nullptr));

▶️ 编译运行

cmake --build build && ./build/gacha3

检查点 2

  • 编译没报错
  • 程序行为和之前一样(因为我们还没真正用 rand

3. 步骤 2:在回调里掷骰子

✏️draw_callback 函数里第一行加(替换对应 TODO):

int r = std::rand() % 100;

这一行还看不到效果。下一步把 r 显示在窗口上。


4. 步骤 3:用 sprintf 把数字显示出来

🤔 先想一下

  • result_box->label(...) 只接受字符串。你怎么把整数 r 变成字符串?
  • 以前命令行用 std::cout << "r=" << r,但 label 没有 <<。怎么办?

💡 新工具:sprintf

char info[50];                            // 准备一个 50 字节的盒子
std::sprintf(info, "本次点数:%d", r);    // 把 "本次点数:" + r 的值 塞进盒子
info_box->label(info);                    // 把盒子的内容贴到 label 上

%d 是占位符,运行时被 r 的值替换。

✏️ 改 3 个地方

(1) 顶上加(替换对应 TODO):

#include <cstdio>    // sprintf

(2) 在全局区,result_box 下面加(替换对应 TODO):

Fl_Box *info_box;

(3) 在 main 里,紧挨 result_box 的创建代码下面加(替换对应 TODO):

info_box = new Fl_Box(50, 200, 300, 50, "");
info_box->labelsize(14);

(4) 在 draw_callback 里、int r = ... 那一行下面加(替换对应 TODO):

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 后这里会变)"); 整段替换成:

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. 偷看老师答案

cmake -B build -DLESSON=3 -DUSE_SOLUTION=ON
cmake --build build && ./build/gacha3

10. 下一课预告

第 4 课加一个 "十连抽" 按钮:一次抽 10 张全部显示。你会用上老朋友 for 循环字符串拼接

📝 学完记得在 学习进度与状态-GUI.md 里把第 3 课打勾。

下一课第04课-十连抽.md