学会实现最高分记录系统,掌握文件 I/O 和数据结构的设计。
最高分系统的作用:
学到的技能:
typedef struct {
char name[32]; // 玩家名字
int score; // 分数
int lines; // 消除行数
int level; // 等级
time_t date; // 时间戳
} HighScore;
为什么这些字段?
| 字段 | 类型 | 作用 |
|---|---|---|
name |
char[32] | 玩家名字,32 字节足够 |
score |
int | 分数,用于排序 |
lines |
int | 消除行数,额外信息 |
level |
int | 等级,额外信息 |
date |
time_t | 时间戳,记录达成时间 |
#define MAX_SCORES 10 // 保存前 10 名
HighScore high_scores[MAX_SCORES];
为什么是 10 名?
void save_high_score(const char* name, int score,
int lines, int level) {
// 1. 创建新记录
HighScore new_score = {
.score = score,
.lines = lines,
.level = level,
.date = time(NULL) // 当前时间
};
strncpy(new_score.name, name, 31);
new_score.name[31] = '\0'; // 确保字符串结束
// 2. 插入到排序数组中
for (int i = 0; i < MAX_SCORES; i++) {
if (score > high_scores[i].score) {
// 3. 后移后面的记录
for (int j = MAX_SCORES - 1; j > i; j--) {
high_scores[j] = high_scores[j-1];
}
// 4. 插入新记录
high_scores[i] = new_score;
break;
}
}
// 5. 保存到文件
FILE* f = fopen("data/highscores.txt", "w");
if (!f) return;
for (int i = 0; i < MAX_SCORES; i++) {
if (high_scores[i].score > 0) {
fprintf(f, "%s %d %d %d %ld\n",
high_scores[i].name,
high_scores[i].score,
high_scores[i].lines,
high_scores[i].level,
high_scores[i].date);
}
}
fclose(f);
}
初始状态(按分数降序):
[0] Alice 5000
[1] Bob 3200
[2] Carol 1500
[3] Dave 1000
...
新分数:2500
步骤 1:找到插入位置
i=0: 2500 > 5000? 否,继续
i=1: 2500 > 3200? 否,继续
i=2: 2500 > 1500? 是!插入位置是 i=2
步骤 2:后移后面的记录
[9] = [8]
[8] = [7]
...
[3] = [2]
步骤 3:插入新记录
[2] = 新记录 (2500)
结果:
[0] Alice 5000
[1] Bob 3200
[2] You 2500 ← 新记录
[3] Carol 1500
[4] Dave 1000
...
void load_high_scores(void) {
FILE* f = fopen("data/highscores.txt", "r");
if (!f) {
// 文件不存在,初始化为空
memset(high_scores, 0, sizeof(high_scores));
return;
}
// 读取记录
for (int i = 0; i < MAX_SCORES; i++) {
int result = fscanf(f, "%31s %d %d %d %ld",
high_scores[i].name,
&high_scores[i].score,
&high_scores[i].lines,
&high_scores[i].level,
&high_scores[i].date);
if (result != 5) {
// 读取失败或文件结束
// 填充剩余为空记录
for (int j = i; j < MAX_SCORES; j++) {
memset(&high_scores[j], 0, sizeof(HighScore));
}
break;
}
}
fclose(f);
}
fscanf(f, "%31s %d %d %d %ld", ...)
格式符说明:
%31s - 读取字符串,最多 31 个字符(留 1 个给\0)
%d - 读取整数(score)
%d - 读取整数(lines)
%d - 读取整数(level)
%ld - 读取长整数(time_t date)
返回值:成功读取的字段数
如果返回 5,说明成功读取一条完整记录
bool is_new_high_score(int score) {
// 如果还没填满,肯定是新高分
for (int i = 0; i < MAX_SCORES; i++) {
if (high_scores[i].score == 0) {
return true;
}
}
// 如果分数超过最后一名,是新高分
return score > high_scores[MAX_SCORES - 1].score;
}
优化:提前判断
// 在游戏结束时,先判断是否可能上榜
if (!is_new_high_score(game->score)) {
// 不可能上榜,直接显示排行榜
show_high_scores();
return;
}
// 可能上榜,显示输入名字界面
// ...
if (is_new_high_score(game->score)) {
// 1. 显示输入界面
clear();
mvprintw(5, 10, "╔══════════════════════════════╗");
mvprintw(6, 10, "║ NEW HIGH SCORE! ║");
mvprintw(7, 10, "║ Score: %-6d ║", game->score);
mvprintw(8, 10, "║ Enter your name: ║");
mvprintw(9, 10, "║ > < ║");
mvprintw(10, 10, "╚══════════════════════════════╝");
refresh();
// 2. 获取用户输入
echo(); // 显示输入字符
curs_set(1); // 显示光标
move(9, 35); // 移动到输入位置
char name[32] = {0};
int i = 0;
int ch;
while ((ch = getch()) != '\n' && ch != KEY_ENTER && i < 31) {
if (ch == 127 || ch == KEY_BACKSPACE || ch == 8) {
// 退格
if (i > 0) {
i--;
name[i] = '\0';
mvaddch(9, 35 + i, ' ');
move(9, 35 + i);
}
} else if (ch >= 32 && ch <= 126) {
// 可打印字符
name[i] = ch;
i++;
addch(ch);
}
refresh();
}
name[i] = '\0';
// 3. 默认名字
if (strlen(name) == 0) {
strcpy(name, "Player");
}
// 4. 保存
save_high_score(name, game->score,
game->lines_cleared, game->level);
// 5. 恢复设置
noecho();
curs_set(0);
}
退格处理:
if (ch == 127 || ch == KEY_BACKSPACE || ch == 8) {
if (i > 0) {
i--; // 索引减 1
name[i] = '\0'; // 字符串截断
mvaddch(9, 35 + i, ' ');// 擦除屏幕上的字符
move(9, 35 + i); // 移动光标
}
}
为什么检查多个退格码?
127 - Delete 键KEY_BACKSPACE - ncurses 退格8 - Ctrl+H(传统退格)void show_high_scores(void) {
printf("\n╔══════════════════════════════════════╗\n");
printf("║ TETRIS HIGH SCORES ║\n");
printf("╠══════╤═══════════╤═══════╤═══════════╣\n");
printf("║ Rank │ Name │ Score │ Level ║\n");
printf("╠══════╪═══════════╪═══════╪═══════════╣\n");
for (int i = 0; i < MAX_SCORES; i++) {
if (high_scores[i].score > 0) {
printf("║ %2d │ %-9s │ %5d │ %2d ║\n",
i + 1,
high_scores[i].name,
high_scores[i].score,
high_scores[i].level);
} else {
printf("║ %2d │ │ │ ║\n", i + 1);
}
}
printf("╚══════╧═══════════╧═══════╧═══════════╝\n\n");
}
"%2d" - 右对齐,2 位宽度
"%-9s" - 左对齐,9 位宽度(名字)
"%5d" - 右对齐,5 位宽度(分数)
效果:
╔══════════════════════════════════════╗
║ TETRIS HIGH SCORES ║
╠══════╤═══════════╤═══════╤═══════════╣
║ Rank │ Name │ Score │ Level ║
╠══════╪═══════════╪═══════╪═══════════╣
║ 1 │ Alice │ 5000 │ 8 ║
║ 2 │ Bob │ 3200 │ 6 ║
║ 3 │ Player │ 1250 │ 5 ║
╚══════╧═══════════╧═══════╧═══════════╝
Alice 5000 42 8 1708934400
Bob 3200 35 6 1708934500
Player 1250 20 5 1708934600
每行字段:
为什么用空格分隔?
修改最高分数量为 20 名
添加日期格式化显示(把时间戳转为可读日期)
实现删除指定排名的功能