c语言——俄罗斯方块

一、游戏效果


俄罗斯方块


二. 游戏背景

俄罗斯方块是久负盛名的游戏,它也和贪吃蛇,扫雷等游戏位列经典游戏的⾏列。

《俄罗斯方块》(Tetris,俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。

该游戏曾经被多家公司代理过。经过多轮诉讼后,该游戏的代理权最终被任天堂获得。 任天堂对于俄罗斯方块来说意义重大,因为将它与GB搭配在一起后,获得了巨大的成功。 

《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。

三、游戏开发日志

基本逻辑结构与贪吃蛇一致 

四、游戏实现

我们有了贪吃蛇的知识储备及了解WIN32 API,理解下面内容也就不难了。

4.1  游戏逻辑主体

#include"tetris.h"int main()
{//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印setlocale(LC_ALL, "");srand((unsigned int)time(NULL));char ch=0;do{GameInit();GameRun();GameOver();system("cls");SetPos(20, 12);printf("再来一局吗?(Y/N):\n");SetPos(22, 15);ch = getchar();getchar();//清理\n} while (ch == 'y' || ch == 'Y');SetPos(0, 29);return 0;
}

4.2  游戏初始化的实现

4.2.1  设置窗口大小、名称及隐藏光标

//设置控制台窗⼝的⼤⼩,30⾏,30列system("mode con cols=60 lines=30");//设置cmd窗⼝名称system("title  俄罗斯方块");//获取标准输出的句柄(⽤来标识不同设备的数值)HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

4.2.2 打印欢迎界面 

void WelcomeToGame()
{SetPos(20, 10);printf("欢迎来到俄罗斯方块!");SetPos(20, 23);//让按任意键继续的出现的位置好看点system("pause");system("cls");SetPos(14, 10);printf("↑为变换.↓为加速.←为左移.→为右移\n");SetPos(20, 23);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}

 4.2.3  初始化地图

void CreateMap()
{memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)int i = 0;//墙体轮廓SetPos(40, 0);for (i = 40; i < 60; i += 2){wprintf(L"%lc", WALL);}SetPos(0,29);for (i = 0; i < 60; i += 2){IF.pos[i][29] = 1;wprintf(L"%lc", WALL);}//x是0,y从1开始增⻓for (i = 0; i < 30; i++){IF.pos[0][i] = 1;//标记此处有方块SetPos(0, i);wprintf(L"%c", WALL);}for (i = 0; i < 30; i++){IF.pos[38][i] = 1;SetPos(38, i);wprintf(L"%c", WALL);}for (i = 0; i < 30; i++){IF.pos[58][i] = 1;SetPos(58, i);wprintf(L"%c", WALL);}SetPos(40, 10);for (i = 40; i < 60; i += 2){IF.pos[i][10] = 1;wprintf(L"%lc", WALL);}//提示字体SetPos(40, 1);printf("下一个方块:");SetPos(44, 12);printf("左移:←");SetPos(44, 14);printf("右移:→");SetPos(44, 16);printf("加速:↓");SetPos(44, 18);printf("变换:↑");SetPos(44, 20);printf("暂停: 空格");SetPos(44, 22);printf("退出: Esc");SetPos(44, 24);printf("重新开始:R");SetPos(44, 26);printf("当前分数:%d", grade);}

注解: 

1)一个宽字符占2个字节,我们设置 cols=60 lines=30,实际就是长宽各30个字节大小

2)如上图所示,游戏展示区长为20,高为30,右上部分为下一个方块提示区域,正下面为游戏操作提示区

3)横轴为x轴,向右坐标逐渐增大,向下为y轴,坐标逐渐增大。(x,y)确定宽字符(小方块)的位置,因为宽字符(小方块)占2个字节宽度,所以x轴坐标没有奇数值

4)为了方便检测下落方块是否靠墙、落地及碰到其它方块,我们定义一个大数组pos[60][30],一一对应地图上每个宽字符(小方块)的位置,用大数组pos[60][30]记录墙体位置及落地的方块位置

typedef struct InterFace
{int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)}InterFace;InterFace IF;

4.2.4 初始化方块信息

void InitBlockInfo()
{//'T'字型int i = 0;for (i = 0; i < 3; i++){block[0][0].space[1][i] = 1;}block[0][0].space[2][1] = 1;//‘L'字型for (i = 1; i < 4; i++){block[1][0].space[i][1] = 1;}block[1][0].space[3][2] = 1;//'J'字形for (i = 1; i < 4; i++){block[2][0].space[i][2] = 1;}block[2][0].space[3][1] = 1;//’Z'字型for (i = 0; i < 2; i++){block[3][0].space[1][i] = 1;}for (i = 1; i < 3; i++){block[3][0].space[2][i] = 1;}//‘S'字型for (i = 1; i < 3; i++){block[4][0].space[1][i] = 1;}for (i = 0; i < 2; i++){block[4][0].space[2][i] = 1;}//'田’字型int j = 0;for (i = 1; i < 3; i++){for (j = 1; j < 3; j++){block[5][0].space[i][j] = 1;}}//长条型for (i = 0; i < 4; i++){block[6][0].space[i][1] = 1;}int m = 0;int n = 0;int tmp[4][4];for (i = 0; i < 7; i++){block[i][0].speed = 200;//初始化下落速度block[i][0].blockstate = NORMAL;}for (i = 0; i < LINES; i++){for (j = 0; j < COLS-1; j++){for (m = 0; m < 4; m++){for (n = 0; n < 4; n++){tmp[m][n] = block[i][j].space[m][n];}}for (m = 0; m < 4; m++){for (n = 0; n < 4; n++){block[i][j + 1].space[n][3-m] = tmp[m][n];}}block[i][j + 1].speed = 200;//初始化下落速度block[i][j + 1].blockstate =NORMAL;//初始化方块状态}}}

注解: 

1)我们定义结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种。

#define  COLS 4
#define LINES  7
enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
typedef struct Block
{int space[4][4];int speed;enum BlockState blockstate;
}Block; 
Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种

 2)首先初始化7种基本形状方块,然后基本形状方块通过顺时针旋转依次得到各自剩下的三种形态。

4.3  游戏运行的实现

void GameRun()
{int i = rand() % 7, j = rand() % 4; //随机获取方块的形状do{int x = 18;int y = 0;int next_i =  rand() % 7, next_j = rand() % 4;PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形PrintBlock(i, j, 18, 0);//绘制方块初始位置while(IsLegal(i, j, x, y ))//不合法,说明到底了{if (KEY_PRESS(VK_UP)){block[i][j].blockstate = CHANGE;}else if (KEY_PRESS(VK_DOWN)){block[i][j].blockstate = DOWN;}else if (KEY_PRESS(VK_LEFT)){block[i][j].blockstate = LEFT;//左移}else if (KEY_PRESS(VK_RIGHT)){block[i][j].blockstate = RIGHT;//右移}else if (KEY_PRESS(VK_SPACE)){pause();//暂停}else if (KEY_PRESS(VK_ESCAPE)){state = END;//退出break;}else if (KEY_PRESS(R)){state = RESTART;//重新开始break;}else//什么都没按{block[i][j].blockstate = NORMAL;}BlockMove(block, &i, &j, &x,&y);PrintBlock(i, j, x, y);//绘制方块位置Sleep(block[i][j].speed);//每一帧休息的时间}i = next_i;j = next_j;PrintSpace(i,j, 46, 4);//覆盖提示框图形} while (state ==RUN);
}

4.3.1  合法判断(碰撞检测)

//碰撞检测
int IsLegal(int i, int j, int x, int y)
{for (int m = 0; m < 4; m++){for (int n = 0; n< 4; n++){//如果方块落下的位置本来就已经有方块了,则不合法if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))return 0; //不合法}}return 1; //合法
}

4.3.2  覆盖方块

void  PrintSpace(int i, int j, int  x, int y)//覆盖指定位置方块
{for (int m = 0; m< 4; m++){for (int n = 0; n < 4; n++){if (block[i][j].space[m][n] == 1){SetPos(x+2*n,y+m); //光标跳转到指定位置printf("  "); //打印空格覆盖(两个空格)}}}}

4.3.3  绘制方块

void PrintBlock(int i, int j, int x, int y)
{
int m = 0;
int n = 0;
for (m = 0; m < 4; m++)
{for (n = 0; n < 4; n++){if (block[i][j].space[m][n] == 1){//IF.pos[x+n*2][y+m] = 1;//记录该位置有方块SetPos(x + n * 2, y + m);wprintf(L"%c", BIOCK);}}
}
}

 4.3.4   判断满行消除

int JudeIsErase()
{int sign = 0;int i = 0;for (i = 28; i > 3; i--){int count = 0;for (int j = 2; j < 38; j += 2){if (IF.pos[j][i] == 1){count++;}}if (count == 18)//整行全是方块{sign = 1;grade += 10;SetPos(44, 26);printf("当前分数:%d", grade);//更新当前分数for (int a = 2; a < 38; a += 2){SetPos(a, i);printf("  ");IF.pos[a][i] = 0;}//将该行以上全部下移一格for (int m = i; m > 3; m--){for (int n = 2; n < 38; n += 2){IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块{SetPos(n, m); //光标跳转到该位置wprintf(L"%c", BIOCK); //打印方块}else //上一行移下来的是空格,打印空格{SetPos(n, m); //光标跳转到该位置printf("  "); //打印空格(两个空格)}}}}}return sign;}

注:

1)用大数组pos[60][30],从下往上判断是否满行,如果满行,则将将该行以上全部下移一格,这样就达到消行的目的

2)若第十四第十三行都满行了,当检测到第十四行满行,将第十三行及以上全部下移一格,再次循环,从现十三行(原十二行)开始检测,此时会漏掉现十四行(原十三行),所以引进标识变量sign,只要有消行,则变为1,当sign=0时,表示全部方块没有满行了

4.4.5   判断游戏失败

void JudeIsOver()
{for (int i = 2; i < 38; i+=2){if (IF.pos[i][1] == 1) //第一行有方块存在{state = FAIL;}}}

 

4.4.6  方块移动

 

void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
{switch (block[*i][*j].blockstate){case CHANGE:if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞{//方块旋转后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*j) = ((*j) + 1) % 4;(*y)++;//PrintBlock(i, j, x, y);//绘制下一个变化的方块block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;}//else //方块再下落就碰撞了(已经到达底部)//{//	//将当前方块的信息录入IF当中//	for (int m = 0; m< 4; m++)//	{//		for (int n = 0; n< 4; n++)//		{//			if (block[*i][*j].space[m][n] == 1)//			{//				IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块//				//			}//		}//	}//	while (JudeIsErase()); //判断此次方块下落是否得分//	JudeIsOver();//}break;case LEFT:if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法{//方块左移后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*x) = (*x) - 2;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;//PrintBlock(i, j, x, y);//绘制下一个变化的方块}break;case RIGHT:if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法{//方块右移后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*x) = (*x) + 2;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;//PrintBlock(i, j, x, y);//绘制下一个变化的方块}break;case DOWN:if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法{//方块下移后合法才进行以下操作PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置(*y)++;block[*i][*j].blockstate = NORMAL;if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug{block[*i][*j].speed -= 50;}}else //碰撞{//将当前方块的信息录入IF当中for (int m = 0; m < 4; m++){for (int n = 0; n < 4; n++){if (block[*i][*j].space[m][n] == 1){IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块}}}while (JudeIsErase()); //判断此次方块下落是否得分JudeIsOver();}break;case NORMAL:if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法{//方块右移后合法才进行以下操作PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置(*y)++;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed=200;}else //碰撞{//将当前方块的信息录入IF当中for (int m = 0; m < 4; m++){for (int n = 0; n < 4; n++){if (block[*i][*j].space[m][n] == 1){IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块}}}while (JudeIsErase()); //判断此次方块下落是否得分JudeIsOver();}break;}}

注:

1)这里我们根据方块状态判断方块的下一步位置和形状,需要改变参数,所以传的是地址

2)只有在正常下落和加速下落才需要描述不合法时(碰撞时)状况,因为此时已经到底了。变换方块时不需要描述不合法(碰撞时)状况(因为'L'型,在变换时容易与边界发生碰撞,若此时将方块位置记录在pos[60][30]的大数组中,导致跳出该循环,进行下一个方块判断,导致该方块悬浮在半空的BUG)

4.4   游戏结束的实现

enum GameState{ RUN = 1,END,FAIL,RESTART };
enum GameState state;void GameOver()
{switch (state){case END:SetPos(14, 15);printf("您主动退出游戏\n");SetPos(14, 18);system("pause");break;case FAIL:Sleep(1000); //留给玩家反应时间system("cls"); //清空屏幕SetPos(20, 10);printf("小垃圾,游戏结束!\n");SetPos(20, 14);printf("你的最终得分为: %d ", grade);SetPos(20, 18);system("pause");break;case RESTART:system("cls"); //清空屏幕SetPos(26, 10);printf("重新开始!\n");SetPos(22, 14);printf("小垃圾,你准备好了吗?\n");Sleep(2000);main();break;}
}

注:

1)对游戏的三个状态分别进行描述。

五、完整代码

5.1   tetris.h

#include<Windows.h>
#include <locale.h>
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define WALL  L'□'
#define BIOCK  L'■' 
#define  COLS 4
#define LINES  7
#define R 0x52	
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
enum GameState{ RUN = 1,END,FAIL,RESTART };
enum GameState state;enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
int grade;
typedef struct InterFace
{int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)}InterFace;InterFace IF;
typedef struct Block
{int space[4][4];int speed;enum BlockState blockstate;
}Block; 
Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种
void GameInit();
void CreateMap();
void InitBlockInfo();
void GameRun();
void PrintBlock(int i,int j,int x,int y);//ij 为方块形状,x,y为打印坐标
void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y);
void  PrintSpace(int i, int j, int  x, int y);
int JudeIsErase();
void JudeIsOver();
void GameOver();

5.2   tetris.c

#include"tetris.h"//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);}
void WelcomeToGame()
{SetPos(20, 10);printf("欢迎来到俄罗斯方块!");SetPos(20, 23);//让按任意键继续的出现的位置好看点system("pause");system("cls");SetPos(14, 10);printf("↑为变换.↓为加速.←为左移.→为右移\n");SetPos(20, 23);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}
void CreateMap()
{memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)int i = 0;//墙体轮廓SetPos(40, 0);for (i = 40; i < 60; i += 2){wprintf(L"%lc", WALL);}SetPos(0,29);for (i = 0; i < 60; i += 2){IF.pos[i][29] = 1;wprintf(L"%lc", WALL);}//x是0,y从1开始增⻓for (i = 0; i < 30; i++){IF.pos[0][i] = 1;//标记此处有方块SetPos(0, i);wprintf(L"%c", WALL);}for (i = 0; i < 30; i++){IF.pos[38][i] = 1;SetPos(38, i);wprintf(L"%c", WALL);}for (i = 0; i < 30; i++){IF.pos[58][i] = 1;SetPos(58, i);wprintf(L"%c", WALL);}SetPos(40, 10);for (i = 40; i < 60; i += 2){IF.pos[i][10] = 1;wprintf(L"%lc", WALL);}//提示字体SetPos(40, 1);printf("下一个方块:");SetPos(44, 12);printf("左移:←");SetPos(44, 14);printf("右移:→");SetPos(44, 16);printf("加速:↓");SetPos(44, 18);printf("变换:↑");SetPos(44, 20);printf("暂停: 空格");SetPos(44, 22);printf("退出: Esc");SetPos(44, 24);printf("重新开始:R");/*SetPos(44, 26);printf("最高纪录:%d", max);*/SetPos(44, 26);printf("当前分数:%d", grade);/*getchar();*/}
void InitBlockInfo()
{//'T'字型int i = 0;for (i = 0; i < 3; i++){block[0][0].space[1][i] = 1;}block[0][0].space[2][1] = 1;//‘L'字型for (i = 1; i < 4; i++){block[1][0].space[i][1] = 1;}block[1][0].space[3][2] = 1;//'J'字形for (i = 1; i < 4; i++){block[2][0].space[i][2] = 1;}block[2][0].space[3][1] = 1;//’Z'字型for (i = 0; i < 2; i++){block[3][0].space[1][i] = 1;}for (i = 1; i < 3; i++){block[3][0].space[2][i] = 1;}//‘S'字型for (i = 1; i < 3; i++){block[4][0].space[1][i] = 1;}for (i = 0; i < 2; i++){block[4][0].space[2][i] = 1;}//'田’字型int j = 0;for (i = 1; i < 3; i++){for (j = 1; j < 3; j++){block[5][0].space[i][j] = 1;}}//长条型for (i = 0; i < 4; i++){block[6][0].space[i][1] = 1;}int m = 0;int n = 0;int tmp[4][4];for (i = 0; i < 7; i++){block[i][0].speed = 200;//初始化下落速度block[i][0].blockstate = NORMAL;}for (i = 0; i < LINES; i++){for (j = 0; j < COLS-1; j++){for (m = 0; m < 4; m++){for (n = 0; n < 4; n++){tmp[m][n] = block[i][j].space[m][n];}}for (m = 0; m < 4; m++){for (n = 0; n < 4; n++){block[i][j + 1].space[n][3 - m] = tmp[m][n];}}block[i][j + 1].speed = 200;//初始化下落速度block[i][j + 1].blockstate =NORMAL;//初始化方块状态}}}void GameInit()
{//设置控制台窗⼝的⼤⼩,30⾏,30列system("mode con cols=60 lines=30");//设置cmd窗⼝名称system("title  俄罗斯方块");//获取标准输出的句柄(⽤来标识不同设备的数值)HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界⾯WelcomeToGame();//初始化地图CreateMap();//初始化方块信息InitBlockInfo();//初始化游戏状态state = RUN;
}
void PrintBlock(int i, int j, int x, int y)
{
int m = 0;
int n = 0;
for (m = 0; m < 4; m++)
{for (n = 0; n < 4; n++){if (block[i][j].space[m][n] == 1){//IF.pos[x+n*2][y+m] = 1;//记录该位置有方块SetPos(x + n * 2, y + m);wprintf(L"%c", BIOCK);}}
}
}
void pause()//暂停
{while (1){Sleep(300);if (KEY_PRESS(VK_SPACE)){break;}}
}
//碰撞检测
int IsLegal(int i, int j, int x, int y)
{for (int m = 0; m < 4; m++){for (int n = 0; n< 4; n++){//如果方块落下的位置本来就已经有方块了,则不合法if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))return 0; //不合法}}return 1; //合法
}
void  PrintSpace(int i, int j, int  x, int y)//覆盖指定位置方块
{for (int m = 0; m< 4; m++){for (int n = 0; n < 4; n++){if (block[i][j].space[m][n] == 1){SetPos(x+2*n,y+m); //光标跳转到指定位置printf("  "); //打印空格覆盖(两个空格)}}}}
int JudeIsErase()
{int sign = 0;int i = 0;for (i = 28; i > 3; i--){int count = 0;for (int j = 2; j < 38; j += 2){if (IF.pos[j][i] == 1){count++;}}if (count == 18)//整行全是方块{sign = 1;grade += 10;SetPos(44, 26);printf("当前分数:%d", grade);//更新当前分数for (int a = 2; a < 38; a += 2){SetPos(a, i);printf("  ");IF.pos[a][i] = 0;}//将该行以上全部下移一格for (int m = i; m > 3; m--){for (int n = 2; n < 38; n += 2){IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块{SetPos(n, m); //光标跳转到该位置wprintf(L"%c", BIOCK); //打印方块}else //上一行移下来的是空格,打印空格{SetPos(n, m); //光标跳转到该位置printf("  "); //打印空格(两个空格)}}}}}return sign;}
void JudeIsOver()
{for (int i = 2; i < 38; i+=2){if (IF.pos[i][1] == 1) //第一行有方块存在{state = FAIL;}}}void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
{switch (block[*i][*j].blockstate){case CHANGE:if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞{//方块旋转后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*j) = ((*j) + 1) % 4;(*y)++;//PrintBlock(i, j, x, y);//绘制下一个变化的方块block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;}//else //方块再下落就碰撞了(已经到达底部)//{//	//将当前方块的信息录入IF当中//	for (int m = 0; m< 4; m++)//	{//		for (int n = 0; n< 4; n++)//		{//			if (block[*i][*j].space[m][n] == 1)//			{//				IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块//				//			}//		}//	}//	while (JudeIsErase()); //判断此次方块下落是否得分//	JudeIsOver();//}break;case LEFT:if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法{//方块左移后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*x) = (*x) - 2;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;//PrintBlock(i, j, x, y);//绘制下一个变化的方块}break;case RIGHT:if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法{//方块右移后合法才进行以下操作PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置(*x) = (*x) + 2;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed = 200;//PrintBlock(i, j, x, y);//绘制下一个变化的方块}break;case DOWN:if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法{//方块下移后合法才进行以下操作PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置(*y)++;block[*i][*j].blockstate = NORMAL;if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug{block[*i][*j].speed -= 50;}}else //碰撞{//将当前方块的信息录入IF当中for (int m = 0; m < 4; m++){for (int n = 0; n < 4; n++){if (block[*i][*j].space[m][n] == 1){IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块}}}while (JudeIsErase()); //判断此次方块下落是否得分JudeIsOver();}break;case NORMAL:if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法{//方块右移后合法才进行以下操作PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置(*y)++;block[*i][*j].blockstate = NORMAL;block[*i][*j].speed=200;}else //碰撞{//将当前方块的信息录入IF当中for (int m = 0; m < 4; m++){for (int n = 0; n < 4; n++){if (block[*i][*j].space[m][n] == 1){IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块}}}while (JudeIsErase()); //判断此次方块下落是否得分JudeIsOver();}break;}}void GameRun()
{int i = rand() % 7, j = rand() % 4; //随机获取方块的形状do{int x = 18;int y = 0;int next_i =  rand() % 7, next_j = rand() % 4;PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形PrintBlock(i, j, 18, 0);//绘制方块初始位置while(IsLegal(i, j, x, y ))//不合法,说明到底了{if (KEY_PRESS(VK_UP)){block[i][j].blockstate = CHANGE;}else if (KEY_PRESS(VK_DOWN)){block[i][j].blockstate = DOWN;}else if (KEY_PRESS(VK_LEFT)){block[i][j].blockstate = LEFT;//左移}else if (KEY_PRESS(VK_RIGHT)){block[i][j].blockstate = RIGHT;//右移}else if (KEY_PRESS(VK_SPACE)){pause();//暂停}else if (KEY_PRESS(VK_ESCAPE)){state = END;//退出break;}else if (KEY_PRESS(R)){state = RESTART;//重新开始break;}else//什么都没按{block[i][j].blockstate = NORMAL;}BlockMove(block, &i, &j, &x,&y);PrintBlock(i, j, x, y);//绘制方块位置Sleep(block[i][j].speed);//每一帧休息的时间}i = next_i;j = next_j;PrintSpace(i,j, 46, 4);//覆盖提示框图形} while (state ==RUN);
}
void GameOver()
{switch (state){case END:SetPos(14, 15);printf("您主动退出游戏\n");SetPos(14, 18);system("pause");break;case FAIL:Sleep(1000); //留给玩家反应时间system("cls"); //清空屏幕SetPos(20, 10);printf("小垃圾,游戏结束!\n");SetPos(20, 14);printf("你的最终得分为: %d ", grade);SetPos(20, 18);system("pause");break;case RESTART:system("cls"); //清空屏幕SetPos(26, 10);printf("重新开始!\n");SetPos(22, 14);printf("小垃圾,你准备好了吗?\n");Sleep(2000);main();break;}
}

5.3  test.c

#include"tetris.h"int main()
{//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印setlocale(LC_ALL, "");srand((unsigned int)time(NULL));char ch=0;do{GameInit();GameRun();GameOver();system("cls");SetPos(20, 12);printf("再来一局吗?(Y/N):\n");SetPos(22, 15);ch = getchar();getchar();//清理\n} while (ch == 'y' || ch == 'Y');SetPos(0, 29);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/201720.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GIT实践与常用命令---回退

实践场景 场景1 回退提交 在日常工作中&#xff0c;我们可能会和多个同事在同一个分支进行开发&#xff0c;有时候我们可能会出现一些错误提交&#xff0c;这些错误提交如果想撤销&#xff0c;可以有两种解决办法:回退( reset )、反做(revert) keywords&#xff1a;reset、rev…

Android Spannable 使用​注意事项

1、当前示例中间的 "评论"&#xff0c;使用SpannableStringBuilder实现&#xff0c;点击评论会有高亮效果加粗&#xff0c;但再点击其它Bar时无法恢复默认样式。 2、因为SpannableString或SpannableStringBuilder中的效果是叠加的&#xff0c;恢复默认样式需要先移除…

创作4周年

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言机…

【鸿蒙应用ArkTS开发系列】- 云开发入门实战二 实现城市多级联动Demo(上)

目录 概述 云数据库开发 一、创建云数据库的对象类型。 二、预置数据&#xff08;为对象类型添加数据条目&#xff09;。 三、部署云数据库 云函数实现业务逻辑 一、创建云函数 二、云函数目录讲解 三、创建resources目录 四、获取云端凭据 五、导出之前创建的元数据…

Windows + VS2022超详细点云库(PCL1.8.1)配置

本文在结合多位CSDN大佬的步骤&#xff0c;记录以下最全的点云配置过程&#xff0c;防止走弯路&#xff08;并在最后配上PCL环境配置成功的测试代码-彩色兔子&#xff09; 一、PCL介绍 PCL概述_pcl技术_一杯盐水的博客-CSDN博客 二、准备工作&#xff08;PCL版本的下载&…

2023年危险化学品生产单位安全生产管理人员证模拟考试题库及危险化学品生产单位安全生产管理人员理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年危险化学品生产单位安全生产管理人员证模拟考试题库及危险化学品生产单位安全生产管理人员理论考试试题是由安全生产模拟考试一点通提供&#xff0c;危险化学品生产单位安全生产管理人员证模拟考试题库是根据危…

DedeBIZ 管理系统 DedeV6 v6.2.6 社区版 免费授权版

DedeBIZ 系统&#xff1a;开源、安全、高效的 DedeV6 v6.2.6 社区版 DedeBIZ 系统是基于 PHP 7 版本开发的&#xff0c;具有强大的可扩展性&#xff0c;并且完全开放源代码。它采用现流行的 Go 语言设计开发&#xff0c;不仅拥有简单易用、灵活扩展的特性&#xff0c;还具备更…

Golang版本处理Skywalking Trace上报数据

Tips: 中间记录了解决问题的过程&#xff0c;如不感兴趣可直接跳至结尾 首先去es里查询skywalking trace的元数据 可以拿到一串base64加密后的data_binary(直接解密不能用&#xff0c;会有乱码&#xff0c;可参考https://github.com/apache/skywalking/issues/7423) 对data_b…

[点云分割] 条件欧氏聚类分割

介绍 条件欧氏聚类分割是一种基于欧氏距离和条件限制的点云分割方法。它通过计算点云中点与点之间的欧氏距离&#xff0c;并结合一定的条件限制来将点云分割成不同的区域或聚类。 在条件欧氏聚类分割中&#xff0c;通常会定义以下两个条件来判断两个点是否属于同一个聚类&…

php文件上传例子

目录结构&#xff1a; index.html代码&#xff1a; <!DOCTYPE html> <html><head><title>文件上传</title><meta charset"utf-8"></head><body><form action"./up.php" method"post" encty…

CentOS 7 使用pugixml 库

安装 pugixml Git下载地址&#xff1a;https://github.com/zeux/pugixml 步骤1&#xff1a;首先&#xff0c;你需要下载pugixml 的源代码。你可以从Github或者源代码官方网站下载。并上传至/usr/local/source_code/ 步骤2&#xff1a;下载完成后&#xff0c;需要将源代码解压…

摩尔定律,梅特卡夫定律,吉尔德定律

信息系统的三大定律(摩尔定律&#xff0c;梅特卡夫定律&#xff0c;吉尔德定律)有一个清晰的视角&#xff1a; 信息系统不是左边的生产消费系统&#xff0c;而是右边的交易系统&#xff0c;交易系统与生产消费典型的区别在于信息交易过程会产生新的信息&#xff0c;就像钱一样…

【JVM精讲与GC调优教程(概述)】

如何理解虚拟机(JVM)跨语言的平台 java虚拟机根本不关心运行在其内部的程序到底是使用何种编程语言编写的,他只关心“字节码”文件。 java不是最强大的语言,但是JVN是最强大的虚拟机。 不存在内存溢出? 内存泄露? JAVA = (C++)–; 垃圾回收机制为我们打理了很多繁琐的…

一键去水印免费网站快速无痕处理图片、视频水印

水印问题往往是一个大麻烦。即使我们只想将这些照片保留在我们的个人相册中以供怀旧&#xff0c;水印也可能像顽固的符号一样刺激我们的眼睛。为了解决这个问题&#xff0c;我们需要不断探索创新的解决方案&#xff0c;让我们深入研究一款强大的一键去水印免费网站“水印云”。…

MySQL慢查询

快捷查看指令 ctrlf 进行搜索会直接定位到需要的知识点和命令讲解&#xff08;如有不正确的地方欢迎各位小伙伴在评论区提意见&#xff0c;博主会及时修改&#xff09; MySQL慢查询 MySQL 慢查询是指查询语句执行速度过慢&#xff0c;影响系统整体性能和查询效率的情况。MySQL…

防止恶意攻击,服务器DDoS防御软件科普

作为一种恶意的攻击方式&#xff0c;DDoS攻击正以超出服务器承受能力的流量淹没网站&#xff0c;让网站变得不可用。近几年&#xff0c;这种攻击持续增多&#xff0c;由此优秀服务器DDoS防御软件的需求也随之增长。那么如何选择服务器DDoS防御软件&#xff0c;从根本上根除DDoS…

Cent OS 8.2 安装 自定义硬盘 固定IP VMware

时间&#xff1a;20231122 环境&#xff1a;win11 、VMware 16 pro、Cent OS 8.2 说明&#xff1a;自定义安装方法、自定义硬盘分区、固定IP且能联网 1、使用自定义的方式安装虚拟机 此处选择典型&#xff0c;则会自动安装系统&#xff0c;无法自定义硬件以及配置信息 选择…

缓存雪崩、击穿、穿透_解决方案

文章目录 缓存雪崩、击穿、穿透1.缓存雪崩造成缓存雪崩解决缓存雪崩 2. 缓存击穿造成缓存击穿解决缓存击穿 3.缓存穿透造成缓存穿透解决缓存穿透 缓存雪崩、击穿、穿透 一般用户数据存储于磁盘&#xff0c;读写速度慢。 使用redis作为缓存&#xff0c;相当于数据缓存在内存&a…

C++使用Tensorflow2.6训练好的模型进行预测

要在C语言中调用训练好的TensorFlow模型,需要使用TensorFlow C API。 https://tensorflow.google.cn/install/lang_c?hl=zh-cnten TensorFlow 提供了一个 C API,该 API 可用于为其他语言构建绑定。该 API 在 c_api.h 中定义,旨在实现简洁性和一致性,而不是便利性。 下载…

EMG肌肉信号处理合集 (一)

本文归纳了常见的肌肉信号预处理流程&#xff0c;方便EMG信号的后续分析。使用pyemgpipeline库 来进行信号的处理。文中使用了 UC Irvine 数据库的下肢数据。 目录 1 使用wrappers 定义数据类&#xff0c;来进行后续的操作 2 肌电信号DC偏置去除 3 带通滤波器处理 4 对肌电…