9.2 KiB
9.2 KiB
第 5 课:卡片名字与布局
本课目标:给每种稀有度配上具体的卡片名字(小火龙、冰霜之狼、雷霆神龙……)。再把界面分区,让窗口整齐漂亮。
预计时间:35~45 分钟
学完之后:你能用数组管理一组"内容",并用
box()/color()让窗口有层次感。
0. 上课前先理理
从 "★ 稀有卡 ★" 到 "★ 稀有卡 ★ 冰霜之狼"
设计思路两步走:
1. 随机决定稀有度(这一步和第 3、4 课一样)
2. 从这一档的卡片数组里再随机选一张
卡牌表设计
| 稀有度 | 卡片名 | 张数 |
|---|---|---|
| 普通 | 小火龙、水跃鱼、绿草龟、小电鼠、土拨仔 | 5 |
| 稀有 | 烈焰飞龙、冰霜之狼、暗影豹、风暴鹰、水晶鹿 | 5 |
| 超稀有 | 雷霆神龙、凤凰王、深海巨兽 | 3 |
界面分区目标
┌────────────────────────────┐
│ [单抽] [十连抽] │ ← 顶部:操作区
├────────────────────────────┤
│ │
│ ★ 稀有卡 ★ │ ← 中部:结果区(凹陷边框)
│ 冰霜之狼 │
│ │
├────────────────────────────┤
│ 单抽:普通 0 稀有 1 超 0 │ ← 底部:统计区(灰色背景)
└────────────────────────────┘
这一课认识 2 个新朋友
| 名字 | 干什么 |
|---|---|
| 多组数组 | 每档稀有度一个数组,下标随机选 |
box() |
控件的边框样式(凹陷、凸起、扁平……) |
color() |
控件的背景色(注意和 labelcolor 文字色 区分) |
1. 起步:先看现状
打开 lesson_gui_5.cpp。骨架是第 4 课结束的样子——但是变量 info_box 之后要改名 stats_box。先编译看看:
cmake -B build -DLESSON=5
cmake --build build && ./build/gacha5
✅ 检查点 1
- 和第 4 课一样:单抽、十连抽都能用,结果只有"普通/稀有/超稀有"
2. 步骤 1:定义三组卡片数组
🤔 先想一下
- 每档数组长度可以不一样吗?(可以!)
- 怎么从数组里"随机取一张"?
✏️ 在所有函数之外(include 下面)加(替换对应 TODO):
std::string normal_cards[5] = {
"小火龙", "水跃鱼", "绿草龟", "小电鼠", "土拨仔"
};
std::string rare_cards[5] = {
"烈焰飞龙", "冰霜之狼", "暗影豹", "风暴鹰", "水晶鹿"
};
std::string super_cards[3] = {
"雷霆神龙", "凤凰王", "深海巨兽"
};
▶️ 编译运行
✅ 检查点 2
- 编译没报错(即使还没用到这些数组)
- 程序行为同前
3. 步骤 2:让单抽显示具体卡名
🤔 先想一下
- "普通卡数组有 5 张,要随机选一张"——表达式是?(答案:
normal_cards[std::rand() % 5]) - 超稀有数组只有 3 张,对应的取模数是?
✏️ 把 draw_one_callback 函数体整体替换成:
void draw_one_callback(Fl_Widget *w, void *data) {
int r = std::rand() % 100;
std::string result = "";
std::string card_name = "";
if (r < 70) {
result = "☆ 普通卡 ☆";
card_name = normal_cards[std::rand() % 5];
result_box->labelcolor(fl_rgb_color(128, 128, 128));
} else if (r < 95) {
result = "★ 稀有卡 ★";
card_name = rare_cards[std::rand() % 5];
result_box->labelcolor(FL_BLUE);
} else {
result = "★★★ 超稀有卡!! ★★★";
card_name = super_cards[std::rand() % 3];
result_box->labelcolor(FL_RED);
}
// 拼成两行:稀有度 + 换行 + 卡片名
std::string display = result + "\n " + card_name + "\n";
result_box->labelsize(24);
result_box->label(display.c_str());
}
注意:暂时把
info_box->label(...)拿掉了,下一步再统一处理统计行。
▶️ 编译运行
✅ 检查点 3
- 点单抽 → 看到两行:稀有度 + 卡片名
- 多点几次:同一档里的卡片名会随机变化
4. 步骤 3:让十连抽也显示卡名
✏️ 把 draw_ten_callback 里的 for 循环整体替换成:
for (int i = 1; i <= 10; i++) {
int r = std::rand() % 100;
all += "第" + std::to_string(i) + "抽:";
if (r < 70) {
all += "普通 " + normal_cards[std::rand() % 5];
} else if (r < 95) {
all += "★稀有 " + rare_cards[std::rand() % 5];
rare_count++;
} else {
all += "★★★超稀有 " + super_cards[std::rand() % 3];
super_count++;
}
all += "\n";
}
只是在每个分支末尾把卡名拼了上去。
▶️ 编译运行
✅ 检查点 4
- 十连抽 10 行结果,每行都有卡名
- 同一档里卡名随机变化
5. 步骤 4:分区布局,让界面变好看 ⭐
现在界面看着像草稿。我们用 box() 和 color() 给它分区。
顺手做一件清理:把 info_box 改名 stats_box(语义更准——它显示的是统计而不是单次信息)。
✏️ 改 4 处
(1) 全局变量改名(替换对应 TODO):
Fl_Box *stats_box;
(删掉原来的 Fl_Box *info_box;)
(2) 用编辑器把整个文件里所有 info_box 替换成 stats_box(两个回调函数里都有用)。
(3) 改窗口高度:
Fl_Window *window = new Fl_Window(420, 520, "抽卡世界 - 第5课");
(4) 把 result_box 和原来的 info_box 创建代码换成新版(替换 main 里相关 3 段):
result_box = new Fl_Box(20, 80, 380, 350, "点击按钮开始抽卡!");
result_box->labelsize(22);
result_box->align(FL_ALIGN_TOP | FL_ALIGN_LEFT);
result_box->box(FL_DOWN_BOX); // ← 凹陷边框,像卡片
stats_box = new Fl_Box(20, 445, 380, 40, "等待抽卡...");
stats_box->labelsize(14);
stats_box->box(FL_FLAT_BOX);
stats_box->color(fl_rgb_color(230, 230, 230)); // ← 浅灰背景
也别忘了改按钮位置(让它们和新的窗口宽度匹配):
Fl_Button *btn1 = new Fl_Button(40, 15, 150, 45, "抽一张");
Fl_Button *btn10 = new Fl_Button(230, 15, 150, 45, "十连抽");
💡 新工具:box() 和 color()
result_box->box(FL_DOWN_BOX); // 边框样式:凹陷
stats_box->color(fl_rgb_color(230, 230, 230)); // 背景色
常用边框样式:
| 样式 | 效果 |
|---|---|
FL_NO_BOX |
没有边框(默认) |
FL_FLAT_BOX |
扁平、有背景 |
FL_UP_BOX |
凸起 |
FL_DOWN_BOX |
凹陷 |
FL_EMBOSSED_BOX |
浮雕 |
⚠ 别混淆:color() 是背景色,labelcolor() 是文字色。
▶️ 编译运行
✅ 检查点 5
- 中间是个凹陷框,结果显示在里面
- 底部是灰底的统计区
- 单抽 / 十连抽都还能用
- 整体看起来"像个游戏"了
🎉 第 5 课通关!
6. 玩一玩
- 把三组卡片名全换成你自己的主题(宝可梦、数码宝贝、三国武将、星座……)。
- 超稀有再加 2 张,别忘了改取模数
% 3→% 5。 - 把
FL_DOWN_BOX改成FL_UP_BOX、FL_EMBOSSED_BOX试试。 - 改窗口背景色:在
Fl_Window创建后加window->color(fl_rgb_color(245, 245, 255));。 - 思考题:如果数组长度从 5 改成 8,
rand() % 5不改会怎样?
7. 课后挑战(选做)
- A. 图鉴按钮:再加一个按钮 "图鉴",点了把三组数组里所有卡名拼成长字符串显示出来。
- B. 突出超稀有:在单抽回调里,如果抽到超稀有就把
result_box->box(FL_UP_BOX);(凸起感);其他档恢复FL_DOWN_BOX。 - C. 思考题:现在两个回调里都有"判断
r→ 选卡名"的相似代码。能写一个函数std::string get_card(int &rarity),让两个回调都调用它吗?(这是第 6 课会做的事,可以提前试)
8. 卡住了?常见小问题
| 现象 | 多半是 |
|---|---|
| 数组越界崩溃 | % N 没和数组长度对齐 |
| 同档卡名总是同一个 | rand() % N 放错位置,或一次抽卡里调多次但漏写 |
编译报 stats_box 未定义 |
改名时漏了某处的 info_box |
| 文字看不见但背景能看到 | labelcolor 和 color 设成同色了 |
| 凹陷框看不见 | result_box->box(FL_DOWN_BOX); 没加 |
9. 偷看老师答案
cmake -B build -DLESSON=5 -DUSE_SOLUTION=ON
cmake --build build && ./build/gacha5
10. 下一课预告
第 6 课是大作业:把前面所有功能组合起来——加金币系统(每抽消耗金币)、累计统计、重置/退出按钮,做一个真正像样的抽卡游戏。
📝 学完记得在
学习进度与状态-GUI.md里把第 5 课打勾。
下一课:第06课-完整抽卡游戏.md