C语言:俄罗斯方块项目详解与开发笔记
目录
一、项目概述
本项目是一个使用纯C语言实现的终端版俄罗斯方块游戏,采用经典的MVC(Model-View-Controller)设计模式,支持游戏的基本功能、菜单系统和速度调节。
1.1功能特性
-
完整的俄罗斯方块游戏逻辑
-
多级游戏难度(随分数增加而加速)
-
可视化菜单界面
-
实时游戏信息显示
-
可配置的下落速度
-
非阻塞键盘输入
-
终端彩色输出和框线绘制
1.2 项目结构
tetris/
├── tetris.h # 主头文件,包含所有声明和常量
├── main.c # 主程序入口
├── model.c # 游戏模型和数据逻辑
├── view.c # 游戏界面渲染
├── controller.c # 输入控制和游戏流程
└── Makefile # 编译配置文件
二、编译说明
1. 使用提供的Makefile编译
我使用的Makefile配置如下:
# 定义目标可执行文件名
OBJ:=els
# 添加所有源文件
OBJS+=main.c
OBJS+=model.c
OBJS+=view.c
OBJS+=controller.c
# 编译规则
$(OBJ):$(OBJS)
gcc $^ -o $@ -g
# 伪目标定义
.PHONY:
clean:
rm $(OBJ)
memcheck:
valgrind --tool=memcheck --leak-check=full ./${OBJ}
编译命令:
make # 编译项目
./els # 运行游戏
make clean # 清理编译文件
make memcheck # 内存检查
2. Makefile详解
-
OBJ: 定义最终生成的可执行文件名(这里为
els) -
OBJS: 列出所有需要编译的源文件
-
-g选项: 添加调试信息,便于使用gdb调试
-
.PHONY: 声明伪目标,防止与同名文件冲突
-
memcheck: 使用Valgrind进行内存泄漏检查
三、核心模块设计
1. 数据模型(Model)
文件: model.c, tetris.h
主要功能:
-
方块形状定义(7种经典俄罗斯方块)
-
游戏状态管理
-
碰撞检测逻辑
-
分数计算和等级系统
-
行消除算法
关键数据结构:
typedef struct {
int type; // 方块类型
int rotation; // 旋转状态
int x, y; // 屏幕位置
int shape[4][4]; // 4x4方块形状
} Tetromino;
typedef struct {
int board[GAME_HEIGHT][GAME_WIDTH]; // 游戏棋盘
Tetromino current; // 当前下落方块
Tetromino next; // 下一个方块预览
int score; // 当前分数
int level; // 当前等级
int lines_cleared; // 消除行数
GameState state; // 游戏状态
} GameModel;
2. 视图渲染(View)
文件: view.c
主要功能:
-
游戏主界面绘制
-
菜单系统界面
-
信息面板显示
-
VT100终端控制码使用
-
彩色输出和边框绘制
界面布局:

3. 控制器(Controller)
文件: controller.c
主要功能:
-
非阻塞键盘输入处理
-
游戏状态机管理
-
菜单导航控制
-
终端属性配置
-
游戏主循环控制
输入处理:
// 游戏控制键
A/D键: 左右移动
W键: 旋转方块
S键: 加速下落
空格键: 快速下落到底
P键: 暂停/继续
Q键: 返回主菜单
四、关键算法实现
1. 方块旋转算法
// 顺时针旋转90度
int rotated[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
rotated[j][3-i] = shape[i][j];
}
}
2. 碰撞检测
int can_move(const GameModel *model, int dx, int dy) {
// 检查边界
if (new_x < 0 || new_x >= GAME_WIDTH || new_y >= GAME_HEIGHT) {
return 0;
}
// 检查与已有方块的重叠
if (new_y >= 0 && model->board[new_y][new_x]) {
return 0;
}
return 1;
}
3. 行消除和计分
// 计分规则
int points[] = {0, 100, 300, 500, 800};
model->score += points[line_count] * model->level;
model->level = model->lines_cleared / 10 + 1;
五、终端控制技术
1. VT100控制码
#define CLEAR_SCREEN "\033[2J\033[H" // 清屏并移动光标到左上角
#define HIDE_CURSOR "\033[?25l" // 隐藏光标
#define SHOW_CURSOR "\033[?25h" // 显示光标
#define RESET_COLOR "\033[0m" // 重置颜色
2. 非阻塞输入
void init_terminal(void) {
tcgetattr(STDIN_FILENO, &old_term);
new_term = old_term;
new_term.c_lflag &= ~(ICANON | ECHO); // 关闭规范模式和回显
tcsetattr(STDIN_FILENO, TCSANOW, &new_term);
}
六、调试技巧
1. 使用Valgrind进行内存检查
make memcheck
这将检查内存泄漏、非法内存访问等问题。
2. GDB调试
由于Makefile中使用了-g选项,可以直接使用gdb调试:
gdb ./els
(gdb) break main
(gdb) run
七、性能优化
-
帧率控制:通过
usleep(50000)控制游戏刷新率为20FPS -
局部刷新:只在必要时重绘屏幕区域
-
缓冲区管理:使用
fflush(stdout)确保输出及时显示
八、扩展建议
-
添加高分榜:保存和显示历史最高分
-
增加音效:使用系统声音提示
-
网络对战:实现双人对战功能
-
更多方块:增加特殊方块类型
-
关卡设计:预设不同的初始棋盘布局
九、学习收获
通过这个项目,我掌握了:
-
C语言高级特性:结构体、枚举、函数指针的使用
-
模块化编程:合理的文件分割和接口设计
-
终端编程:VT100控制码、非阻塞输入处理
-
游戏开发基础:游戏循环、状态机、碰撞检测
-
调试技巧:Makefile编写、Valgrind内存检查
十、完整代码
1. tetris.h
#ifndef _TETRIS_H
#define _TETRIS_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>
// ================ 游戏常量定义 ================
// 游戏区域大小
#define GAME_HEIGHT 20
#define GAME_WIDTH 12
#define INFO_WIDTH 20
#define TOTAL_WIDTH (GAME_WIDTH + INFO_WIDTH + 2)
// 方块形状定义(7种基本形状)
extern const int SHAPES[7][4][4];
// 游戏状态枚举
typedef enum {
STATE_MENU, // 主菜单
STATE_PLAYING, // 游戏中
STATE_PAUSED, // 暂停
STATE_SETTINGS, // 设置
STATE_GAME_OVER // 游戏结束
} GameState;
// 速度设置
typedef struct {
int initial_delay; // 初始下落延迟
int min_delay; // 最小延迟
int max_delay; // 最大延迟
} SpeedSettings;
// 方块数据结构
typedef struct {
int type; // 方块类型(0-6)
int rotation; // 旋转状态(0-3)
int x, y; // 位置
int shape[4][4]; // 当前形状
} Tetromino;
// 游戏模型结构
typedef struct {
// 游戏区域(区域1)
int board[GAME_HEIGHT][GAME_WIDTH];
// 当前方块
Tetromino current;
// 下一个方块(区域2)
Tetromino next;
// 游戏状态
int score;
int level;
int lines_cleared;
int game_over;
int paused;
// 计时相关
int gravity_counter;
int gravity_delay;
// 游戏状态
GameState state;
// 速度设置
SpeedSettings speed;
// 设置菜单选项
int selected_option;
// 菜单选项数量
int menu_item_count;
} GameModel;
// ================ 视图相关常量 ================
// VT100控制码定义
#define CLEAR_SCREEN "\033[2J\033[H"
#define HIDE_CURSOR "\033[?25l"
#define SHOW_CURSOR "\033[?25h"
#define RESET_COLOR "\033[0m"
#define BLOCK_COLOR "\033[42m" // 绿色背景
#define WALL_COLOR "\033[47m" // 白色背景
#define NEXT_COLOR "\033[44m" // 蓝色背景
#define TEXT_COLOR "\033[37m" // 白色文字
#define SCORE_COLOR "\033[33m" // 黄色文字
#define MENU_COLOR "\033[35m" // 紫色文字
#define SELECTED_COLOR "\033[36;1m" // 青色高亮
#define BORDER_COLOR "\033[36m" // 青色边框
// 绘制边框字符
#define TOP_LEFT_CORNER "┌"
#define TOP_RIGHT_CORNER "┐"
#define BOTTOM_LEFT_CORNER "└"
#define BOTTOM_RIGHT_CORNER "┘"
#define HORIZONTAL_LINE "─"
#define VERTICAL_LINE "│"
#define T_LEFT "├"
#define T_RIGHT "┤"
#define T_TOP "┬"
#define T_BOTTOM "┴"
#define CROSS "┼"
// ================ 函数声明 ================
// 模型函数
void init_game(GameModel *model);
int can_move(const GameModel *model, int dx, int dy);
void move_current(GameModel *model, int dx, int dy);
int can_rotate(const GameModel *model);
void rotate_current(GameModel *model);
void lock_piece(GameModel *model);
int check_lines(GameModel *model);
void clear_lines(GameModel *model, int lines[], int count);
void generate_new_piece(GameModel *model);
// 视图函数
void render_game(const GameModel *model);
// 控制函数
void handle_menu_input(GameModel *model, char key);
void handle_settings_input(GameModel *model, char key);
void handle_game_input(GameModel *model, char key);
void handle_pause_input(GameModel *model, char key);
void handle_input(GameModel *model, char key);
void init_terminal(void);
void restore_terminal(void);
int kbhit(void);
char getch(void);
#endif
2.model.c
#include "tetris.h"
// 方块形状定义(7种基本形状)
const int SHAPES[7][4][4] = {
// I形
{{0,0,0,0},
{1,1,1,1},
{0,0,0,0},
{0,0,0,0}},
// O形
{{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0}},
// T形
{{0,0,0,0},
{1,1,1,0},
{0,1,0,0},
{0,0,0,0}},
// S形
{{0,0,0,0},
{0,1,1,0},
{1,1,0,0},
{0,0,0,0}},
// Z形
{{0,0,0,0},
{1,1,0,0},
{0,1,1,0},
{0,0,0,0}},
// J形
{{0,0,0,0},
{1,1,1,0},
{0,0,1,0},
{0,0,0,0}},
// L形
{{0,0,0,0},
{1,1,1,0},
{1,0,0,0},
{0,0,0,0}}
};
// 初始化游戏
void init_game(GameModel *model) {
int i, j;
// 清空游戏区域
for (i = 0; i < GAME_HEIGHT; i++) {
for (j = 0; j < GAME_WIDTH; j++) {
model->board[i][j] = 0;
}
}
// 初始化游戏状态
model->score = 0;
model->level = 1;
model->lines_cleared = 0;
model->game_over = 0;
model->paused = 0;
model->gravity_counter = 0;
model->gravity_delay = model->speed.initial_delay;
// 初始化菜单状态
model->selected_option = 0;
model->menu_item_count = 3; // 菜单选项数量
// 生成第一个方块
srand(time(NULL));
// 当前方块
model->current.type = rand() % 7;
model->current.rotation = 0;
model->current.x = GAME_WIDTH / 2 - 2;
model->current.y = 0;
memcpy(model->current.shape, SHAPES[model->current.type], sizeof(model->current.shape));
// 下一个方块
model->next.type = rand() % 7;
model->next.rotation = 0;
model->next.x = 0;
model->next.y = 0;
memcpy(model->next.shape, SHAPES[model->next.type], sizeof(model->next.shape));
}
// 检查方块是否能移动到指定位置
int can_move(const GameModel *model, int dx, int dy) {
Tetromino temp = model->current;
temp.x += dx;
temp.y += dy;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (temp.shape[i][j]) {
int new_x = temp.x + j;
int new_y = temp.y + i;
// 检查边界
if (new_x < 0 || new_x >= GAME_WIDTH || new_y >= GAME_HEIGHT) {
return 0;
}
// 检查是否与已有方块重叠
if (new_y >= 0 && model->board[new_y][new_x]) {
return 0;
}
}
}
}
return 1;
}
// 移动当前方块
void move_current(GameModel *model, int dx, int dy) {
if (can_move(model, dx, dy)) {
model->current.x += dx;
model->current.y += dy;
}
}
// 检查方块是否能旋转
int can_rotate(const GameModel *model) {
// 计算旋转后的形状
int rotated[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
rotated[j][3-i] = model->current.shape[i][j];
}
}
// 检查旋转后的位置是否合法
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (rotated[i][j]) {
int new_x = model->current.x + j;
int new_y = model->current.y + i;
if (new_x < 0 || new_x >= GAME_WIDTH ||
new_y >= GAME_HEIGHT ||
(new_y >= 0 && model->board[new_y][new_x])) {
return 0;
}
}
}
}
return 1;
}
// 旋转当前方块
void rotate_current(GameModel *model) {
if (can_rotate(model)) {
int rotated[4][4];
// 创建旋转后的形状
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
rotated[j][3-i] = model->current.shape[i][j];
}
}
// 更新当前形状
memcpy(model->current.shape, rotated, sizeof(rotated));
}
}
// 锁定当前方块到游戏区域
void lock_piece(GameModel *model) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (model->current.shape[i][j]) {
int board_x = model->current.x + j;
int board_y = model->current.y + i;
if (board_y >= 0) {
model->board[board_y][board_x] = 1;
}
}
}
}
}
// 检查是否有完整的行
int check_lines(GameModel *model) {
int lines_to_clear[GAME_HEIGHT];
int line_count = 0;
for (int y = GAME_HEIGHT - 1; y >= 0; y--) {
int full = 1;
for (int x = 0; x < GAME_WIDTH; x++) {
if (!model->board[y][x]) {
full = 0;
break;
}
}
if (full) {
lines_to_clear[line_count++] = y;
}
}
if (line_count > 0) {
clear_lines(model, lines_to_clear, line_count);
}
return line_count;
}
// 清除完整的行
void clear_lines(GameModel *model, int lines[], int count) {
// 根据消除的行数计算分数
int points[] = {0, 100, 300, 500, 800};
if (count > 0 && count <= 4) {
model->score += points[count] * model->level;
model->lines_cleared += count;
model->level = model->lines_cleared / 10 + 1;
model->gravity_delay = model->speed.initial_delay - model->level;
if (model->gravity_delay < 2) model->gravity_delay = 2;
}
// 从下往上处理,避免索引问题
for (int i = count - 1; i >= 0; i--) {
int line = lines[i];
// 将上面的行向下移动
for (int y = line; y > 0; y--) {
for (int x = 0; x < GAME_WIDTH; x++) {
model->board[y][x] = model->board[y-1][x];
}
}
// 清空最上面一行
for (int x = 0; x < GAME_WIDTH; x++) {
model->board[0][x] = 0;
}
}
}
// 生成新方块
void generate_new_piece(GameModel *model) {
// 当前方块变为下一个方块
model->current = model->next;
model->current.x = GAME_WIDTH / 2 - 2;
model->current.y = 0;
// 生成新的下一个方块
model->next.type = rand() % 7;
model->next.rotation = 0;
memcpy(model->next.shape, SHAPES[model->next.type], sizeof(model->next.shape));
// 检查游戏是否结束
if (!can_move(model, 0, 0)) {
model->game_over = 1;
}
}
3.view.h
#include "tetris.h"
// 显示游戏界面(区域1、2、3)
void render_game(const GameModel *model) {
int i, j;
// 清屏并隐藏光标
printf(CLEAR_SCREEN HIDE_CURSOR);
// 如果游戏状态是菜单,显示菜单
if (model->state == STATE_MENU) {
printf(MENU_COLOR "\n\n");
printf(" ********************************\n");
printf(" * *\n");
printf(" * 俄罗斯方块 *\n");
printf(" * *\n");
printf(" ********************************\n");
printf(" * *\n");
printf(" * %s【1】 开始游戏 *\n",
(model->selected_option == 0) ? SELECTED_COLOR : "");
printf(RESET_COLOR MENU_COLOR);
printf(" * %s【2】 功能设置 *\n",
(model->selected_option == 1) ? SELECTED_COLOR : "");
printf(RESET_COLOR MENU_COLOR);
printf(" * %s【0】 退出游戏 *\n",
(model->selected_option == 2) ? SELECTED_COLOR : "");
printf(RESET_COLOR MENU_COLOR);
printf(" * *\n");
printf(" ********************************\n");
printf(RESET_COLOR "\n");
printf(" 使用方向键选择,回车确认\n");
return;
}
// 如果是设置菜单
if (model->state == STATE_SETTINGS) {
printf(MENU_COLOR "\n\n");
printf(" ********************************\n");
printf(" * 功能设置 *\n");
printf(" ********************************\n");
printf(" * *\n");
printf(" * %s下落速度: %-3d *\n",
(model->selected_option == 0) ? SELECTED_COLOR : "",
model->speed.initial_delay);
printf(RESET_COLOR MENU_COLOR);
printf(" * %s【S】 保存设置 *\n",
(model->selected_option == 1) ? SELECTED_COLOR : "");
printf(RESET_COLOR MENU_COLOR);
printf(" * %s【R】 返回菜单 *\n",
(model->selected_option == 2) ? SELECTED_COLOR : "");
printf(RESET_COLOR MENU_COLOR);
printf(" * *\n");
printf(" ********************************\n");
printf(RESET_COLOR "\n");
printf(" 使用↑↓键选择,←→键调整速度,回车确认\n");
printf(" 速度范围: %d-%d (数字越大速度越慢)\n",
model->speed.min_delay, model->speed.max_delay);
return;
}
// 正常游戏界面
// ===== 绘制游戏边框 =====
printf(BORDER_COLOR);
printf(TOP_LEFT_CORNER);
for (i = 0; i < GAME_WIDTH; i++) printf(HORIZONTAL_LINE HORIZONTAL_LINE);
printf(TOP_RIGHT_CORNER);
// 绘制信息区域的顶边框
printf(TOP_LEFT_CORNER);
for (i = 0; i < INFO_WIDTH; i++) printf(HORIZONTAL_LINE);
printf(TOP_RIGHT_CORNER RESET_COLOR "\n");
for (i = 0; i < GAME_HEIGHT; i++) {
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
for (j = 0; j < GAME_WIDTH; j++) {
// 绘制当前方块
int in_current = 0;
for (int bi = 0; bi < 4 && !in_current; bi++) {
for (int bj = 0; bj < 4 && !in_current; bj++) {
if (model->current.shape[bi][bj] &&
model->current.x + bj == j &&
model->current.y + bi == i) {
printf(BLOCK_COLOR " " RESET_COLOR);
in_current = 1;
}
}
}
// 绘制已落下的方块或空格
if (!in_current) {
if (model->board[i][j]) {
printf(BLOCK_COLOR " " RESET_COLOR);
} else {
printf(" ");
}
}
}
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
// ===== 区域2和3:信息显示区 =====
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
if (i == 0) {
printf(TEXT_COLOR " 下一个方块:");
for (j = 0; j < INFO_WIDTH - 12; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i >= 1 && i <= 5) {
printf(" ");
for (j = 0; j < 4; j++) {
if (model->next.shape[i-1][j]) {
printf(NEXT_COLOR " " RESET_COLOR);
} else {
printf(" ");
}
}
for (j = 0; j < INFO_WIDTH - 9; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 7) {
printf(TEXT_COLOR " 分数: " SCORE_COLOR "%6d", model->score);
for (j = 0; j < INFO_WIDTH - 13; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 8) {
printf(TEXT_COLOR " 等级: " SCORE_COLOR "%6d", model->level);
for (j = 0; j < INFO_WIDTH - 13; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 9) {
printf(TEXT_COLOR " 行数: " SCORE_COLOR "%6d", model->lines_cleared);
for (j = 0; j < INFO_WIDTH - 13; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 11) {
printf(TEXT_COLOR " 下落速度:" SCORE_COLOR "%3d", model->speed.initial_delay);
for (j = 0; j < INFO_WIDTH - 13; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 13) {
printf(TEXT_COLOR " 操作说明:");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 14) {
printf(TEXT_COLOR " A键: 左移");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 15) {
printf(TEXT_COLOR " D键: 右移");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 16) {
printf(TEXT_COLOR " W键: 旋转");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 17) {
printf(TEXT_COLOR " S键: 加速");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 18) {
printf(TEXT_COLOR " P键: 暂停");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else if (i == 19) {
printf(TEXT_COLOR " Q键: 退出");
for (j = 0; j < INFO_WIDTH - 10; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
} else {
for (j = 0; j < INFO_WIDTH; j++) printf(" ");
printf(BORDER_COLOR VERTICAL_LINE RESET_COLOR);
}
printf("\n");
}
// 底部边框
printf(BORDER_COLOR BOTTOM_LEFT_CORNER);
for (i = 0; i < GAME_WIDTH; i++) printf(HORIZONTAL_LINE HORIZONTAL_LINE);
printf(BOTTOM_RIGHT_CORNER);
printf(BOTTOM_LEFT_CORNER);
for (i = 0; i < INFO_WIDTH; i++) printf(HORIZONTAL_LINE);
printf(BOTTOM_RIGHT_CORNER RESET_COLOR "\n");
// 游戏状态提示
if (model->game_over) {
printf("\n" TEXT_COLOR " 游戏结束!按R重新开始,按Q退出" RESET_COLOR);
} else if (model->paused) {
printf("\n" TEXT_COLOR " 游戏暂停,按P继续" RESET_COLOR);
}
fflush(stdout);
}
4.controller.h
#include "tetris.h"
// 终端设置变量
static struct termios old_term, new_term;
// 初始化终端为非阻塞模式
void init_terminal(void) {
tcgetattr(STDIN_FILENO, &old_term);
new_term = old_term;
new_term.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_term);
}
// 恢复终端设置
void restore_terminal(void) {
tcsetattr(STDIN_FILENO, TCSANOW, &old_term);
}
// 检查是否有按键输入
int kbhit(void) {
struct timeval tv = {0L, 0L};
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
// 获取单个字符输入
char getch(void) {
char c;
if (read(STDIN_FILENO, &c, 1) != 1) {
return 0;
}
return c;
}
// 处理菜单输入
void handle_menu_input(GameModel *model, char key) {
switch (key) {
// 方向键上
case 'w':
case 'W':
model->selected_option = (model->selected_option - 1 + model->menu_item_count) % model->menu_item_count;
break;
// 方向键下
case 's':
case 'S':
model->selected_option = (model->selected_option + 1) % model->menu_item_count;
break;
// 回车键
case '\n':
case '\r':
case ' ':
switch (model->selected_option) {
case 0: // 开始游戏
model->state = STATE_PLAYING;
init_game(model);
break;
case 1: // 功能设置
model->state = STATE_SETTINGS;
model->selected_option = 0;
model->menu_item_count = 3; // 设置菜单选项数量
break;
case 2: // 退出游戏
model->state = STATE_GAME_OVER;
break;
}
break;
// 直接按键
case '1':
model->state = STATE_PLAYING;
init_game(model);
break;
case '2':
model->state = STATE_SETTINGS;
model->selected_option = 0;
model->menu_item_count = 3;
break;
case '0':
model->state = STATE_GAME_OVER;
break;
}
}
// 处理设置菜单输入
void handle_settings_input(GameModel *model, char key) {
// 处理特殊按键序列(箭头键)
static int esc_seq = 0;
if (key == 27) { // ESC字符
esc_seq = 1;
return;
}
if (esc_seq) {
if (key == '[') { // CSI序列
char next = getch();
switch (next) {
case 'A': // 上箭头
model->selected_option = (model->selected_option - 1 + model->menu_item_count) % model->menu_item_count;
break;
case 'B': // 下箭头
model->selected_option = (model->selected_option + 1) % model->menu_item_count;
break;
case 'D': // 左箭头
if (model->selected_option == 0 && model->speed.initial_delay > model->speed.min_delay) {
model->speed.initial_delay--;
}
break;
case 'C': // 右箭头
if (model->selected_option == 0 && model->speed.initial_delay < model->speed.max_delay) {
model->speed.initial_delay++;
}
break;
}
}
esc_seq = 0;
return;
}
// 处理普通按键
switch (key) {
// 上移
case 'w':
case 'W':
model->selected_option = (model->selected_option - 1 + model->menu_item_count) % model->menu_item_count;
break;
// 下移
case 'x': // 改为x,避免与s冲突
case 'X':
model->selected_option = (model->selected_option + 1) % model->menu_item_count;
break;
// 左移/减少速度
case 'a':
case 'A':
if (model->selected_option == 0 && model->speed.initial_delay > model->speed.min_delay) {
model->speed.initial_delay--;
}
break;
// 右移/增加速度
case 'd':
case 'D':
if (model->selected_option == 0 && model->speed.initial_delay < model->speed.max_delay) {
model->speed.initial_delay++;
}
break;
// 确认选择
case '\n':
case '\r':
case ' ':
switch (model->selected_option) {
case 0: // 速度设置
break;
case 1: // 保存设置
model->state = STATE_MENU;
model->selected_option = 0;
model->menu_item_count = 3;
break;
case 2: // 返回菜单
model->state = STATE_MENU;
model->selected_option = 0;
model->menu_item_count = 3;
break;
}
break;
// 快捷键
case 's':
case 'S':
// 保存设置
model->state = STATE_MENU;
model->selected_option = 0;
model->menu_item_count = 3;
break;
case 'r':
case 'R':
// 返回菜单
model->state = STATE_MENU;
model->selected_option = 0;
model->menu_item_count = 3;
break;
}
}
// 处理游戏输入
void handle_game_input(GameModel *model, char key) {
if (model->game_over) {
if (key == 'r' || key == 'R') {
model->state = STATE_MENU;
}
return;
}
if (model->paused && key != 'p' && key != 'P') {
return;
}
switch (key) {
case 'a':
case 'A':
move_current(model, -1, 0);
break;
case 'd':
case 'D':
move_current(model, 1, 0);
break;
case 's':
case 'S':
if (can_move(model, 0, 1)) {
model->current.y++;
model->score++; // 加速下落奖励
}
break;
case 'w':
case 'W':
rotate_current(model);
break;
case 'p':
case 'P':
model->paused = !model->paused;
if (model->paused) {
model->state = STATE_PAUSED;
} else {
model->state = STATE_PLAYING;
}
break;
case ' ':
// 快速下落
while (can_move(model, 0, 1)) {
model->current.y++;
model->score += 2;
}
break;
case 'q':
case 'Q':
model->state = STATE_MENU;
break;
}
}
// 处理暂停输入
void handle_pause_input(GameModel *model, char key) {
if (key == 'p' || key == 'P') {
model->paused = 0;
model->state = STATE_PLAYING;
} else if (key == 'q' || key == 'Q') {
model->state = STATE_MENU;
}
}
// 统一输入处理
void handle_input(GameModel *model, char key) {
switch (model->state) {
case STATE_MENU:
handle_menu_input(model, key);
break;
case STATE_SETTINGS:
handle_settings_input(model, key);
break;
case STATE_PLAYING:
handle_game_input(model, key);
break;
case STATE_PAUSED:
handle_pause_input(model, key);
break;
case STATE_GAME_OVER:
if (key == 'r' || key == 'R') {
model->state = STATE_MENU;
}
break;
}
}
5.main.c
#include "tetris.h"
int main(void) {
GameModel model;
// 初始化速度设置
model.speed.initial_delay = 20; // 默认速度
model.speed.min_delay = 5; // 最快速度
model.speed.max_delay = 40; // 最慢速度
// 初始化游戏状态
model.state = STATE_MENU;
model.selected_option = 0;
model.menu_item_count = 3;
// 初始化终端
init_terminal();
// 设置随机种子
srand(time(NULL));
// 游戏主循环
while (model.state != STATE_GAME_OVER) {
// 处理键盘输入
if (kbhit()) {
char key = getch();
if (key == 0) continue;
if (key == 'q' || key == 'Q') {
if (model.state == STATE_PLAYING || model.state == STATE_PAUSED) {
model.state = STATE_MENU;
} else {
break;
}
} else {
handle_input(&model, key);
}
}
// 如果不是暂停状态,处理下落逻辑
if (model.state == STATE_PLAYING && !model.paused && !model.game_over) {
model.gravity_counter++;
if (model.gravity_counter >= model.gravity_delay) {
// 尝试下落
if (can_move(&model, 0, 1)) {
model.current.y++;
} else {
// 锁定方块
lock_piece(&model);
// 检查并消除完整的行
int lines = check_lines(&model);
// 生成新方块
generate_new_piece(&model);
}
model.gravity_counter = 0;
}
}
// 渲染界面
render_game(&model);
// 控制帧率
usleep(50000); // 50ms
}
// 恢复终端设置
restore_terminal();
// 显示退出信息
printf(SHOW_CURSOR "\n游戏结束!\n");
return 0;
}
十一、总结
这个俄罗斯方块项目虽然规模不大,但涵盖了从底层算法到上层界面的完整开发流程。通过MVC架构的设计,代码结构清晰,易于维护和扩展。项目不仅实现了经典的游戏功能,还提供了友好的用户界面和灵活的配置选项。
项目特点总结:
-
代码结构清晰,符合MVC设计模式
-
编译配置简单,一键编译运行
-
内存管理严谨,无内存泄漏
-
界面美观,支持彩色显示
-
操作流畅,响应及时
这个项目可以作为学习C语言游戏开发的案例,无论是初学者还是有经验的开发者都能从中获益。
欢迎在评论区交流讨论,如果你觉得这篇文章有帮助,请点赞收藏支持!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)