这个作业属于哪个课程 | 面向对象程序设计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.还没能进行窗口的美化,感觉还是都点丑。