c语言——俄罗斯方块

avatar
作者
猴君
阅读量:1

一、游戏效果



二. 游戏背景

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

《俄罗斯方块》(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); 	//设置标准输出上光标的位置为pos 	SetConsoleCursorPosition(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; }

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!