我罗斯方块最终汇报

这个作业属于哪个课程 面向对象程序设计2020
这个作业的要求在哪里 我罗斯方块
这个作业的目的 我罗斯方块最终汇报
作业正文 我罗斯方块
Github地址 我罗斯方块
其他参考文献 Windows程序设计
小组成员 041901328 王真平

游戏运行示意


视频示意

代码要点

窗口的创建

对于制作我罗斯方块来说,第一点也是最重要的一点就是得先创建出用于游戏界面。
我采用的是win32编程来创建窗口。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPTSTR lpCmdLine, int nCmdshow)
{

	//初始化窗口类
	WNDCLASSEX wc;
	HWND hwnd;
	MSG msg;//消息结构体
	wc.cbClsExtra = 0;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wc.hCursor = NULL;
	wc.hIcon = NULL;
	wc.hIconSm = NULL;
	wc.hInstance = hInstance;
	wc.lpfnWndProc = Myluosi;
	wc.lpszClassName = "wls";
	wc.lpszMenuName = NULL;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	//注册窗口类对象
	if (RegisterClassEx(&wc) == 0)
	{
		return 0;
	}
	//创建窗口
	hwnd = CreateWindowEx(WS_EX_TOPMOST, "wls", "我罗斯方块", WS_OVERLAPPEDWINDOW, 100, 30, 900, 650, NULL, NULL, hInstance, NULL);
	if (NULL == hwnd)		//窗口句柄
	{
		return 0;
	}
	//显示窗口
	ShowWindow(hwnd, SW_SHOWNORMAL);
	//消息循环
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);//翻译消息
		DispatchMessage(&msg);//分发消息
	}
	return 0;
}

而实现游戏功能的部分则在放在回调函数中

LRESULT CALLBACK Myluosi(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT pt;
	HDC hdc;
	switch (nMsg)
	{
            //初始化创建窗口
	case WM_CREATE:
		break;
            //计时器
	case WM_TIMER:
		break;
            //绘制窗口
	case WM_PAINT:
		break;
             //键盘消息
	case WM_KEYDOWN:		
		break;
            //关闭窗口
	case WM_DESTROY:		
		break;
	}
	return DefWindowProc(hwnd, nMsg, wParam, lParam);
}

方块变形

  这个模块是比较难处理的,我刚开始的想法是罗列出所有可能的情况,但是这个方法过于简单粗暴,而且不易实现。

在看了许多视频和文章后,我找到了一种简单的算法。
对于以下八种方块:

前五种可使用同一种变形方法,正方形方块不需要变形,而长条形方块用另一种方法。
做出这种选择的原因是前五种方块都可以放在一个3×3的数组里进行变形,适合同一种算法,而长条状显然不适合该算法,需要另行实现。
对于前五种方块变形时,
先把背景数组里的方块复制出来,要实现这一步,首先要在创建方块时,将不同种的方块编写一个ID,使其在变形时,可以根据方块的ID选择对应的变形方法。其次还需要再创建方块时,连带记录改方块的坐标信息,以便于变形。
对于该算法的原理,我找到了下面这张图:

从图中我们可以发现以下规律:
对应于变形前的数组坐标
二维数组每一横行的纵坐标不变,横坐标依次递减。
二维数组每一纵行的横坐标不变,纵坐标依次递增
根据这个规律,第一种算法的思路就一目了然了,根据方块的坐标,从背景数组里复制出待变形的方块,再通过遍历赋值的方法,将变形后的方块直接复制到背景上。

int arrsquare[3][3] = { 0 };
	int i, j;
	int temp = 2;
	//背景块复制
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			arrsquare[i][j] = arr_background[y_square + i][x_square + j];
		}
	}
	//变形复制回去
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			arr_background[y_square + i][x_square + j] = arrsquare[temp][i];
			temp--;
		}
		temp = 2;
	}

对于长条方块变形时:先判断其横向还是纵向,然后按照下图所表示的基准点,先将背景数组中原数组清空,再将变形后的长条直接复制回背景数组完成变形。
这是第一种变形的基准点,对应一般情况

第二种变形的基准点,对应特殊情况:

当然再复制回去时要考虑多种情况,就是横变纵或者纵变横时,是否可以直接按照基准点进行复制回去。所以对于变形,有以下代码对不同情况进行判断赋值:

if (arr_background[y_square][x_square-1]==1)//横着
	{
		//清零
		arr_background[y_square][x_square - 1] = 0;
		arr_background[y_square][x_square + 1] = 0;
		arr_background[y_square][x_square + 2] = 0;
		if (arr_background[y_square + 1][x_square]==2)
		{
			arr_background[y_square - 1][x_square] = 1;
			arr_background[y_square - 2][x_square] = 1;
			arr_background[y_square - 3][x_square] = 1;
		}
		else if (arr_background[y_square + 2][x_square] == 2)
		{
			arr_background[y_square - 1][x_square] = 1;
			arr_background[y_square + 1][x_square] = 1;
			arr_background[y_square - 2][x_square] = 1;
		}
		else
		{
			//赋值
			arr_background[y_square - 1][x_square] = 1;
			arr_background[y_square + 1][x_square] = 1;
			arr_background[y_square + 2][x_square] = 1;
		}
	}
	else //竖着
	{
		arr_background[y_square - 1][x_square ] = 0;
		arr_background[y_square + 1][x_square ] = 0;
		arr_background[y_square + 2][x_square ] = 0;
		if (arr_background[y_square][x_square + 1] == 2||x_square == 9)
		{
			arr_background[y_square][x_square - 1] = 1;
			arr_background[y_square][x_square - 2] = 1;
			arr_background[y_square][x_square - 3] = 1;
			//改变标记
			x_square -= 2;
		}
		else if (arr_background[y_square][x_square + 2] == 2||x_square == 8 )
		{
			arr_background[y_square][x_square - 1] = 1;
			arr_background[y_square][x_square - 2] = 1;
			arr_background[y_square][x_square + 1] = 1;
			//改变标记
			x_square -= 1;
		}
		else if (arr_background[y_square][x_square - 1] == 2|| x_square == 0)
		{
			arr_background[y_square][x_square + 1] = 1;
			arr_background[y_square][x_square + 2] = 1;
			arr_background[y_square][x_square + 3] = 1;
			//改变标记
			x_square +=1 ;
		}
		
		else
		{
			arr_background[y_square][x_square - 1] = 1;
			arr_background[y_square][x_square + 2] = 1;
			arr_background[y_square][x_square + 1] = 1;
		}


	}

增行消行

这个部分是我自己出bug最多的地方,曾经使游戏数次崩溃出错。
对于消行函数还是比较容易判断的,唯一一个可能出bug的点我觉得是一次消多行的情况。我对于这给消行的处理是:从下往上,遍历背景数组,判断每一行数组相加之和是否等于20(对于落下的方块,已经赋值为2),如果等于20,就可以从此行开始,将其上的元素赋值给它的下一行。从而实现消一行。为了解决消多行的问题,我采取了一个简单粗暴的方法,每次消一行后,再次从最底部开始遍历,判断是否有满行存在。

int i, j;
		int sum = 0;
		int temp = 0;
		for (i = 19; i >= 0; i--)
		{
			for (j = 0; j < 10; j++)
			{
				sum += arr_background1[i][j];
			}
			if (sum == 20)
			{
				//消除一行
				AddLine_P2();
				for (temp = i - 1; temp >= 0; temp--)
				{
					for (j = 0; j < 10; j++)
					{
						arr_background1[temp + 1][j] = arr_background1[temp][j];
					}
				}
				p.score1++;
				i = 20;
				//i+=1;//第二种思路
			}
			sum = 0;
		}

在消行代码里,还有一个增行函数,对于这个增行函数,其实不是太难实现,就是需要理解一些特殊情况下的赋值问题。
1.从上向下,将每一个元素的值赋给它下一个时,需要注意第一行不可以给它上一行赋值,否则会出现访问越界的问题,第一行的值会付给最后一行,导致出错。
2.存在一种特殊就是当玩家一刚好增行时,他的方块刚刚在第一行生成,存在一个将其方块覆盖的问题。要解决此问题,需要一个判断函数,在整体向上移动时,判断第一行是否有新生成的方块存在,如果有,那么前三行就不参与向上移动来保证新生成的方块不会被覆盖掉。而对于一般情况,下落中的方块要也要向上移动,来保证不会被已经固定的方块所覆盖(其方块纵坐标也要相应上移),代码如下

	bool Can_MoveUp_1()
	{
		bool a=true;
			for (int j = 0; j < 10; j++)
			{
				if (arr_background1[0][j] == 1)
				{
					a= false;
					break;
				}
			}
			if (!a)
			{
				return false;
			}
			else
			{
				return true;
			}
	}

void AddLine_P1()
	{
		if (Can_MoveUp_1())
		{
			Square s;
			s.P1_y_square--;
			for (int i = 1; i < 20; i++)
			{
				for (int j = 0; j < 10; j++)
				{
						arr_background1[i - 1][j] = arr_background1[i][j];
				}
			}
		}
		else 
		{
			for (int i = 3; i < 20; i++)
			{
				for (int j = 0; j < 10; j++)
				{
					arr_background1[i - 1][j] = arr_background1[i][j];
				}
			}
		}
		for (int i = 0; i < 10; i++)
		{
				int n = rand() % 2;
				if (n == 0) arr_background1[19][i] = 0;
				if (n == 1) arr_background1[19][i] = 2;
		}
		
	}

收获与心得

      这次的大作业确实很不容易,因为以前没有接触任何与窗口显示相关的知识,为了创建出这个窗口找了好多资料和视频
真的很不容易,不过也正因如此,我学习了很多关于win32编程的知识,学会了如何创建一个窗口,和用它来显示内容。其次,
如此大的码量不仅锻炼了我的打字速度,还让我熟练了VS2019的用法,发现了很多新的功能。最后一点,我认为通过这次的项目,
锻炼了我找bug和分析问题的能力。

仍存在的问题

    1.没有找到队友。我认为这是我在这次大作业里存在最大的问题。缺乏了与队友沟通的环节,所有问题都是在单打独斗
这一点确实是很糟糕的一点。而且也缺少了对使用Git的练习。这个问题的责任在我,没有勇气去和其他同学沟通,错过了组队
的机会导致只能一个人做。
      2.代码组织的不好。整个项目都是在一个cpp文件里完成,我认为这个也是很不好的习惯,当进度一半多以后,整个行数
过多,调试和修改都很不方便。
      3.虽然本次使用了C++进行编程,我觉得自己的代码仍然不能称作面向对象编程。感觉除了使用了类以外,不太符合面向
对象的要求。面向对象的方法使用不到位,只是用了类来组织函数和方法,缺少了对继承以及其他特性的使用。
      4.存在当一名玩家长按某一方向操作键时,会影响另一玩家的相同操作的bug。暂时无法解决,只能禁止在游戏中长按按键。
      5.还未实现游戏结束后后开始新游戏这一功能,只能关闭窗口再重新打开。
      6.可能存在其他隐藏的bug,在测试时没有发现(在消行的时候)。
      7.还没能进行窗口的美化,感觉还是都点丑。
posted @ 2020-06-13 15:50  与谁  阅读(288)  评论(2编辑  收藏  举报