目录

一、项目概述

1.1功能特性

1.2 项目结构

二、编译说明

1. 使用提供的Makefile编译

2. Makefile详解

三、核心模块设计

1. 数据模型(Model)

2. 视图渲染(View)

3. 控制器(Controller)

四、关键算法实现

1. 方块旋转算法

2. 碰撞检测

3. 行消除和计分

五、终端控制技术

1. VT100控制码

2. 非阻塞输入

六、调试技巧

1. 使用Valgrind进行内存检查

2. GDB调试

七、性能优化

八、扩展建议

九、学习收获

十、完整代码

1. tetris.h

2.model.c

3.view.h

4.controller.h

5.main.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

七、性能优化

  1. 帧率控制:通过usleep(50000)控制游戏刷新率为20FPS

  2. 局部刷新:只在必要时重绘屏幕区域

  3. 缓冲区管理:使用fflush(stdout)确保输出及时显示

八、扩展建议

  1. 添加高分榜:保存和显示历史最高分

  2. 增加音效:使用系统声音提示

  3. 网络对战:实现双人对战功能

  4. 更多方块:增加特殊方块类型

  5. 关卡设计:预设不同的初始棋盘布局

九、学习收获

通过这个项目,我掌握了:

  1. C语言高级特性:结构体、枚举、函数指针的使用

  2. 模块化编程:合理的文件分割和接口设计

  3. 终端编程:VT100控制码、非阻塞输入处理

  4. 游戏开发基础:游戏循环、状态机、碰撞检测

  5. 调试技巧: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语言游戏开发的案例,无论是初学者还是有经验的开发者都能从中获益。


欢迎在评论区交流讨论,如果你觉得这篇文章有帮助,请点赞收藏支持!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐