用c语言完成俄罗斯方块小游戏
用c语言完成俄罗斯方块小游戏
这估计是你在编程学习过程中的第一个小游戏开发,怎么说呢,在这里只针对刚学程序设计的学生,就是说刚接触C语言没多久,有一点功底的学生看看,简陋的代码,简陋的实现,
by:BoBoo文睡不醒
注:该代码内注释加入个人理解,若有错误,多多包涵
由于不需要插件,绘制出来的玩意恐怕不够美观,多多理解
语言:C的不能再C。(没涉及啥很高深的玩意)
(高级一点的话用EasyX插件里面的玩意,那就很炫酷了)
涉及内容:光标移动,颜色,图形绘制、消去、覆盖和打印,键盘按键敲击的读取,计时器,文件的创建与读取
看似简单,实际上一点也不难
1. 游戏概述
俄罗斯方块是一款经典的益智游戏,主要功能包括:
- 显示游戏界面
- 随机生成方块并控制其移动和旋转
- 消除完整行
- 记录并保存最高分
- 游戏结束判断
本代码使用控制台操作,并通过系统函数控制光标位置、打印字符实现图形效果。
2. 游戏开发所需知识
在实现类似游戏之前,需掌握以下知识:
- 控制台操作:控制光标位置 (
CursorJump
) 和隐藏光标 (HideCursor
)。 - 颜色设置:通过
SetConsoleTextAttribute
改变文本颜色。 - 二维数组操作:表示游戏区域与方块信息。
- 随机数生成:使用
rand()
生成随机方块。 - 键盘事件:读取按键事件(如
_kbhit
和_getch
)。 - 文件操作:保存和读取游戏历史最高分。
3. 代码解读
3.1 数据结构
Face
- 表示整个游戏界面。
data
: 标记某处是否有方块(0无,1有)。color
: 存储方块颜色信息。
Block
- 存储7种方块的4种形态。
- 通过二维数组
space
表示方块形状。
c复制代码struct Face {int data[ROW][COL + 10];int color[ROW][COL + 10];
};struct Block {int space[4][4];
} block[7][4];
3.2 核心模块
(1) 界面初始化
使用InitInterface
绘制边框与提示信息:
- 通过
CursorJump
设置光标位置。 - 使用
printf("■")
绘制边框。 - 边框信息存入
face.data
。
c复制代码void InitInterface() {color(7);for (int i = 0; i < ROW; i++) {for (int j = 0; j < COL + 10; j++) {if (j == 0 || j == COL - 1 || j == COL + 9) { face.data[i][j] = 1; CursorJump(2 * j, i);printf("■");}}}
}
(2) 方块初始化
InitBlockInfo
存储方块的7种形状和4种旋转状态:
- 每种形状的基本状态由
block[shape][0]
定义。 - 使用二维旋转公式生成其余三种形态。
c复制代码for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {block[shape][form + 1].space[i][j] = temp[3 - j][i];}
}
(3) 方块绘制与移动
DrawBlock
:在指定位置打印方块。DrawSpace
:用空格覆盖指定位置,实现“擦除”效果。
c复制代码void DrawBlock(int shape, int form, int x, int y) {for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (block[shape][form].space[i][j] == 1 && y + i > 0) {CursorJump(2 * (x + j), y + i);printf("■");}}}
}
(4) 键盘事件处理
通过_getch
检测按键:
LEFT
:左移RIGHT
:右移DOWN
:快速下落SPACE
:旋转
c复制代码switch (ch) {case LEFT:if (IsLegal(shape, form, x - 1, y)) x--;break;case RIGHT:if (IsLegal(shape, form, x + 1, y)) x++;break;case SPACE:if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4;break;
}
(5) 行清除与得分判断
JudeFunc
实现:
- 检查行是否满。
- 若满,则消除行并将上方行下移。
- 更新得分。
c复制代码if (sum == COL - 2) { grade += 10;for (int j = 1; j < COL - 1; j++) {face.data[i][j] = 0;CursorJump(2 * j, i);printf(" ");}
}
(6) 游戏逻辑与结束判断
StartGame
控制方块下落、键盘响应与结束判断:
- 随机生成方块并控制其运动。
- 检测顶层是否有方块,若有则结束游戏。
(7) 文件操作
ReadGrade
与WriteGrade
负责读取和保存最高分:
- 通过
fopen
打开文件,保存最高分。 - 使用二进制方式存储成绩,防止文本存储带来的额外处理。
c复制代码FILE* pf = fopen("record.txt", "r");
fread(&max, sizeof(int), 1, pf);
4. 编写俄罗斯方块的步骤
- 明确需求:
- 游戏逻辑(下落、移动、旋转、消除)。
- 数据存储(方块形态、游戏区域)。
- 图形展示(控制台或GUI)。
- 搭建基础框架:
- 初始化游戏区域、边框与方块。
- 定义随机生成与旋转逻辑。
- 逐步完善:
- 实现消除与得分逻辑。
- 增加键盘事件处理。
- 添加文件操作功能。
- 优化与美化:
- 增加动画效果。
- 提高界面美观度(如使用插件或图形库)。
通过本代码,你可以学习到俄罗斯方块的实现核心逻辑,同时可以在此基础上尝试更高级的图形化实现,如使用EasyX插件或其他图形库,进一步提升游戏的视觉体验和功能丰富性。
您说:
对每一步进行详细的介绍
ChatGPT 说:
ChatGPT
编写俄罗斯方块游戏的详细步骤与说明
本文将从代码结构与逻辑出发,逐步拆解如何实现一个俄罗斯方块游戏,帮助理解并指导如何从零开始构建类似的游戏。
1. 明确需求与设计思路
俄罗斯方块的基本玩法包括以下几部分:
- 随机生成方块:从7种形状中随机生成一个方块,并在游戏区域顶部下落。
- 方块运动控制:通过键盘实现左右移动、加速下落和旋转操作。
- 碰撞检测与消行:检测方块是否碰到其他方块或到达底部,并判断是否需要消行。
- 得分与游戏结束:计算得分,判断是否达到游戏结束条件。
- 数据持久化:保存和读取最高分记录。
设计中还需考虑:
- 游戏的界面绘制。
- 使用二维数组存储游戏区域状态。
- 持续的方块生成与处理逻辑。
2. 搭建基础框架
2.1 确定游戏区域和方块结构
俄罗斯方块的核心在于使用二维数组存储数据状态:
-
Face
结构体:存储整个游戏区域状态。
data
数组:记录每个位置是否有方块。color
数组:记录每个位置的颜色。
-
Block
结构体:存储所有方块的形状及其4种旋转状态。
c复制代码#define ROW 29 // 游戏区域行数
#define COL 20 // 游戏区域列数struct Face {int data[ROW][COL + 10]; // 游戏区域方块标记int color[ROW][COL + 10]; // 方块颜色
};struct Block {int space[4][4]; // 存储方块形状
} block[7][4]; // 7种形状,每种4种状态
2.2 初始化游戏区域与边框
绘制游戏界面的边框,展示基本信息(如得分、提示):
- 利用双重循环,遍历所有格子。
- 使用特定符号(如
■
)绘制边框。 - 在右侧区域打印提示文字。
代码实现:
c复制代码void InitInterface() {color(7); // 设置边框颜色为白色for (int i = 0; i < ROW; i++) {for (int j = 0; j < COL + 10; j++) {if (j == 0 || j == COL - 1 || j == COL + 9) {face.data[i][j] = 1; // 边框标记为有方块CursorJump(2 * j, i);printf("■");} else if (i == 0 || i == ROW - 1) { face.data[i][j] = 1; // 顶部和底部边框printf(" ■");} else {face.data[i][j] = 0; // 非边框区域标记为空}}}// 打印右侧提示信息CursorJump(2 * COL, 1);printf("下一个方块:");CursorJump(2 * COL + 4, ROW - 3);printf("当前分数:%d", grade);
}
3. 初始化方块形状与逻辑
3.1 定义方块形状
俄罗斯方块共有7种形状,每种形状有4种旋转状态。通过二维数组定义每种形状的初始状态,并通过旋转公式生成其他状态。
代码实现:
c复制代码void InitBlockInfo() {// 定义“T”形for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1;block[0][0].space[2][1] = 1;// 定义其他形状...// 通过旋转公式生成每种形状的其他3种状态for (int shape = 0; shape < 7; shape++) {for (int form = 0; form < 3; form++) {for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {block[shape][form + 1].space[i][j] = block[shape][form].space[3 - j][i];}}}}
}
4. 方块的运动控制
4.1 键盘事件检测
通过_kbhit
检测键盘输入,并用_getch
捕获按键值:
LEFT
:左移。RIGHT
:右移。DOWN
:加速下落。SPACE
:旋转。
实现示例:
c复制代码char ch = getch();
switch (ch) {case LEFT:if (IsLegal(shape, form, x - 1, y)) x--;break;case RIGHT:if (IsLegal(shape, form, x + 1, y)) x++;break;case SPACE:if (IsLegal(shape, (form + 1) % 4, x, y)) form = (form + 1) % 4;break;case ESC:exit(0); // 退出游戏
}
4.2 碰撞检测
使用IsLegal
函数判断方块是否可以移动或旋转:
- 检查方块每个格子是否会越界或与已有方块重叠。
- 返回
1
表示合法,0
表示不合法。
c复制代码int IsLegal(int shape, int form, int x, int y) {for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (block[shape][form].space[i][j] == 1 &&(x + j < 0 || x + j >= COL || y + i >= ROW || face.data[y + i][x + j] == 1)) {return 0; // 非法}}}return 1; // 合法
}
5. 方块落地与消行
5.1 方块落地
每次方块下落时:
- 检查其下一步是否越界或碰到其他方块。
- 如果不能继续下落,则将当前方块写入
Face
结构。
5.2 行满判断与消除
当某一行填满时:
- 删除该行并将上方所有行下移。
- 更新分数。
代码实现:
c复制代码int JudeFunc() {for (int i = ROW - 2; i > 4; i--) {int sum = 0;for (int j = 1; j < COL - 1; j++) sum += face.data[i][j];if (sum == COL - 2) { // 满行grade += 10;for (int j = 1; j < COL - 1; j++) {face.data[i][j] = 0;CursorJump(2 * j, i);printf(" ");}for (int m = i; m > 1; m--) { // 上方行下移for (int n = 1; n < COL - 1; n++) {face.data[m][n] = face.data[m - 1][n];face.color[m][n] = face.color[m - 1][n];}}}}return 0;
}
6. 游戏结束与得分记录
6.1 游戏结束判断
若方块到达顶层,则游戏结束,提示玩家是否重开一局。
6.2 保存与读取最高分
通过文件操作保存最高分:
- 使用
fopen
以只读或写模式打开文件。 - 利用
fwrite
保存分数。
代码实现:
c复制代码void ReadGrade() {FILE* pf = fopen("record.txt", "r");if (pf == NULL) {pf = fopen("record.txt", "w");fwrite(&grade, sizeof(int), 1, pf);}fread(&max, sizeof(int), 1, pf);fclose(pf);
}
7. 整合与优化
- 游戏主循环: 控制方块生成、运动、碰撞和消除,维护游戏逻辑流。
- 视觉优化: 增加更多颜色和图案设计,使游戏界面更美观。
以上是详细的游戏开发步骤,你可以在此基础上进行个性化扩展,比如增加背景音乐、提升图形效果,或者引入难度等级功能。
c(下面的代码经过了详细的注释,希望大家慢慢研究)
//作品:tetris(带注释)
// by:BoBoo文睡不醒
//注:该代码内注释加入个人理解,若有错误,多多包涵
//由于不需要插件,绘制出来的玩意恐怕不够美观,多多理解
//语言:C的不能再C。(没涉及啥很高深的玩意)(高级一点的话用EasyX插件里面的玩意,那就很炫酷了)
//涉及内容:光标移动,颜色,图形绘制、消去、覆盖和打印,键盘按键敲击的读取,计时器,文件的创建与读取
//看似简单,实际上一点也不难//头文件部分:
#include <stdio.h>
#include <Windows.h>
//提供了一些在控制台程序中进行输入输出、控制光标位置、设置文本属性等方面的函数和常量
//在代码中使用了其中的一些函数比如SetConsoleCursorPosition()、SetConsoleTextAttribute()来控制光标位置和设置文本颜色
#include <stdlib.h>
//这个头文件包含了一些常用的函数和类型定义,用于进行内存分配、进程控制、字符串转换等操作
#include <time.h>
//这个头文件包含了一些与时间相关的函数和类型定义
#include <conio.h>
//于检测键盘输入字符,以及在控制台上进行一些简单的控制,比如隐藏光标等
//在代码中用于于检测键盘输入,比如判断是否按下了特定的方向键或者空格键,从而控制俄罗斯方块的移动、旋转等操作。//宏定义
#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数
//接下来说明敲击键盘该按键的值
#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右
#define SPACE 32 //空格键
#define ESC 27 //Esc键//两个结构体
struct Face
{int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无)int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码
}face;struct Block
{int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种//函数部分?函数。
//隐藏光标
void HideCursor();
//光标跳转
void CursorJump(int x, int y);
//初始化界面
void InitInterface();
//初始化方块信息
void InitBlockInfo();
//颜色设置
void color(int num);
//画出方块
void DrawBlock(int shape, int form, int x, int y);
//空格覆盖
void DrawSpace(int shape, int form, int x, int y);
//合法性判断
int IsLegal(int shape, int form, int x, int y);
//判断得分与结束
int JudeFunc();
//游戏主体逻辑函数
void StartGame();
//从文件读取最高分
void ReadGrade();
//更新最高分到文件
void WriteGrade();//全局变量
int max, grade;//主函数
int main()
{
#pragma warning (disable:4996) //消除警告max = 0, grade = 0; //初始化变量system("title 俄罗斯方块"); //设置cmd窗口的名字system("mode con lines=29 cols=60"); //设置cmd窗口的大小HideCursor(); //隐藏光标ReadGrade(); //从文件读取最高分到max变量 InitInterface(); //初始化界面InitBlockInfo(); //初始化方块信息srand((unsigned int)time(NULL)); //设置随机数生成的起点StartGame(); //开始游戏return 0;
}//关于隐藏光标的操作(不懂他在干啥)(估计就是隐藏光标)
void HideCursor()
{CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量curInfo.dwSize = 1; //如果没赋值的话,隐藏光标无效curInfo.bVisible = FALSE; //将光标设置为不可见HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
//为了解清晰其中,关键结构CONSOLE_CURSOR_INFO在其头文件当中的内容如下:
// (自己去查,我也不知道)//光标跳转(在屏幕上进行输出时,我们需要光标先移动到目标位置再进行输出,因此,光标跳转函数也是必不可少的)
void CursorJump(int x, int y)
{COORD pos; //定义光标位置的结构体变量pos.X = x; //横坐标设置pos.Y = y; //纵坐标设置HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄SetConsoleCursorPosition(handle, pos); //设置光标位置
}//初始化界面
//主界面的外层的边框打印
void InitInterface()
{color(7); //颜色设置为白色for (int i = 0; i < ROW; i++){for (int j = 0; j < COL + 10; j++){if (j == 0 || j == COL - 1 || j == COL + 9){face.data[i][j] = 1; //标记该位置有方块CursorJump(2 * j, i);printf("■");}else if (i == ROW - 1 || i == 0)//首末两行边框的打印{face.data[i][j] = 1; //标记该位置有方块printf(" ■");//此处予以说明,为了整齐起见,每行方块之间加入空格。下方代码同理}elseface.data[i][j] = 0; //标记该位置无方块}}for (int i = COL; i < COL + 10; i++)//右侧边框分隔线{face.data[8][i] = 1; //标记该位置有方块CursorJump(2 * i, 8);printf("■");}//从这里开始就是写右侧边框中的文字与提示语句(无需细看,无脑打印,想整什么活都可以整)CursorJump(2 * COL, 1);printf("下一个方块:");CursorJump(2 * COL + 4, ROW - 19);printf("左移:←");CursorJump(2 * COL + 4, ROW - 17);printf("右移:→");CursorJump(2 * COL + 4, ROW - 15);printf("加速:↓");CursorJump(2 * COL + 4, ROW - 13);printf("旋转:空格");CursorJump(2 * COL + 4, ROW - 11);printf("暂停: S");CursorJump(2 * COL + 4, ROW - 9);printf("退出: Esc");CursorJump(2 * COL + 4, ROW - 7);printf("重新开始:R");CursorJump(2 * COL + 4, ROW - 5);printf("最高纪录:%d", max);CursorJump(2 * COL + 4, ROW - 3);printf("当前分数:%d", grade);
}//初始化方块信息
//存储各种形状的俄罗斯方块
void InitBlockInfo()
{//“T”形for (int i = 0; i <= 2; i++)block[0][0].space[1][i] = 1;block[0][0].space[2][1] = 1;//“L”形for (int i = 1; i <= 3; i++)block[1][0].space[i][1] = 1;block[1][0].space[3][2] = 1;//“J”形for (int i = 1; i <= 3; i++)block[2][0].space[i][2] = 1;block[2][0].space[3][1] = 1;for (int i = 0; i <= 1; i++){//“Z”形block[3][0].space[1][i] = 1;block[3][0].space[2][i + 1] = 1;//“S”形block[4][0].space[1][i + 1] = 1;block[4][0].space[2][i] = 1;//“O”形block[5][0].space[1][i + 1] = 1;block[5][0].space[2][i + 1] = 1;}//“I”形for (int i = 0; i <= 3; i++)block[6][0].space[i][1] = 1;int temp[4][4];for (int shape = 0; shape < 7; shape++) //7种形状{for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种){//获取第form种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){temp[i][j] = block[shape][form].space[i][j];}}//将第form种形态顺时针旋转,得到第form+1种形态for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){block[shape][form + 1].space[i][j] = temp[3 - j][i];}}}}
}//颜色设置
void color(int c)
{switch (c){case 0:c = 13; //“T”形方块设置为紫色break;case 1:case 2:c = 12; //“L”形和“J”形方块设置为红色break;case 3:case 4:c = 10; //“Z”形和“S”形方块设置为绿色break;case 5:c = 14; //“O”形方块设置为黄色break;case 6:c = 11; //“I”形方块设置为浅蓝色break;default:c = 7; //其他默认设置为白色(若需将颜色重置可把color变为7)break;}SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置//注:SetConsoleTextAttribute是一个API(应用程序编程接口)(from百度,自行了解)
}//画出方块
void DrawBlock(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1 && y + i > 0) //如果该位置有方块{CursorJump(2 * (x + j), y + i); //光标跳转到指定位置printf("■"); //输出方块}}}//由于不怎么会如何让打印的图形不影响边框(上边框),于是愚蠢的我决定再打印一遍上边框//大家有更好的方法自行尝试color(7);//不加的话第一行的颜色会与图形的颜色相同for (int j = 0; j < COL + 10; j++){if (j == 0 || j == COL - 1 || j == COL + 9){CursorJump(2 * j, 0);printf("■");}else{printf(" ■");}}
}//空格覆盖!!!
void DrawSpace(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1) //如果该位置有方块{CursorJump(2 * (x + j), y + i); //光标跳转到指定位置printf(" "); //打印空格覆盖(两个空格)}}}
}//合法性判断
int IsLegal(int shape, int form, int x, int y)
{for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){//如果方块落下的位置本来就已经有方块了,则不合法if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1))return 0; //不合法}}return 1; //合法
}//判断得分与结束
int JudeFunc()
{//判断是否得分for (int i = ROW - 2; i > 4; i--){int sum = 0; //记录第i行的方块个数for (int j = 1; j < COL - 1; j++){sum += face.data[i][j]; //统计第i行的方块个数}if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)break; //跳出循环if (sum == COL - 2) //该行全是方块,可得分{grade += 10; //满一行加10分color(7); //颜色设置为白色CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置printf("当前分数:%d", grade); //更新当前分数for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息{face.data[i][j] = 0; //该位置得分后被清除,标记为无方块CursorJump(2 * j, i); //光标跳转到该位置printf(" "); //打印空格覆盖(两个空格)}//把被清除行上面的行整体向下挪一格for (int m = i; m > 1; m--){sum = 0; //记录上一行的方块个数for (int n = 1; n < COL - 1; n++){sum += face.data[m - 1][n]; //统计上一行的方块个数face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块{CursorJump(2 * n, m); //光标跳转到该位置color(face.color[m][n]); //颜色设置为还方块的颜色printf("■"); //打印方块}else //上一行移下来的是空格,打印空格{CursorJump(2 * n, m); //光标跳转到该位置printf(" "); //打印空格(两个空格)}}if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)}}}//判断游戏是否结束for (int j = 1; j < COL - 1; j++){if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行){Sleep(1000); //留给玩家反应时间system("cls"); //清空屏幕color(7); //颜色设置为白色CursorJump(2 * (COL / 3), ROW / 2 - 9);if (grade > max)//接下来就是曼妙的人机交互部分{printf("恭喜你打破最高记录,最高记录更新为%d", grade);WriteGrade();}else if (grade == max){printf("与最高记录持平,加油再创佳绩", grade);}else{printf("请继续加油,当前与最高记录相差%d", max - grade);}CursorJump(2 * (COL / 3), ROW / 2 - 6);printf("GAME OVER");while (1){char ch;CursorJump(2 * (COL / 3), ROW / 2 - 3);printf("再来一局?");CursorJump(2 * (COL / 3), ROW / 2);printf("输入y并敲击回车(y/n)进入下一局;");CursorJump(2 * (COL / 3), ROW / 2 + 3);printf("输入n并敲击回车(n/n)结束游戏;");CursorJump(2 * (COL / 3), ROW / 2 + 6);printf("敲击esc则退出游戏;");if (kbhit() != 0){printf("Y");if (getch() == 27){printf("Y");system("cls"); //清空屏幕color(7);CursorJump(COL, ROW / 2);printf(" 游戏结束 ");CursorJump(COL, ROW / 2 + 2);exit(0); //结束程序}//疑点:上面的if判断和执行代码无法实现,还未研究明白******}scanf("%c", &ch);if (ch == 'y' || ch == 'Y'){system("cls");main();}else if (ch == 'n' || ch == 'N'){CursorJump(2 * (COL / 3), ROW / 2 + 5);exit(0);}else{CursorJump(2 * (COL / 3), ROW / 2 + 4);printf("选择错误,请再次选择");}}}}return 0; //判断结束,无需再调用该函数进行判断
}//游戏主体逻辑函数!!!
void StartGame()
{int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态while (1){int t = 0;int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标color(nextShape); //颜色设置为下一个方块的颜色DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角while (1){color(shape); //颜色设置为当前正在下落的方块DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置if (t == 0){t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度,我的评价是可以搞事)}while (--t){if (kbhit() != 0) //若键盘被敲击,则退出循环break;}if (t == 0) //键盘未被敲击{if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部){//将当前方块的信息录入face当中//face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){if (block[shape][form].space[i][j] == 1){face.data[y + i][x + j] = 1; //将该位置标记为有方块face.color[y + i][x + j] = shape; //记录该方块的颜色数值}}}while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束break; //跳出当前死循环,准备进行下一个方块的下落}else //未到底部{DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}}else //键盘被敲击{//对敲击键盘的按键的判断//(不理解为什么这个对,上面那个ESC的判断就有问题)******char ch = getch(); //读取keycodeswitch (ch){case DOWN: //方向键:下if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法{//方块下落后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)}break;case LEFT: //方向键:左if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法{//方块左移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x--; //横坐标自减(下一次显示方块时就相当于左移了一格了)}break;case RIGHT: //方向键:右if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法{//方块右移后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置x++; //横坐标自增(下一次显示方块时就相当于右移了一格了)}break;case SPACE: //空格键if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法{//方块旋转后合法才进行以下操作DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置y++; //纵坐标自增(总不能原地旋转吧)form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了)}break;case ESC: //Esc键system("cls"); //清空屏幕color(7);CursorJump(COL, ROW / 2);printf(" 游戏结束 ");CursorJump(COL, ROW / 2 + 2);exit(0); //结束程序case 's':case 'S': //暂停system("pause>nul"); //暂停(按任意键继续)break;case 'r':case 'R': //重新开始system("cls"); //清空屏幕main(); //重新执行主函数}}}shape = nextShape, form = nextForm; //获取下一个方块的信息DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖}
}
//从文件读取最高分
void ReadGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "r"); //以只读方式打开文件if (pf == NULL) //打开文件失败{pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件(文件不存在可以自动创建该文件)fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0}fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}
//更新最高分到文件
void WriteGrade()
{FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件if (pf == NULL) //打开文件失败{printf("保存最高得分记录失败\n");exit(0);}fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分)fclose(pf); //关闭文件pf = NULL; //文件指针及时置空
}
//over
// //还是那句话
//看似简单,实际上一点也不难
相关文章:
用c语言完成俄罗斯方块小游戏
用c语言完成俄罗斯方块小游戏 这估计是你在编程学习过程中的第一个小游戏开发,怎么说呢,在这里只针对刚学程序设计的学生,就是说刚接触C语言没多久,有一点功底的学生看看,简陋的代码,简陋的实现࿰…...
Leetcode打卡:N皇后
执行结果:通过 题目:51 N皇后 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#…...
位运算在嵌入式系统开发中的应用
目录 一、数据存储与节省 “绝技” 1.1. 传感器数据存储挑战 1.2. 位运算解决方案 1.2.1. 数据整合 1.2.2. 数据提取 1.3. 收益分析 二、硬件控制 “精准操纵术” 2.1. 位运算操控硬件寄存器的实例 2.2. 位运算在硬件控制中的优势 2.3. 电机驱动芯片寄存器控制示例 …...
livekit 服务部署
本地起 1. 拉取生成文件镜像 sudo docker pull livekit/generate 2. 生成配置文件 sudo docker run --rm -v $PWD:/output livekit/generate --local (记住输出信息) 3. 拉取livekit/livekit-server sudo docker pull livekit/livekit-server 4. 开始运行服务 sudo docker run…...
笔记:visual studio2022编译 和 运行 VTK9.4.0
一、下载源码 VTK官网下载对应源码。 Download | VTKhttps://vtk.org/download/ 二、编译动态库(基于Win11 24h) 1. 用VS打开VTK源码的CMakeLists.txt,等待项目配置完成。 生成完毕如图 2.生成动态库,点击全部生成,…...
【Linux | 计网】TCP协议深度解析:从连接管理到流量控制与滑动窗口
目录 前言: 1、三次握手和四次挥手的联系: 为什么挥手必须要将ACK和FIN分开呢? 2.理解 CLOSE_WAIT 状态 CLOSE_WAIT状态的特点 3.FIN_WAIT状态讲解 3.1、FIN_WAIT_1状态 3.2、FIN_WAIT_2状态 3.3、FIN_WAIT状态的作用与意义 4.理解…...
Qt Sensors 传感器控制介绍篇
文章目录 Qt Sensors 模块介绍前言 什么是 Qt Sensors?主要特点: 支持的传感器类型Qt Sensors 的核心组件应用场景优势总结 Qt Sensors 模块介绍 前言 随着现代硬件设备的不断发展,传感器已成为许多设备(如智能手机、平板电脑和…...
探索3D世界:使用 lib3ds 读取和解析 3DS 文件
在3D图形开发中,读取和解析3DS文件是创建和渲染3D场景的第一步。3DS(3D Studio)文件格式是一种广泛使用的3D模型文件格式,它包含了多种类型的数据,用于描述3D场景中的物体、材质、相机、灯光和动画等。lib3ds 是一个开…...
【开源免费】基于SpringBoot+Vue.JS服装生产管理系统(JAVA毕业设计)
博主说明:本文项目编号 T 066 ,文末自助获取源码 \color{red}{T066,文末自助获取源码} T066,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…...
计算机技术:冯·诺伊曼架构
冯诺伊曼架构(Von Neumann Architecture)是由著名数学家和计算机科学家约翰冯诺伊曼(John von Neumann)在20世纪40年代提出的一种计算机设计模型。它奠定了现代通用计算机设计的基础。 1. 核心思想 冯诺伊曼架构的核心思想是存储…...
在Window10或11系统中同时安装 JDK8 和 JDK11
在Window10或11系统中同时安装 JDK8 和 JDK11 最近写项目,之前的项目是用Java8环境开发的,在二次迭代中,但是新开发的项目采用Java11环境来开发,所以需要同时安装JDK8和JDK11环境,但是两个环境是不能同时使用的&#…...
Hbase2.2.7集群部署
环境说明 准备三台服务器,分别为:bigdata141(作为Hbase主节点)、bigdata142、bigdata143确保hadoop和zookeeper集群都先启动好我这边的hadoop版本为3.2.0,zookeeper版本为3.5.8 下载安装包 下载链接:In…...
Java学习,数据结构
Java 提供了丰富的数据结构来处理和组织数据,Java的 java.util 包中提供了许多这些数据结构的实现,可以根据需要选择合适的类。 List(列表) ArrayList:基于动态数组的数据结构,允许包含重复元素和 null。 …...
微众银行前端面试题及参考答案
使用的协议是 HTTP 还是 HTTPS,为什么没用 HTTPS? 在前端开发中,有些网站使用 HTTP 协议,有些使用 HTTPS 协议。 使用 HTTP 协议的情况可能是因为网站对安全性的要求不是极高,或者处于开发的早期阶段,还没有…...
spark同步mysql数据到sqlserver
使用Apache Spark将数据从MySQL同步到SQL Server是一个常见的ETL(Extract, Transform, Load)任务。这里提供一个基本的步骤指南,以及一些代码示例来帮助你完成这项工作。 ### 前提条件 1. **安装Spark**:确保你的环境中已经安装了…...
使用 useMemo 和 React.memo 优化 React 组件渲染
在 React 中,性能优化是一个重要的主题,特别是在复杂的组件树中。本文将演示如何在同一个父组件中使用 useMemo 和 React.memo 来优化子组件的渲染。 1. 组件结构 创建一个父组件,包含两个子组件: MemoChild:使用 R…...
像素流送api ue多人访问需要什么显卡服务器
关于像素流送UE推流,在之前的文章里其实小芹和大家聊过很多,不过今天偶然搜索发现还是有很多小伙伴,在搜索像素流送相关的问题,搜索引擎给的提示有这些。当然这些都是比较短的词汇,可能每个人真正遇到的问题和想获取的…...
Bert+CRF的NER实战
CRF(条件随机场-Conditional Random Field) 原始本文:我在北京吃炸酱面 标注示例: 我O在O北B-PLA京I-PLA吃O炸B-FOOD酱I-FOOD面I-FOOD CRF: 目的:提出一些不可能出现的预测组合(例如I-PLA不能…...
python学习笔记8-函数2
参数传递 传不可变对象 & 传可变对象 def func(b):print(id(a), a) #140737041872600 234print(id(b), b) #140737041872600 234a 234 func(a)def func(b):print(id(a), a) #1413554098560 [343]print(id(b), b) #1413554098560 [343]a [343] func(a)def func(b):b.appe…...
【linux学习指南】详解Linux进程信号保存
文章目录 📝保存信号🌠 信号其他相关常⻅概念🌉在内核中的表⽰ 🌠 sigset_t🌠信号集操作函数🌉sigprocmask🌉sigpending 🚩总结 📝保存信号 🌠 信号其他相关常…...
Postman设置接口关联,实现参数化
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 postman设置接口关联 在实际的接口测试中,后一个接口经常需要用到前一个接口返回的结果, 从而让后一个接口能正常执行,这…...
ORACLE之DBA常用数据库查询
数据库信息 数据库概要select a.name "DB Name", e.global_name "Global Name", c.host_name "Host Name", c.instance_name "Instance Name" , DECODE(c.logins,RESTRICTED,YES,NO) "Restricted Mo…...
C++之C++11新特性(三)--- 智能指针
目录 一、智能指针 1.1 为什么需要智能指针 1.2 内存泄漏 1.2.1 内存泄漏的基本概念 1.2.2 内存泄漏的分类 1.2.3 如何避免内存泄漏 1.3 智能指针的使用及其原理 1.3.1 RAII 1.3.2 智能指针的基本原理 1.3.3 auto_ptr 1.3.4 unique_ptr 1.3.5 shared_ptr 1.3.6 sha…...
12 设计模式之工厂方法模式
一、什么是工厂方法模式? 1.定义 在软件开发中,设计模式 是解决常见软件设计问题的最佳实践。而 工厂方法模式(Factory Method Pattern) 作为创建型设计模式之一,常常被用来解决对象创建问题。它通过将对象的创建交给…...
Python 自动化办公的 10 大脚本
大家好,我是你们的 Python 讲师!今天我们将讨论 10 个实用的 Python 自动化办公脚本。这些脚本可以帮助你简化日常工作,提高效率。无论是处理 Excel 文件、发送邮件,还是自动化网页操作,Python 都能派上用场。 1. 批量…...
十一、快速入门go语言之接口和反射
文章目录 接口:one: 接口基础:two: 接口类型断言和空接口:star2: 空接口实现存储不同数据类型的切片/数组:star2: 复制切片到空接口切片:star2: 类型断言 反射 📅 2024年5月9日 📦 使用版本为1.21.5 接口 十、Java类的封装和继承、多态 - 七点半的菜市…...
Python中字符串和正则表达式
Python中字符串和正则表达式 在Python编程中,字符串是最常用的数据类型之一。字符串用于表示文本数据,而正则表达式则是一种强大的工具,用于处理和匹配字符串中的模式。本文将介绍Python中的字符串操作、字符串格式化以及如何使用正则表达式…...
力扣刷题TOP101:8.BM10 两个链表的第一个公共结点
目录: 目的 思路 复杂度 记忆秘诀 python代码 目的 两个无环的单向链表,它们的第一个公共结点{{6,7}。 思路 这个任务是找到两个链表的第一个公共结点。可以看作两个心机boy偷偷补课翻车事件。平时嘴上说自己在家玩游戏,实际上背地里都偷…...
⽂件操作详解
⽬录 一 文件操作的引入 1 为什么使⽤⽂件? 2 什么是⽂件? 3 文件分类(1 从⽂件功能的⻆度来分类:程序⽂件/数据⽂件 2根据数据的组织形式:为⽂本⽂件/⼆进制⽂件) 二 ⽂件的打开和关闭 1 …...
UR开始打中国牌,重磅发布国产化协作机器人UR7e 和 UR12e
近日,优傲(UR)机器人公司立足中国市场需求,重磅推出UR7e和UR12e 两款本地化协作机器人。它们延续优傲(UR)一以贯之的高品质与性能特质,着重优化负载自重比,且在价格层面具竞争力&…...
PostgreSQL实现透视表查询
PostgreSQL 8.3版本发布时,引入了一个名为tablefunc的新扩展。这个扩展提供了一组非常有趣的函数。其中之一是交叉表函数,用于创建数据透视表。这就是我们将在本文中讨论的内容。 需求说明 解释此函数如何工作的最简单方法是使用带有数据透视表的示例…...
C#里怎么样使用Array.BinarySearch函数?
C#里怎么样使用Array.BinarySearch函数? 因为二分算法如此重要,所以要多加练习。 但是它的返回值,也有三种状态,导致很多人使用它的时候, 也感觉到迷惑的。 在这里的例子演示了三种返回值的使用: /** C# Program to Search an element with Array Indices*/ using …...
量化交易系统开发-实时行情自动化交易-8.5.VNPY平台
19年创业做过一年的量化交易但没有成功,作为交易系统的开发人员积累了一些经验,最近想重新研究交易系统,一边整理一边写出来一些思考供大家参考,也希望跟做量化的朋友有更多的交流和合作。 接下来会对于VNPY平台介绍。 VN.PY 是…...
分治算法中的主定理及其应用
引言 学习递归算法的时候,找到了用来计算算法复杂度的主定理。问大语言模型,发现回答的主定理描述有所不同。本文比较了两个不同版本中表述的差异。并给出一些例子用来计算分治递归类算法的复杂度。 主定理的不同版本 版本1 在《算法导论》第三版第四…...
前端的面试题
1.常用的块与行属性内标签有哪些?有什么特征? 块标签:div、h1~h6、ul、li、table、p、br、form。 特征:独占一行,换行显示,可以设置宽高,可以嵌套块和行 行标签:span、a、img、text…...
vue实现excel导出导入
文章目录 安装xlsx依赖和file-saver依赖Excel导出使用element-ui的el-table展示数据定义导出按钮将数据导出 excel导入定义文件导入显示框定义导入按钮解析选择的文件进行导入 安装xlsx依赖和file-saver依赖 npm install xlsx -S npm install file-saver -SExcel导出 使用ele…...
婚纱摄影管理系统|Java|SSM|VUE| 前后端分离
【重要1⃣️】前后端源码万字文档部署文档 【重要2⃣️】正版源码有问题包售后 【重要3⃣️】虚拟可复制品不支持退换货 【重要3⃣️】虚拟可复制品不支持退换货 【重要3⃣️】虚拟可复制品不支持退换货 【包含内容】 【一】项目提供非常完整的源码注释 【二】相关技术栈文档 【…...
对拍详细使用方法
对拍的作用 对于我们在学校OJ,cf,牛客…各种只提供少量测试数据的题目,常常交上代码常常超时,能写出正确的暴力代码而题目要求的时间复杂度更低。然而这时你写出了能通过样例且时间复杂度更低的代码,但交上去就是错误…...
Flume 与 Kafka 整合实战
目录 一、Kafka 作为 Source【数据进入到kafka中,抽取出来】 (一)环境准备与配置文件创建 (二)创建主题 (三)测试步骤 二、Kafka 作为 Sink数据从别的地方抽取到kafka里面】 (…...
Web开发技术栈选择指南
互联网时代的蓬勃发展,让越来越多人投身软件开发领域。面对前端和后端的选择,很多初学者往往陷入迷茫。让我们一起深入了解这两个领域的特点,帮助你做出最适合自己的选择。 在互联网发展的早期,前端开发主要负责页面布局和简单的…...
社群赋能电商:小程序 AI 智能名片与 S2B2C 商城系统的整合与突破
摘要:本文聚焦于社群在电商领域日益凸显的关键地位,深入探讨在社群粉丝经济迅猛发展背景下,小程序 AI 智能名片与 S2B2C 商城系统如何与社群深度融合,助力电商突破传统运营局限,挖掘新增长点。通过分析社群对电商的价值…...
C++11 http服务端和客户端库cpp-httplib
C11 http服务端和客户端库cpp-httplib 环境: http: yhirose/cpp-httplib v0.18.1 json: nlohmann/json v3.11.31. 简介 cpp-httplib 是一个轻量级且易于使用的 C11 HTTP 库,由 yhirose 开发和维护,开源协议为MIT。它支持 HTTP/HTTPS 协议&…...
spring-boot自定义ApplicationListener及源码分析
ApplicationListener是spring boot应用启动时的事件监听器。监听的事件有(包括但不限于): (1)接下来,我们先通过一个例子实现自定义ApplicationListener: 监听器需要实现ApplicationListener<…...
打造双层环形图:基础与高级渐变效果的应用
在数据可视化领域,环形图因其独特的展示方式而广受欢迎。今天,我们将通过ECharts库来创建一个具有双层渐变效果的高级环形图。本文将详细介绍如何实现这种视觉效果。 1. 环形图基础 首先,我们需要了解环形图的基本构成。环形图由内外两个圆…...
BUGKU printf
整体思路 实现循环-->获取libc版本和system函数地址->将strcpy的got表项修改为system并获得shell 第一步:实现循环 从汇编语句可以看出,在每次循环结束时若0x201700处的值是否大于1则会继续循环。 encode1会将编码后的结果保存至0x2015c0处&am…...
spring boot2.7集成OpenFeign 3.1.7
1.Feign Feign是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持&…...
SSM相关面试题01
目录 1.何为Spring Bean容器?Spring Bean容器与Spring IOC 容器有什么不同吗? 2.Spring IOC 如何理解? 3.Spring DI 如何理解? 4.Spring 中基于注解如何配置对象作用域?以及如何配置延迟加载机制? 5.Spring 工厂底层构建Bean对象借助什么机制?当对象不使用了要释放…...
Python websocket
router.websocket(/chat/{flow_id}) 接口代码,并了解其工作流程、涉及的组件以及如何基于此实现你的新 WebSocket 接口。以下内容将分为几个部分进行讲解: 接口整体概述代码逐行解析关键组件和依赖关系如何基于此实现新功能示例:创建一个新的…...
正则表达式
正则表达式: 正则表达式区别于通配符,正则表达式是用来匹配文本的内容,命令的输出结果也属于文本内容。也可以使用正则表达式。 通配符用来匹配文件名和目录名。 grep用来过滤文本内容,以匹配要查询的结果。 linux的文本三剑客…...
机器学习——生成对抗网络(GANs):原理、进展与应用前景分析
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一. 生成对抗网络的基本原理二. 使用步骤2.1 对抗性训练2.2 损失函数 三. GAN的变种和进展四. 生成对抗网络的应用五. 持续挑战与未来发展方向六. 小结 前言 生…...