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);
	//设置标准输出上光标的位置为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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT/GPT4丨编程助手;AI画图;数据分析;科研/项目实现;提示词工程技巧;论文写作等

ChatGPT 在论文写作与编程方面也具备强大的能力。无论是进行代码生成、错误调试还是解决编程难题&#xff0c;ChatGPT都能为您提供实用且高质量的建议和指导&#xff0c;提高编程效率和准确性。此外&#xff0c;ChatGPT是一位出色的合作伙伴&#xff0c;可以为您提供论文写作的…

lv11 嵌入式开发 FS4412开发环境搭建 8

目录 1 开发板硬件资源介绍 2 初始原理图 3 交叉开发环境搭建 3.1 在ubuntu下安装交叉编译工具链 3.2 在windows下安装SecureCRT 3.3 在windows下安装USB转串口驱动 3.4 下载测试程序 4 硬件控制原理 4.1 地址映射表 4.2 查看数据手册 1 开发板硬件资源介绍 …

关于sklearn的:还可能是网络的问题???

前提&#xff1a;安装sklearn需要 numpy、scipy等库&#xff08;这个自行搜索&#xff09; 昨天安装numpy、scipy很快&#xff0c;一会就好了&#xff0c;然后安装sklearn一直报错&#xff0c;还以为是版本问题。 今天大早上起来&#xff0c;再次安装&#xff0c;顺利成功&…

GPIO模式详解:推挽/开漏/浮空/上拉/下拉/施密特(迟滞)输入

GPIO(General Purpose Input Output)可用于执行数字输入或输出功能。典型的应用包括从/向模拟或数字传感器/设备读写数值、驱动LED、为I2C通信驱动时钟、生成外部组件的触发、发出中断等。 文章目录 1 GPIO简介2 输出模式2.1 推挽输出2.2 开漏输出 3 输入模式3.1 高阻态(浮空)、…

CVE-2023-22515 Confluence未授权漏洞分析

0x01 漏洞描述 Confluence 是由 Atlassian 开发的企业级协作软件。2023年10月&#xff0c;Atlassian 官方披露 CVE-2023-22515 Atlassian Confluence Data Center & Server 权限提升漏洞。攻击者可构造恶意请求创建管理员&#xff0c;从而登录系统&#xff0c;造成敏感信息…

K8s实战RestartPoliy策略

一、默认策略为Always cmd.yaml apiVersion: v1 kind: Pod metadata:name: myapp-pod labels:app: myapp spec: containers:- name: myapp-container image: busyboxcommand: [sh, -c, echo OK!&& sleep 60]首先我们根据这个yaml创建一个测试的pod 执行命令 kubec…

GIT | 基础操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤销修改 | 删除文件

GIT | 基础操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤销修改 | 删除文件 文章目录 GIT | 基础操作 | 初始化 | 添加文件 | 修改文件 | 版本回退 | 撤销修改 | 删除文件前言一、安装git二、git基本操作2.1 初始化git2.2 配置局部生效2.3 配置全局生效 三、认识工作区…

【计算机网络笔记】路由算法之层次路由

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

路由的控制与转发原理

场景1&#xff1a;路由器收到数据包后&#xff0c;会根据数据包的目标IP地址&#xff0c;计算出目标网段&#xff0c;再确定终端设备的具体位置。这个过程中&#xff0c;还需要计算出接口&#xff0c;或数据包下一跳的地址。最终会生成一条路由&#xff0c;即路径&#xff0c;存…

2023亚太杯数学建模B题思路分析 - 玻璃温室中的微气候法规

1 赛题 问题B 玻璃温室中的微气候法规 温室作物的产量受到各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适 宜的温度和风速是植物生长[2]的关键。为了调节玻璃温室内的温度、风速等气候因素 , 温室的设计通常采用带有温室风扇的通风系统&#xf…

postgreSQL如何快速查询大表数据量

文章目录 场景方案结果 场景 我有一个非常大的表&#xff0c;估计几百万或者几千万。 我开始使用了 select count(*) from my_table_javapub 方式&#xff0c;查询非常慢。 如何解决&#xff1f;&#xff1f;&#xff1f; 方案 如果你需要更快地获取表中的行数&#xff0c…

重磅!这本30w人都在看的Python数据分析畅销书:更新了!

想学习python进行数据分析&#xff0c;这本《利用python进行数据分析》是绕不开的一本书。目前该书根据Python3.10已经更新到第三版。 Python 语言极具吸引力。自从 1991 年诞生以来&#xff0c;Python 如今已经成为最受欢迎的解释型编程语言。 pandas 诞生于2008年。它是由韦…

生成式AI与大语言模型,东软已经准备就绪

伴随着ChatGPT的火爆全球&#xff0c;数以百计的大语言模型也争先恐后地加入了这一战局&#xff0c;掀起了一场轰轰烈烈的“百模大战”。毋庸置疑的是&#xff0c;继方兴未艾的人工智能普及大潮之后&#xff0c;生成式AI与大语言模型正在全球开启新一轮生产力革新的科技浪潮。 …

『亚马逊云科技产品测评』活动征文|通过Lightsail搭建个人笔记

提示&#xff1a;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 前言实践知识储备Lightsail介绍Leanote介绍实践…

一套开源、强大且美观的WPF UI控件库 - HandyControl

前言 今天给大家推荐一套开源、强大且美观的WPF UI控件库&#xff1a;HandyControl。 WPF介绍 WPF 是一个强大的桌面应用程序框架&#xff0c;用于构建具有丰富用户界面的 Windows 应用。它提供了灵活的布局、数据绑定、样式和模板、动画效果等功能&#xff0c;让开发者可以创…

ASO优化之关于应用类别的选择

用户可以通过应用商店内的类别&#xff0c;来发现他们想要的新应用。接下来我们来一起了解如何为应用来选择最符合最准确的类别。 1、应用的类别所显示的区域&#xff1f; 我们可以为APP选择一个主要类别和一个次要类别&#xff0c;所选择的主要类别对于该应用在应用商店曝光度…

Linux 局域网传输工具LANDrop安装

Linux 局域网传输工具LANDrop安装 &#x1f959;下载&#x1f32d;解压&#x1f96a;运行 &#x1f959;下载 官网下载 或网盘 &#x1f32d;解压 使用以下命令解压获得squashfs-root文件夹 ./LANDrop-latest-linux.AppImage --appimage-extract&#x1f96a;运行 进入squ…

Selenium切换窗口、框架和弹出框window、ifame、alert

一、切换窗口 #获取打开的多个窗口句柄 windows driver.window_handles #切换到当前最新打开的窗口 driver.switch_to.window(windows[-1]) #最大化浏览器 driver.maximize_window() #刷新当前页面 driver.refresh() 二、切换框架frame 如存在以下网页&#xff1a; <htm…

Java面试-框架篇-Mybatis

Java面试-框架篇-Mybatis MyBatis执行流程延迟加载使用及原理一, 二级缓存来源 MyBatis执行流程 读取MyBatis配置文件: mybatis-config.xml加载运行环境和映射文件构造会话工厂SqlSessionFactory会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)操作数据库的接口, Ex…

C# Onnx 特征匹配 DeDoDe 检测,不描述---描述,不检测

目录 介绍 效果 模型信息 项目 代码 下载 介绍 github地址&#xff1a;https://github.com/Parskatt/DeDoDe DeDoDe &#x1f3b6; Detect, Dont Describe - Describe, Dont Detect, for Local Feature Matching The DeDoDe detector learns to detect 3D consisten…