Files
chouka/第05课-卡片名字与布局.md
T
2026-05-16 12:05:04 +08:00

9.2 KiB
Raw Blame History

第 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. 玩一玩

  1. 把三组卡片名全换成你自己的主题(宝可梦、数码宝贝、三国武将、星座……)。
  2. 超稀有再加 2 张,别忘了改取模数 % 3% 5
  3. FL_DOWN_BOX 改成 FL_UP_BOXFL_EMBOSSED_BOX 试试。
  4. 改窗口背景色:在 Fl_Window 创建后加 window->color(fl_rgb_color(245, 245, 255));
  5. 思考题:如果数组长度从 5 改成 8rand() % 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
文字看不见但背景能看到 labelcolorcolor 设成同色了
凹陷框看不见 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