当前位置: 首页 > news >正文

俄罗斯方块终端游戏实现 —— C语言系统编程与终端控制

俄罗斯方块终端游戏实现 —— C语言系统编程与终端控制

本项目是一个使用 C语言 实现的简易 俄罗斯方块(Tetris)终端游戏,完整涵盖了 系统调用、终端控制、随机数、二维数组地图、碰撞检测、图形绘制、非阻塞输入、光标控制、清屏与重绘、方块旋转与锁定、行消除与计分机制 等多个核心知识点。


📌 项目依赖头文件与宏定义

#include <fcntl.h>   // 用于 fcntl() 函数,实现非阻塞输入检测
#include <stdio.h>   // 标准输入输出,printf, putchar 等
#include <stdlib.h>  // rand(), srand() 生成随机方块
#include <termios.h> // 终端属性控制,实现 getch() 与 kbhit()
#include <time.h>    // time() 为随机数播种
#include <unistd.h>  // usleep() 实现游戏帧率控制#define H 22  // 游戏地图高度(行数),含边界
#define W 12  // 游戏地图宽度(列数),含边界

✅ 理想结果:程序编译无警告,头文件正确包含,宏定义生效,地图尺寸为 22 行 × 12 列。


🧱 全局变量与方块结构体定义

int map[H][W];  // 二维地图数组,1=边界,2=固定方块,0=空白
int score = 0;  // 玩家得分,每消除一行 +100 分// 当前方块结构体:位置(x,y)、类型(0-6)、旋转状态(0-3)
struct Block
{int x;int y;int type;      // 0-6 对应 7 种方块类型int rotation;  // 0-3 对应 4 种旋转状态
};
struct Block currentBlock;  // 当前活动方块实例

✅ 理想结果:全局变量初始化成功,currentBlock 可在函数中被正确赋值和读取。


🔲 七种方块的 4×4×4 三维旋转矩阵定义

// 7种方块,每种4种旋转,每种旋转为4×4矩阵(实际使用中心2×4或3×3区域)
int BLOCKS[7][4][4][4] = {// I 方块 (长条形) —— 4种旋转状态相同或镜像{{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}},{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}}},// O 方块 (正方形) —— 4种旋转完全相同{{{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}}},// T 方块 (T 字形) —— 4种旋转状态{{{0, 0, 0, 0}, {0, 1, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}},// S 方块 (S 形) —— 2种独特旋转,重复两次{{{0, 0, 0, 0}, {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}},// Z 方块 (Z 形) —— 2种独特旋转,重复两次{{{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}},// L 方块 (L 形) —— 4种旋转状态{{{0, 0, 0, 0}, {1, 1, 1, 0}, {1, 0, 0, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}},{{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}},// J 方块 (J 形) —— 4种旋转状态{{{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}},{{0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},{{0, 0, 0, 0}, {0, 0, 1, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}},{{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}}};

✅ 理想结果:所有方块形状与旋转状态定义正确,游戏中可随机生成并正确旋转。


🗺️ 地图初始化函数 —— initMap()

void initMap()  // 初始化游戏地图,设置边界
{int x, y;for (y = 0; y < H; ++y){for (x = 0; x < W; ++x){if (x == 0 || x == W - 1 || y == H - 1)  // 左右边界和底边界{map[y][x] = 1;  // 边界用 1 标记,绘制为红色}else{map[y][x] = 0;  // 内部区域初始为空白}}}
}

✅ 理想结果:地图四周和底部显示红色边界,内部为空白,为游戏提供碰撞检测基础。


🎲 创建新方块 —— createNewBlock()

void createNewBlock()
{currentBlock.type = rand() % 7;      // 随机选择 0~6 类型currentBlock.rotation = rand() % 4;  // 随机选择 0~3 旋转状态// 新方块出生在顶部中央(x 偏移 -2 以居中 4×4 方块)currentBlock.x = W / 2 - 2;currentBlock.y = 0;
}

✅ 理想结果:每次调用生成一个随机类型和旋转的新方块,出现在地图顶部中央。


🚧 碰撞检测 —— checkCollision()

int checkCollision(int x, int y, int type, int rotation)
// 检测指定位置、类型、旋转的方块是否与边界或固定块碰撞
{int i, j;  // 遍历 4×4 方块矩阵for (i = 0; i < 4; ++i){for (j = 0; j < 4; ++j){if (BLOCKS[type][rotation][i][j] != 0)  // 如果该位置是方块实体{int nx = x + j;  // 计算在地图中的实际 x 坐标int ny = y + i;  // 计算在地图中的实际 y 坐标// 检测是否越界:左、右、下、上边界(上边界允许负值,但地图不绘制)if (nx < 0 || nx >= W || ny >= H || ny < 0){return 1;  // 发生碰撞}// 检测是否与已固定的方块(map[][] == 2)碰撞if (ny >= 0 && map[ny][nx] != 0)  // 注意:只检测地图内区域{return 1;}}}}return 0;  // 无碰撞
}

✅ 理想结果:方块移动或旋转前调用,若返回 1 则禁止操作,防止穿墙或重叠。


🔒 锁定方块 —— lockBlock()

void lockBlock()
// 将当前活动方块写入地图,变为固定块(值为 2)
{int i, j;for (i = 0; i < 4; ++i){for (j = 0; j < 4; ++j){if (BLOCKS[currentBlock.type][currentBlock.rotation][i][j] != 0){int nx = currentBlock.x + j;int ny = currentBlock.y + i;// 确保坐标在地图范围内再写入if (ny >= 0 && ny < H && nx >= 0 && nx < W){map[ny][nx] = 2;  // 固定方块标记为 2}}}}
}

✅ 理想结果:方块落地后变为地图的一部分,不可再移动,后续方块与其碰撞。


🧹 行消除与下移 —— checkLineClear()

void checkLineClear()
{int y = H - 2;  // 从倒数第二行开始检查(最后一行是边界)int lines_cleared = 0;while (y >= 0)  // 从底向上逐行检查{                   int is_full = 1;  // 假设当前行是满的// 检查游戏区域(x=1 到 W-2)是否全为固定块(值为2)for (int x = 1; x < W - 1; x++){if (map[y][x] == 0)  // 存在空白{is_full = 0;break;}}if (is_full){++lines_cleared;// 将当前行及以上所有行向下移动一行for (int y_tmp = y; y_tmp > 0; y_tmp--){for (int x = 0; x < W; x++){map[y_tmp][x] = map[y_tmp - 1][x];  // 下移}}// 清空最顶行for (int x = 0; x < W; x++){map[0][x] = 0;}// 注意:y 不自减,因为下移后原 y 行是新内容,需重新检查}else{--y;  // 当前行不满,检查上一行}}// 更新分数:每消除一行 +100 分score += lines_cleared * 100;
}

✅ 理想结果:满行被消除,上方行下移,顶部清空,分数按行数增加。


🖱️ 终端光标控制函数

void gotoxy(int x, int y)
// 使用 VT100 转义序列定位光标到指定位置(y行, x列)
{printf("\033[%d;%dH", y + 1, x * 2 + 1);  // 行号+1,列号*2+1(因每个方块占2字符宽)fflush(stdout);  // 强制刷新输出缓冲区
}void hideCursor()
// 隐藏终端光标,提升游戏沉浸感
{printf("\033[?25l");  // VT100 隐藏光标指令fflush(stdout);
}void showCursor()
// 游戏结束后显示光标
{printf("\033[?25h");  // VT100 显示光标指令fflush(stdout);
}

✅ 理想结果:光标可精确定位到指定位置绘制方块,游戏运行时光标隐藏,退出后恢复。


⌨️ 终端非阻塞输入函数 —— getch() 与 kbhit()

int getch(void)
// 从终端读取一个字符,不回显(非缓冲、非回显模式)
{struct termios oldt, newt;int ch;tcgetattr(STDIN_FILENO, &oldt);          // 获取当前终端属性newt = oldt;newt.c_lflag &= ~(ICANON | ECHO);        // 关闭缓冲与回显tcsetattr(STDIN_FILENO, TCSANOW, &newt); // 应用新设置ch = getchar();                          // 读取字符tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // 恢复原设置return ch;
}int kbhit(void)
// 检测是否有按键按下,非阻塞(立即返回)
{struct termios oldt, newt;int ch;int oldf;tcgetattr(STDIN_FILENO, &oldt);newt = oldt;newt.c_lflag &= ~(ICANON | ECHO);tcsetattr(STDIN_FILENO, TCSANOW, &newt);oldf = fcntl(STDIN_FILENO, F_GETFL, 0);fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); // 设置非阻塞读取ch = getchar();  // 尝试读取tcsetattr(STDIN_FILENO, TCSANOW, &oldt);fcntl(STDIN_FILENO, F_SETFL, oldf);  // 恢复原设置if (ch != EOF){ungetc(ch, stdin);  // 将字符放回输入流,供后续 getch() 使用return 1;           // 有按键}return 0;             // 无按键
}

✅ 理想结果:kbhit() 立即返回是否有按键,getch() 读取按键值,支持方向键(ESC [ D/C/A)。


🖼️ 地图绘制函数 —— drawMap()

void drawMap()
{printf("\033[2J");  // VT100 清屏指令gotoxy(0, 0);       // 光标归位到左上角int y = 0, x = 0;for (y = 0; y < H; ++y){for (x = 0; x < W; ++x){// 如果当前格子属于活动方块范围if (x >= currentBlock.x && x < currentBlock.x + 4 && y >= currentBlock.y && y < currentBlock.y + 4){int rel_x = x - currentBlock.x;int rel_y = y - currentBlock.y;// 如果方块矩阵中该位置为实体,则绘制活动方块if (BLOCKS[currentBlock.type][currentBlock.rotation][rel_y][rel_x]){printf("[]");continue;}}// 绘制地图内容:边界(红色)、固定块(白色)、空白(空格)if (map[y][x] == 1){printf("\033[1;31m[]\033[0m");  // 红色边界}else if (map[y][x] == 2){printf("[]");  // 固定方块}else{printf("  ");  // 空白}}putchar('\n');  // 换行}// 在右侧显示当前分数gotoxy(W + 1, 5);printf("Score: %d", score);fflush(stdout);  // 强制输出
}

✅ 理想结果:每帧清屏重绘,活动方块叠加在地图上,边界红色,分数实时显示。


🎮 主游戏循环 —— main()

int main(void)
{srand(time(NULL));  // 用当前时间初始化随机数种子initMap();          // 初始化带边界的地图createNewBlock();   // 创建第一个活动方块hideCursor();       // 隐藏终端光标while (1)  // 无限游戏循环{if (kbhit())  // 检测是否有按键{char key = getch();  // 读取按键if (key == 27)       // 如果是 ESC 键(方向键序列开头){getch();         // 读取 '['key = getch();   // 读取方向字符if (key == 'D')  // 左方向键{// 检测左移是否碰撞,无碰撞则移动if (checkCollision(currentBlock.x - 1, currentBlock.y, currentBlock.type, currentBlock.rotation) == 0){--currentBlock.x;}}else if (key == 'C')  // 右方向键{if (checkCollision(currentBlock.x + 1, currentBlock.y, currentBlock.type, currentBlock.rotation) == 0){++currentBlock.x;}}else if (key == 'A')  // 上方向键(旋转){int new_rotation = (currentBlock.rotation + 1) % 4;  // 循环旋转if (checkCollision(currentBlock.x, currentBlock.y, currentBlock.type, new_rotation) == 0){currentBlock.rotation = new_rotation;}}}}// 尝试下落一格if (checkCollision(currentBlock.x, currentBlock.y + 1, currentBlock.type, currentBlock.rotation) == 0){++currentBlock.y;  // 无碰撞,下落}else  // 下落发生碰撞{lockBlock();       // 锁定当前方块到地图checkLineClear();  // 检查并消除满行createNewBlock();  // 生成新方块}drawMap();       // 绘制当前帧usleep(500000);  // 延迟 0.5 秒(500毫秒),控制下落速度}showCursor();  // 理论上不会执行(因 while(1)),但保留用于规范return 0;
}

✅ 理想结果:游戏持续运行,方块自动下落,支持左右移动和旋转,落地后锁定,消除行并计分,新方块生成,帧率稳定。


🧩 知识点归纳

类别 知识点 对应函数/代码段
系统编程 终端属性控制、非阻塞输入 getch(), kbhit(), termios.h, fcntl.h
图形界面 VT100 转义序列、光标定位、清屏、颜色 gotoxy(), hideCursor(), \033[2J, \033[1;31m
游戏逻辑 碰撞检测、方块锁定、行消除、计分 checkCollision(), lockBlock(), checkLineClear()
数据结构 三维数组存储方块形态、二维地图 BLOCKS[7][4][4][4], map[H][W]
控制流程 主循环、条件移动、状态更新 main() 中 while 循环与条件分支
随机机制 随机生成方块类型与旋转 srand(), rand() % 7, rand() % 4
时间控制 控制帧率与下落速度 usleep(500000)


🏁 运行环境与理想结果

  • 操作系统:Linux / macOS(支持 VT100 终端)
  • 编译命令:gcc -o tetris main.c
  • 运行命令:./tetris
  • 控制键:
    • ←:左移(ESC [ D)
    • →:右移(ESC [ C)
    • ↑:旋转(ESC [ A)
    • ESC:退出(未实现,需 Ctrl+C 强制终止)
  • 视觉效果:
    • 红色边界框
    • 白色活动与固定方块 “[]”
    • 实时分数显示
    • 方块自动下落,支持操作
    • 消行后上方方块下移,分数增加

✅ 最终理想结果:游戏流畅运行,玩家可通过方向键控制方块,消除行得分,无崩溃或逻辑错误。


文章转载自:

http://Cwv1jMLQ.sjksk.cn
http://3RoAj3XI.sjksk.cn
http://CJkExCJw.sjksk.cn
http://rRQZ3C9J.sjksk.cn
http://7UYuwWL0.sjksk.cn
http://kBvpEChA.sjksk.cn
http://N2luVTOU.sjksk.cn
http://iDUI8o75.sjksk.cn
http://O0zGNSLQ.sjksk.cn
http://QFmY5etj.sjksk.cn
http://BZwmHEWQ.sjksk.cn
http://0XFkX96u.sjksk.cn
http://vdmVt5FL.sjksk.cn
http://eqGWwDsB.sjksk.cn
http://gbwkqzi5.sjksk.cn
http://vjDoHLHr.sjksk.cn
http://a3VrY44o.sjksk.cn
http://ajGTuQgV.sjksk.cn
http://l5kkt4h0.sjksk.cn
http://CeRzH9KK.sjksk.cn
http://oBQRH8of.sjksk.cn
http://kuCcjq5m.sjksk.cn
http://qQRF8N7E.sjksk.cn
http://vRsUwPr8.sjksk.cn
http://eyqcRJGX.sjksk.cn
http://8Z8PgaDb.sjksk.cn
http://yAXgH8fl.sjksk.cn
http://WjtV7sRE.sjksk.cn
http://pr91quz9.sjksk.cn
http://M77Hg77G.sjksk.cn
http://www.dtcms.com/a/380368.html

相关文章:

  • 跨屏互联KuapingCMS建站系统发布更新 增加seo自动外链功能
  • 域账号提权后权限管理问题解析
  • Go语言设计模式(七)组合模式
  • 解密完全二叉树顺序存储之堆结构
  • 【Vue2 ✨】Vue2 入门之旅 · 进阶篇(三):模板编译原理
  • 嵌入式仿真技术在教学中的应用与挑战探析
  • Kaggle项目实践——Titanic: Machine Learning from Disaster
  • 【leetcode】127. 三角形的最小路径和
  • Java设计模式中的几种常用设计模式
  • 【Vue2 ✨】Vue2 入门之旅 · 进阶篇(四):异步更新与 nextTick 原理
  • 【小程序】微信小程序九宫格抽奖动画(完整版)
  • [BJDCTF 2020]encode
  • 系统核心解析:深入操作系统内部机制——进程管理与控制指南(一)【进程/PCB】
  • 速通ACM省铜第二天 赋源码(Adjacent XOR和Arboris Contractio)
  • Python快速入门专业版(二十四):while循环:条件循环与“死循环”避免(猜数字游戏案例)
  • 神经网络构成框架-理论学习
  • 智能眼镜产品成熟度分析框架与评估
  • 从零学算法2327
  • 【C++】:list容器全面解析
  • 渲染农场多少钱一小时
  • IDEA试用过期,无法登录,重置方法
  • IP验证学习之case编写
  • 通过Dockerfile构建Docker镜像并训练模型
  • 操作系统内核架构深度解析:从微内核到宏内核的设计哲学与性能权衡
  • IIS运行账户设置记录
  • 服务管理 systemctl
  • HTTP与HTTPS
  • devextreme-vue表格设置可复制粘贴
  • Go 语言 PDF 生成库综合比较与实践指南
  • 图技术重塑金融未来:悦数图数据库如何驱动行业创新与风控变革