所在位置:首页 > 手游攻略 > 俄罗斯方块的建模与建模研究(一)——方格当中

俄罗斯方块的建模与建模研究(一)——方格当中

发布时间:2022-08-16 16:04:04来源:网络整理浏览:122

本文涵盖了游戏背景搭建、积木建模、按键响应、随机生成积木和生成下一个小积木等内容,记录一个C语言练习。本项目涉及图形,可以使用EasyX库绘制简单图形。这个库的下载和使用可以在百度找到。

本项目使用的软件是2019,新建一个C++空项目编译代码。文件的项目结构如右图所示。一共三个文件,一个头文件,两个C++文件。之所以使用cpp后缀是因为EasyX库只能在C++文件中使用,工程代码全部用C语言编译。

​基本概念:

网格:指游戏区域内的小条纹,大小为20*20;

小方格:指日式小方格,由4*4共16个小碎花组成,大小为80*80;

游戏区域:背景为网格线作为游戏区域,大小为300*500,有25*15=375个方格;

图形区:图形区包括游戏区和文字显示部分,大小为500*500;

小方块坐标点:定义小方块左下角的坐标点为整个小方块的坐标点;

小方块的行号和列号:统计条带的个数,第一个条带的行号为0,列号为0;个数与坐标点的关系为:行数*20=坐标点的y,列数*20=坐标点的x; 二、构建游戏背景

//game.c
void context(void)
{
	initgraph(500, 500);					// 初始化图形模式
	setorigin(0, 500);						//重新定义原点
	setaspectratio(1, -1);					//将Y上半部分设定为正半轴
	setlinestyle(PS_SOLID);					//设定画线样式为虚线
	setlinecolor(WHITE);					//线条颜色为白色
	for (size_t i = 0; i <= 25; i++)			         //画出游戏区域网格线,宽300,高500
		line(0, 0 + 20 * i, 300, 0 + 20 * i);            //横线
	for (size_t i = 0; i <= 15; i++)
		line(0 + 20 * i, 0, 0 + 20 * i, 500);			 //竖线
	for (size_t i = 0; i <= 4; i++)						 //画出下一个方块网格线,宽80,高80
		line(370, 300 + 20 * i, 450, 300+20 * i);		 //横线
	for (size_t i = 0; i <= 4; i++)
		line(370 + 20 * i, 300, 370 + 20 * i, 380);		//竖线
	setaspectratio(1, 1);
	settextstyle(25, 0, _T("Consolas"));
	outtextxy(310, -430, _T("SCORE:0  points"));
	outtextxy(310, -410, _T("下一个方块:"));
	outtextxy(310, -270, _T("w 转换方向"));
	outtextxy(310, -240, _T("s 加快下降速度"));
	outtextxy(310, -210, _T("a 左移"));
	outtextxy(310, -0, _T("d 右移"));
	outtextxy(310, -150, _T("h 重启"));
	setaspectratio(1, -1);
}

首先对图形区域进行初始设置,设置线的样式为黄色实线;

用两个循环画出游乐区的横竖线,共25行15列,大小为300*500;

用两个循环画下一个区域的网格线,共4行4列,大小为80*80;

绘制提示文字,主要写分数和操作提示;

疗效如右图所示。

三、块建模

​研究日本小方块的各种形状,你会发现它们都是4*4的方块,考虑到C语言的短数据类型正好有2个字节和16bit的粗细。所以我们可以使用short数据类型来表示我们的小方块。比如下面的日文小方块可以表示为11000,换算成16的补码就是8888H。

​考虑到每个日本方格都有不同的方向,不同的方位对应不同的态度,我创建了一个二维链表来表示所有的德国小方格。一共有7个日式小方块,每个有4个方向的变化,有些变化还是和以前一样。在game.h头文件中声明显示日文小方块和位置结构,以小方块左下角点的坐标作为整个小方块的坐标点。

//main.c
unsigned short Diamond[7][4] = {
		  {0x000f,0x8888,0x000f,0x8888},
		  {0x008E,0x0c88,0x00E2,0x044c},
		  {0x002E,0x088c,0x00E8,0x0c44},
		  {0x00CC,0x00CC,0x00CC,0x00CC},
		  {0x006C,0x08c4,0x006c,0x08c4},
		  {0x004E,0x08c8,0x00E4,0x04c4},
		  {0x00C6,0x04c8,0x00c6,0x04c8} };
//game.h
//方块的坐标
typedef struct LOCATE
{
	int x;
	int y;
} Location;
//俄罗斯方块展示函数
void DisplayDiamond(unsigned short diamond, Location Loca, int cur_color);

日文小方块显示功能主要使用了EasyX上面的功能,可以画一个带边框的实心圆。

以下是日文小方块显示功能的具体代码。判断短数据中的每一位是否为1。如果是,则填充一朵小花。辨析。这个函数也是用来擦除的,只是颜色变成了背景色(红色),变相达到了擦除的效果。

int color[15][35] = { 0 };  //定义网格属性
//俄罗斯方块展示函数
void DisplayDiamond(unsigned short diamond, Location Loca, int draw_color)
{
	int num_x, num_y;
	int k = 0;
		setfillcolor(draw_color);	    //设定填充颜色		
		for (int i = 3; i >= 0; i--)    //四行填充
		{
			for (int j = 0; j < 4; j++) //四列填充
			{
				if (diamond & (0x8000 >> k))//判断格子是否存在1,20是格子边长
				{
					fillrectangle(Loca.x + 20 * j, Loca.y + 20 * i, Loca.x + 20 * (j + 1), Loca.y + 20 * (i + 1));
					num_x = Loca.x / 20 + j;
					num_y = Loca.y / 20 + i;
					color[num_x][num_y] = draw_color;
				 }	
				k++;
			}
		}
}

设置填充颜色,这是EasyX的外部函数,直接引用即可;双循环,填写四行四列;函数内部判断,对于每一个短数据类型(小日本方块)用一位来判别,如果为1则填充,如果为0则跳过; color是一个二维链表,用来记录条带对应的颜色,一共25行15列,对应条带的个数。这个变量是一个全局变量,所以game.c文件中的所有函数都可以引用或参数化这个变量。显示所有小印度方块并验证上述建模是否正确。从右图可以看出,形状是正确的,符合我们的设想。

​四、关键回应

连接和翻转需要鼠标选择,所以这涉及到与按钮的交互,所以需要包含头文件conio.h。 conio.h不是C标准库中的头文件,而是vc下的头文件。 conio是Input/(控制台输入输出)的缩写,定义了通过控制台进行数据输入和数据输出的功能,主要是一些用户通过按键形成的相应操作,如getch()函数等.

1.实现小方块自由落体

//main.c
while (MoveEnable(CurDiamond, CurLocation, EnableDown) == 0)
		{
			DisplayDiamond(CurDiamond, CurLocation, BLACK);      //先擦除
			CurLocation.y -= 20;
			DisplayDiamond(CurDiamond, CurLocation, cur_color);  //再更新
			Sleep(speed);
			if (_kbhit())					//如果敲击了键盘,就会返回1
			{
				userHit = _getch();			//获取敲击键盘字符
				Keyboard(userHit, &row, &column, &CurLocation, &speed);
				CurDiamond = Diamond[row][column];
			}
			
		}

//game.h
#define EnableDown 1
#define EnableLeft 2
#define EnableRight 3
//game.c
bool MoveEnable(unsigned short diamond, Location CurLocation, int direction)
{
	bool stop=0;
	int num_x,num_y;
	switch (direction)
	{
	case EnableDown:    //判断是否可以下移
	{
		for (int i = 0; i < GetWidth(diamond); i++)		//对每一列进行判断
		{
			for (int j = 0; j < GetHigh(diamond); j++)   //对该列每一行进行判断
			{
				num_x = CurLocation.x / 20 + i ;		 //小方格的列序号
				num_y = CurLocation.y / 20 + j ;		 //小方格的行序号
				if( diamond & (0x0001<<(3-i+4*j)) )		 
                //如果该小方格是为1,即方块该列最底部
				{ 
					if ((color[num_x][num_y - 1] != BLACK) || (CurLocation.y == 0))	
                    //如果下面那个小方格也是非黑色,就要停止
					{
						stop = 1;
					}
					break;	        //找到底部小格子即可跳出循环,进行下一列的判断
				}	
			}
			if (stop == 1)  break;	//既然已经要停止了,就不用去判断下一列
		}
		break;
	}
	}
	return stop;
}

以上是字符处理函数。上面只判断部分代码是否可以下移,代码是否可以左右移动类似,这里就不展示了。

swith语句选择是判断增长,左移,右移;生长判定,虽然是找出每一列最下面的那一行彩色小花,并在找到它下面的条纹后判断,如果下面的条纹也是彩色小花,则说明它不能生长;如果下面的条纹是红色的小花,它可以生长;然后拿出来,找到第二列最上面的小格子,因为有可能第一列没有被挡住,第二列被挡住了。当其中一列下有彩色条纹时填充方块,可以将stop变量设置为1,然后不判断下一列就跳出循环。函数返回bool数据类型,停止这个变量。如果为1,表示有阻塞,不能再连接。如果为0,则表示没有障碍物,可以继续连接。以上是函数的逻辑。

实现小方块的变化方向和速度

//game.c
//响应键盘函数

填充方块_cad方块填充_我的世界快速填充方块

void Keyboard(char userHit, int *row, int *column, Location *CurLocation, int *speed) { unsigned short Diamond[7][4] = { {0x000f,0x8888,0x000f,0x8888}, {0x008E,0x0c88,0x00E2,0x044c}, {0x002E,0x088c,0x00E8,0x0c44}, {0x00CC,0x00CC,0x00CC,0x00CC}, {0x006C,0x08c4,0x006c,0x08c4}, {0x004E,0x08c8,0x00E4,0x04c4}, {0x00C6,0x04c8,0x00c6,0x04c8} }; unsigned short CurDiamond; switch(userHit) { case 'w': //改变方向 { CurDiamond = Diamond[*row][*column]; DisplayDiamond(CurDiamond, *CurLocation, BLACK); //先擦除 if ((*column) < 3) (*column)++; else (*column) = 0; CurDiamond = Diamond[*row][*column]; DisplayDiamond(CurDiamond, *CurLocation, cur_color); //再更新 break; } case 's': //增加下降速度 { if ((*speed) > 50 ) { (*speed) -= 50; } break; } case 'h': //重新开始 { for (size_t i =0; i < 25; i++) //25行 { for (size_t j = 0; j < 15; j++) //15列 { color[j][i] = BLACK; //每一个格子填充黑色 setfillcolor(color[j][i]); fillrectangle(20 * j, 20 * i, 20 * (j + 1), 20 * (i + 1)); } } break; } } }

以上是响应鼠标的函数。首先对传入的字符进行判断,最后要进行左移、右移、加快滑动速度、改变方向、重启操作。由于我们的日本小方块模型是一个7*4的二维链表,对应7种印度小方块的四个方向。因此,虽然改变方向就是改变的列号,但是只要从0到4循环,就可以达到这些疗效。由于行和列都需要保存并传出函数,所以这里使用指针。

通过影响中间的区间来改变速率,只要区间短,增长速率就快。所以每按一次s,速度变量减少50,速度变快。同理,函数外也传速度,也用针。按'h'是重新开始,也就是清除掉下来的日本小方块填充方块,所以只要把游戏区的所有条纹都填上红色,就可以达到治疗效果,而且属性颜色条纹必须同时更换。

左右连线和小方块的下落一样,只是x变了。 x 每变化 20,就是一个条带的宽度。先擦除,再重新更新小方块,即可达到预期效果。

五、随机生成块和生成下一个小块

//main.c
	unsigned short CurDiamond; //下一个方块
	unsigned short NewDiamond; //下一个方块
	
	context();								//画出游戏背景
	row = 0;
	column = 0;
	CurDiamond = Diamond[row][column];
	cur_color = BLUE;
	while(1)
	{ 		
		RandomDiamond(&new_row, &new_column, &CurLocation, &speed); //随机生成方块
		NewDiamond = Diamond[new_row][new_column];
		DisplayDiamond(NewDiamond, { 370,300 }, new_color);  //画出下一块方块
		while (MoveEnable(CurDiamond, CurLocation, EnableDown) == 0)
		{
			//自由下落+按键响应代码
			
		}
		row = new_row;
		column = new_column;
		cur_color = new_color;
		CurDiamond = Diamond[row][column];
		DisplayDiamond(NewDiamond, { 370,300 }, BLACK);    //先擦除
		FullJudge();
	}

小方块的随机生成使用自定义函数,在链表的行列中生成小方块,给它们赋值,然后在下一个小方块区域绘制。 while 部分中的小块自由落体和键盘响应在不能落下时会跳出 while 循环。将先前随机生成的新行号和列号分配给当前行总和,并分配生成的颜色。之后就可以擦除下一个区域的小方块,等待新的生成。

最后会进行全线判定,全线会消失。接下来我会讲到这个。

以上是日本小方块的随机生成函数。一开始会产生随机的行和,相当于随机的日本小方块的形状。同时,颜色也会生成。颜色使用了链表的内部循环,所以生成了五颜六色的日式小方块。

六、全行清理和更新

//game.c
void FullJudge()
{
	int color_num=0;
	for (size_t i = 0; i < 25; i++)    //25行
	{
		for (size_t j = 0; j < 15; j++) //15列
			if (color[j][i] != BLACK) color_num++;		
		if (color_num == 15)			//遇到满行
		{
			setfillcolor(BLACK);		     //设定填充黑色
			for (size_t j = 0; j < 15; j++)  //填充该行15列,即消除该行		
			{ 
				fillrectangle(j*20, i*20 ,(j+1)*20, (i+1)*20);
				color[j][i] = BLACK;
			}
			Updata(i);//更新
			i--;    //重新判断该行
		}
		color_num = 0;
	}
}
//消除满行后,上面往后降,重新判断该行
void Updata(int row)  
{
	//从满行的第row行开始
	for (size_t i = row; i < 25; i++)
	{
		for (size_t j = 0; j < 15; j++)		//15列
		{
			color[j][i] = color[j][i + 1]; //每一个格子等于上面格子的颜色
			setfillcolor(color[j][i]);
			fillrectangle( 20 * j, 20 * i, 20 * (j + 1), 20 * (i + 1));
		}	
	}
	static int game_point = 0;
	game_point++;   //更新分数
	setaspectratio(1, 1);
	TCHAR s1[5];
	settextstyle(25, 0, _T("Consolas"));
	swprintf_s(s1, _T("%d"), game_point);
	outtextxy(380, -426, s1);
	setaspectratio(1, -1);
}

全行判断是逐行判断。首先判断第一行,判断每条条纹的颜色。如果统计条带颜色为非蓝色(即有小方块),列数为15,即遇到满行情况。如果确定是整行,则将该行的所有条纹都用红色填充,相当于去掉了。

去掉后,里面的条纹应该是掉下来的,相当于把整个往下移。这是函数的任务,必须更新分数,每行一行。整条线下移后,需要重新判断这条线。事实上,掉下来的线路可能已经满了。循环判断25行后,任务完成。

七、判断真长真高

//game.c
//计算方块的真实高度
int GetHigh(unsigned short diamond)
{
	int a=0;
	for (size_t i = 0; i < 4; i++)
	{
		if ( (diamond & (0x000f << 4 * i)) > 0)
			a++;
	}
	return a;
}
//计算方块的真实宽度
int GetWidth(unsigned short diamond)
{
	int a = 0;
	for (size_t i = 0; i < 4; i++)
	{
		if ((diamond & (0x8888 >>  i)) > 0)
			a++;
	}
	return a;
}

从下面的美式小方格可以看出,即使每个日式小方格占据4*4小花的空间大小,其实日式小方格的真实高度和长度也不一定是4。连接左右,还需要判断日本小方块的真实高度和长度,即长度为1,高度为4。判断的逻辑是求和,逻辑运算,然后是否小于0。如果等于0,则表示该行/列全为0,即未占用的空间。

以上是日文小方块的大部分代码,不是全部的代码,有的只讲逻辑,大家可以自由发挥。

八、跑步效果