学会使用 ncurses 库绘制终端图形界面,理解颜色、字符和布局的设计原理。
问题:如何在纯文本终端中绘制图形?
方案 1:使用 ASCII 字符
┌────────┐
│ ██ │
│ ██ │
└────────┘
优点:兼容性好,任何终端都能显示
缺点:不够美观
方案 2:使用 Unicode 字符
╔════════╗
║ ██ ║
║ ██ ║
╚════════╝
优点:美观
缺点:某些终端可能显示为乱码
方案 3:使用 ncurses 特殊字符
┌────────┐
│ ACS_CKBOARD │
└────────┘
优点:平衡美观和兼容性 ← 我们选择这个
| 功能 | 说明 | 本项目应用 |
|---|---|---|
| 光标定位 | 在任意位置绘制字符 | 绘制方块、边框 |
| 颜色支持 | 定义颜色对 | 7 种方块颜色 |
| 特殊字符 | 边框、棋盘格等 | 边框、方块显示 |
| 键盘输入 | 非阻塞输入 | 实时控制 |
| 窗口管理 | 多窗口支持 | 游戏区、预览区 |
void render_init(void) {
initscr(); // 启动 ncurses 模式
cbreak(); // 禁用行缓冲
noecho(); // 不显示输入字符
keypad(stdscr, TRUE); // 启用方向键
curs_set(0); // 隐藏光标
nodelay(stdscr, TRUE); // 非阻塞输入
if (has_colors()) {
start_color();
use_default_colors();
// 定义颜色对
init_pair(1, COLOR_CYAN, -1); // I 方块
init_pair(2, COLOR_YELLOW, -1); // O 方块
init_pair(3, COLOR_MAGENTA, -1); // T 方块
init_pair(4, COLOR_GREEN, -1); // S 方块
init_pair(5, COLOR_RED, -1); // Z 方块
init_pair(6, COLOR_BLUE, -1); // J 方块
init_pair(7, COLOR_WHITE, -1); // L 方块
}
}
| 函数 | 作用 | 为什么需要 |
|---|---|---|
initscr() |
启动 ncurses | 必须第一个调用 |
cbreak() |
禁用行缓冲 | 实时响应按键 |
noecho() |
不显示输入 | 不让字符干扰画面 |
keypad() |
启用特殊键 | 支持方向键 |
curs_set(0) |
隐藏光标 | 避免光标闪烁 |
nodelay() |
非阻塞输入 | 方块持续下落 |
mvaddch(y, x, 'A'); // 单个字符
mvaddstr(y, x, "Hello"); // 字符串
mvprintw(y, x, "Score: %d", score); // 格式化
ACS_CKBOARD // 棋盘格(方块)
ACS_ULCORNER // 左上角 ┌
ACS_URCORNER // 右上角 ┐
ACS_LLCORNER // 左下角 └
ACS_LRCORNER // 右下角 ┘
ACS_HLINE // 水平线 ─
ACS_VLINE // 垂直线 │
// 定义
init_pair(1, COLOR_CYAN, -1);
// 使用
attron(COLOR_PAIR(1)); // 启用
mvaddch(y, x, ACS_CKBOARD);
attroff(COLOR_PAIR(1)); // 关闭
| 方块 | 颜色 | ncurses 常量 |
|---|---|---|
| I | 青色 | COLOR_CYAN |
| O | 黄色 | COLOR_YELLOW |
| T | 紫色 | COLOR_MAGENTA |
| S | 绿色 | COLOR_GREEN |
| Z | 红色 | COLOR_RED |
| J | 蓝色 | COLOR_BLUE |
| L | 白色 | COLOR_WHITE |
// 绘制方块单元
mvaddch(y, x * 2, ACS_CKBOARD);
mvaddch(y, x * 2 + 1, ACS_CKBOARD);
原因:终端字符宽高比
单字符:█ (长方形,高是宽的 2 倍)
双字符:██ (看起来是正方形)
void render_game(Game* game) {
clear(); // 1. 清屏
render_ghost(); // 2. 影子(底层)
render_board(); // 3. 固定方块(中层)
render_tetromino(); // 4. 当前方块(上层)
render_next_piece(); // 5. UI
render_score();
refresh(); // 6. 刷新
}
为什么这个顺序?
┌─────────────────┐ NEXT: HOLD:
│ │ ┌────┐ ┌────┐
│ 游戏区域 │ │方块 │ │方块 │
│ 10x20 │ └────┘ └────┘
│ │
│ │ SCORE: LEVEL:
│ │ 1250 5
└─────────────────┘
修改方块颜色配置
尝试不同的边框样式
为游戏结束界面添加动画
下一课:调试技巧