一、实验实习目的及要求
加强对C++语言的深入理解,达到提高学生分析问题,解决综合问题的能力。要求:分析俄罗斯方块游戏的类及层次图;实现俄罗斯方块游戏的基本功能;实现界面:Windows界面,利用对话框应用程序形式。
二、实验实习设备(环境)及要求(软硬件条件)
设备:PC机
操作系统:Windows
编译软件:Visual Studio 2019
三、实验实习内容简介:
系统应实现的主要功能:
游戏方块移动,游戏方块的碰撞检测,游戏方块旋转,满行消除,分数以及等级计算,开始、结束以及重新开始按钮,游戏方块预览功能。
俄罗斯方块游戏的主要执行过程如下图:
界面设计及绘制:
类图:
各模块的具体功能和简单算法:
1、小方块
每个俄罗斯方块由四个小方块组成。定义小方块类,实现绘制,移动,擦除功能。
2、小方块组合
定义小方块组合即俄罗斯方块类,实现俄罗斯方块的绘制,移动,擦除功能。
3、定时机制
建立计时器后,根据参数定义的时间步长触发定时处理时间,直到杀死计时器为止。通过计时器实现,每隔固定时间,检测方块状态以及方块下落,预览下一方块。当游戏结束或暂停时杀死计时器。
4、绘图实现
游戏开始时初始化界面,绘制俄罗斯方块的网状图、分数以及等级显示。
5、边界判断
通过键盘响应消息处理函数,响应键盘操作,判断俄罗斯方块是否与边界或者下方方块碰撞。
6、方块旋转功能
将小方块组合的位置基点作为旋转中心点,通过旋转各个小方块中心位置到小方块组合的位置基点的x与y边实现俄罗斯方块的旋转。
7、满行消除功能
将俄罗斯方块运动的主要界面看作一个二维数组,存储颜色。当某行满时,消除该行的方块,并且将该行上方的方块颜色向下移一行。
8、方块预览功能
生成的方块存储在数组上,在第一次生成方块时,生成两个方块,第一个方块显示在俄罗斯方块运动界面上,第二个方块预览。后面时运动方块的倒数第二个俄罗斯方块,预览倒数第一个方块。
四、源程序(代码)
1、定时机制
void CMFCADlg::OnTimer(UINT_PTR nIDEvent)
{
//UpdateData(TRUE);
//CDC* pDC = GetDlgItem(IDC_GRID)->GetWindowDC();
CDC *pDC = this->GetDC();//获取设备上下文对象
CCombine C;
if (combines.size() == 0 )
{
C=CreateCombine(pDC);
C.CCombine::Draw(pDC);
CreateCombine(pDC);
NextCombine();
Gameon = true;
}
vector<CCombine>::iterator p = combines.end() - 2;//获取方块组合最后一个对象
if (isUnder() == false && Gameon == true)
{
p->CCombine::Draw(pDC);
p->CCombine::Wipe(pDC);
p->CCombine::Move(pDC, 0, 40);
}
else if (Gameon == true )
{
int game = true;
//UpdateTop(pDC);
for (unsigned int i = 0; i < p->squares.size(); i++)//普通遍历
{
//游戏结束
if (p->squares[i].center.y == 20)
{
CString str;
str = TEXT("sorry, Game is over,Please try again");
Gameover();
if (MessageBox(str, TEXT("INFO"), MB_ICONINFORMATION) == IDOK) {
OnBnClickedButton1();
}
game = false;
break;
}
fflag[(p->squares[i].center.y) / 40][(p->squares[i].center.x) / 40] = p->squares[i].color;
}
if (game)
{
p->CCombine::Wipe(pDC);
p->CCombine::Draw(pDC);
LineClear(pDC);
CreateCombine(pDC);
C = combines[combines.size() - 2];
C.CCombine::Draw(pDC);
NextCombine();
}
}
//ReleaseDC(pDC);
CDialogEx::OnTimer(nIDEvent);
}
2、方块预览功能
}
void CMFCADlg::NextCombine()
{
CDC* pDC = this->GetDC();//获取设备上下文对象
COLORREF color;
color = RGB(245, 245, 245);
CBrush newBrush; // 创建的新画刷
CBrush* pOldBrush; // 旧画刷的指针
newBrush.CreateSolidBrush(color);
pOldBrush = pDC->SelectObject(&newBrush);
CRect rect(600, 10,600+240,200 );
pDC->FillRect(&rect, &newBrush);//CDC::FillSolidRect
// 恢复旧画刷
pDC->SelectObject(pOldBrush);
// 删除新画刷
newBrush.DeleteObject();
CCombine temp;
temp = combines[combines.size() - 1];
temp.Move(pDC, 490, 50);
temp.Draw(pDC);
}
3、方块旋转功能
int CMFCADlg::RotateCombines(CDC* pDC)
{
// TODO: 在此处添加实现代码.
vector::iterator p = combines.end() - 2;//获取焦点对象
//x0 = (x - rx0)*cos(a) - (y - ry0)*sin(a) + rx0 ;
//y0 = (x - rx0)*sin(a) + (y - ry0)*cos(a) + ry0;
if (p->pattern != 1 )
{
int preX[10], preY[10], x, y;
int rx = p->location.x;
int ry = p->location.y;
int count = 0;
p->Wipe(pDC);
//遍历计算
bool flag_boundary = false;//判断是否出界,是->不旋转
for (unsigned i = 0; i < p->squares.size(); i++)
{
x = p->squares[i].center.x;
y = p->squares[i].center.y;
//绕location旋转
preX[i] = (x - rx) * cos(-PI * 90 / 180) - (y - ry) * sin(-PI * 90 / 180) + rx;
preY[i] = (x - rx) * sin(-PI * 90 / 180) + (y - ry) * cos(-PI * 90 / 180) + ry;
if (fflag[(preY[i] / 40)][(preX[i] / 40)] == RGB(255, 255, 255))
{
count++;
}
if ((preY[i] > 720) || (preX[i] > 560) || (preY[i] < 20) || (preX[i] < 20))
{
flag_boundary = true;
}
//消除误差,即归位
preX[i] = (preX[i] / 40) * 40 + 20;
preY[i] = (preY[i] / 40) * 40 + 20;
//赋值
}
//如果可以旋转,那么赋值
if (count == p->squares.size() && flag_boundary == false)
{
for (unsigned i = 0; i < p->squares.size(); i++)
{
p->squares[i].center.x = preX[i];
p->squares[i].center.y = preY[i];
}
}
}
return 0;
}
4、满行消除功能
void CMFCADlg::LineClear(CDC* pDC)//满行消除
{
vector::iterator p = combines.end() - 2;//获取方块组合最后一个对象
COLORREF color;
for (unsigned int i = 0; i < p->squares.size(); i++)
{
//遍历方块所在行
bool flag_fill = true;
COLORREF firstColor = RGB(255,255,255);
for (int j = 20; j < 560; j += 40)
{
color = pDC->GetPixel(j, p->squares[i].center.y);
//如果有颜色不相同的方块,结束
if (color == RGB(255,255,255)||0)
{
flag_fill = false;
break;
}
}
//如果满行,进行消除(当前方块所在行)
if (flag_fill == true)
{
DropPart(p->squares[i].center.y);
}
}
score += 50;
if (score % 200 == 0)
level += 1;
CString str;
str.Format(_T("%d"), score);
Edit_score.SetWindowTextW(str);
str.Format(_T("%d"), level);
Edit_level.SetWindowTextW(str);
}
int CMFCADlg::DropPart(int line)
{
CDC* pDC = this->GetDC();
CRect rect;
int flag=false;
COLORREF color;
for (int j = line; j > 0; j-=40)
{
for (int i = 20; i < 560; i+=40)
{
if (j - 40 > 0)
{
color = pDC->GetPixel(i, j - 40);
if (color != RGB(255, 255, 255) || 0)
flag = true;
rect = CRect(i - 20, j - 20, i + 20, j + 20);
pDC->FillSolidRect(&rect, color);
fflag[j/40][i/40] = color;
flag = true;
//为矩形画上黑色边框
CPen NewPen, * oldPen;
NewPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
oldPen = pDC->SelectObject(&NewPen);
pDC->MoveTo(i - 20, j - 20);
pDC->LineTo(i + 20, j - 20);
pDC->LineTo(i + 20, j + 20);
pDC->LineTo(i - 20, j + 20);
pDC->LineTo(i - 20, j - 20);
pDC->SelectObject(oldPen);
}
}
if (!flag)
return 0;
}
return 0;
}
五、源程序调试过程(注:该项是实验报告成绩评定的主要评分依据之一,越详细越好,语法错误与逻辑错误都要写出来)
1、cannot seek value-initialized vector iterator
是因为vector中没有元素却要去取元素,导致的错误。逻辑失误
2、
函数名前面没有加类名
3、
ontimer的后面如果调用自己会发生溢出错误
CDialogEx::OnTimer(nIDEvent);
4、
如果在多个继承指向成员的指针之间进行强制转换,则可能会发生 C4407。 有时这可以正常工作,但有时不能这样做,因为单个继承指针到成员的表示形式不包含足够的信息。 用 /vmm进行编译可能会有所帮助 你还可以尝试重新排列基类;编译器检测到转换中的信息丢失,因为基类与派生的非零偏移量。
删掉virtual后就没有问题
5、
在生成方块时,在一个模式下漏了squares.push_back()四个方块,导致运行游戏时squares为空而无法运行
6、
在键盘处理消息中,如果不加这一个语句会导致键盘按下一次响应了两次,比如导致游戏按下一次右键会往右边移动两格
if (pMsg->message == WM_KEYDOWN) //捕捉到键盘
7、
游戏结束时,消息对话框一闪一闪的,因为少了个break,而且执行了之后,ontimer继续执行
五、实验实习结果及分析(程序运行结果截图要求测试用例尽量全面)
移动
旋转
消行
游戏结束
方块移动、方块旋转、消行、暂停按钮、开始按钮、重新开始按钮、得分情况、预览情况、游戏结束均通过测试。
七、总结:(通过该项目,你在设计和具体实现环节都学到了什么?是否达到预期结果?有什么不足等?)
在俄罗斯方块项目中,我采用面向对象的方式设计小方块类(Square.h)以及小方块的组合类(Combine.h),采用面向过程的方式编写主要的执行类(MFCADlg.h)。小方块类以及小方块的组合类主要以数据为中心,主要的执行类主要以功能为中心。画出了主要的执行类的实现逻辑图。对类进行设计后,画出了三个类的图。总体采用了瀑布式的开发方式,从上至下开发,以各个功能为模块进行开发以及测试,主要实现每个函数模块执行一个功能,尽量实现高内聚和低耦合的设计思想。在主要的执行类的开发中,先将面板设计图实现,然后依次开发实现小方块组合的初始化,小方块组合的移动以及移动的限制条件,小方块的旋转,消行功能,方块预览功能,开始、暂停、结束按钮的实现,分数和等级的设置。在各个模块的开发中,边测试,边开发。