资源描述
课 程 设 计 报 告
课程名称:面向对象程序设计C++
设计题目:连连看游戏
专 业:计算机科学与技术
姓 名:
学 号:
指导教师:李 晓 虹
2016 年 1 月 1 日
1、 系统需求分析
本程序是一款连连看游戏,该程序应该具备以下功能:
(1) 该游戏界面为黑色背景加方格图片类型,不同的图片上有不同的图案,位置为随机打乱。
(2) 游戏规则为将相同图案的图片进行连接,但需满足只能至少单边无阻呈直线连接,否则无效。一旦成功连接两个相同的图案,则这两个图案消失,如此反复直到所有的图片消失,游戏通关。
(3) 图片的位置具有随机性,但要求都是必须成对出现。
1.1 功能需求
(1)主界面需求
显示玩家当前关卡数及得分数
提供“新游戏”按钮,单击后可以重新开始游戏
提供“退出”按钮,单击后可以退出程序
提供“帮助”按钮,单击后可以弹出制作人信息,联系方式等
(2)消除需求
玩家点击图案,图案会变色以提示被选中;此时再点击另一个图案,如果图案相同,则这两个图案消失;此外,两个图案中间必须无其他图片阻碍才有效,消除规则如下:
一条 直线相连:
图1-1
两条直线相连:
图1-2
三条直线相连:
图1-3
(3)新游戏需求
当玩家点击新游戏按钮时,游戏将重新生成新的图案并重排,且重排的方式是随机的。
(4)帮助
当玩家点击帮助时,将弹出制作者信息以及秘籍。
(5)退出
点击退出按钮,将退出程序。
1.2时间性能需求
响应速度:3秒以内
客户端:可执行文件,点开即可运行,方便快捷。
1.3界面友好需求
系统提供统一的界面操作方式。要求界面操作方式美观大方,布局合理,功能完善,整个操作简单,对于初级用户容易上手。良好的操作界面可以给游戏玩家带来更好地游戏体验和愉悦的心情。复杂的游戏界面会造成玩家的负担,所以在设计程序时,功能以及各种操作都应当尽量简单明了。
1.4系统可用性需求
系统操作快捷,内容完整是保证用户进行使用的基础。因此,应准确而详细的理解各用户区的特征,任务和使用环境,在“有效性”、“效率”等各个方面满足各类用户对系统的需要。
2、 系统总体设计
2.1总体系统图
此系统是运用VC++6.0中MFC框架的多文档视图开发的,文档--视图结构提供的应用程序接口有利于进行窗口操作编程。此游戏引擎包含在视图类中。有时引擎和视图是一体的,并且可能无可避免,因为视图需要为引擎处理用户输入和绘图。
游戏程序设计一般都采用面向对象的设计模式,WindowsAPI提供了一个简单的图像显示接口,利用MFC的多文档视图结构的面向对象设计模式。此系统的总体系统图如下:
连连看游戏
游戏玩家
界面显示
菜单功能模块
图像处理模块
鼠标操作命令
Windows操作系统
Vc++6.0平台
API接口和c++
图2-1 系统功能图
2.2 功能模块总体设计
(1)鼠标操作模块
玩家在玩游戏时需要运用鼠标与游戏系统进行交互实现是运用MFC中的鼠标事件触发的.鼠标进行交互的事件主要有两个,一个是用户点击游戏场景中的图片时触发的事件。另外一种是用户点击菜单栏上的菜单按钮时说触发的鼠标事件。
当用户点击游戏场景中的图像块时就会响应鼠标的OnLButtonDown事件。
当用户点击菜单栏上的菜单命令时就会触发相应的菜单命令对应的相应的事件。
(2)图像处理模块
程序运行后,用户通过游戏菜单的新游戏命令开始进入游戏,在这个模块里要把资源文件中的图片进行处理然后加载到游戏场景中程序中是通过DrawMap()方法实现的。其中用到的API函数主要是是BitBlt,他的主要功能是将某一内存块的数据传送到另一内存块。
(3) 菜单模块
玩家在游戏游戏过程中随时可以通过选择不同的菜单命令来进行相应的操作,菜单的制作是通过MFC框架中多文档程序的菜单栏来添加的,菜单包含一下几个部分:(1)游戏菜单中的新游戏和退出命令。(2)帮助菜单里面的关于连连看命令。
(4)界面显示模块
游戏场景中界面的显示包括以下部分:游戏的关卡数、目前的分数、游戏主场景。这些显示信息会随着游戏的进行而动态改变。
整个连连看游戏的系统是在VC++6.0开发环境中进行开发的,开发过程中大量的使用了微软提供的API接口方法,减少了程序代码的编写量、提高了编程效率。3.系统详细设计
3.1各个功能实现
菜单系统
游戏
帮助
退出
新游戏
图3-1 菜单系统
3.2连连看消去算法实现
在检验两个方块能否消掉的时候,我们要让两个方块同时满足两个条件才行,就是两者配对并且连线成功。
分3种情况:(从下面的这三种情况,我们可以知道,需要三个检测,这三个检测分别检测一条直路经。这样就会有三条路经。若这三条路经上都是空按钮,那么就刚好是三种直线(两个转弯点)把两个按钮连接起来了) * 1.相邻
* 2. 若不相邻的先在第一个按钮的同行找一个空按钮。1).找到后看第二个按钮横向到这个空按钮所在的列是否有按钮。2).没有的话再看第一个按钮到与它同行的那个空按钮之间是否有按钮。3).没有的话,再从与第一个按钮同行的那个空按钮竖向到与第二个按钮的同行看是否有按钮。没有的话路经就通了,可以消了.
* 3.若2失败后,再在第一个按钮的同列找一个空按钮。1).找到后看第二个按钮竖向到这个空按钮所在的行是否有按钮 2).没有的话,再看第一个按钮到与它同列的那个空按钮之间是否有按钮。3).没有的话,再从与第一个按钮同列的那个空按钮横向到与第二个按钮同列看是否有按钮。没有的话路经就通了,可以消了。
* 若以上三步都失败,说明这两个按钮不可以消去。
4. 系统调试和测试
4.1系统调试
本系统调试过程中遇到问题,原因和解决方法如下:
4.1.1信息显示错误
在整个游戏界面上方是游戏过程总的必要信息的显示,比如关卡,积分等。在这些数据更新时出现的模糊问题是因为没有写入必要的刷新函数造成的,其实在这样的图形编程问题的刷新函数是必不可少的,在适当的位置加入刷新函数即可保持图形图像的正确显示。
4.1.2音乐音效不能正常播放
这样的问题是容易遇到的,总结原因有两点:
(1)资源文件的ID以及资源文件的文件名问题。由于资源文件很多,很容易将这些文件混淆,当资源文件引入工程之后就不可再改动了。
(2)文件格式的问题。不同的音乐文件有不同的播放格式。
4.1.3游戏程序不能再其他机器上运行
编写完成的程序要进行推广,这个问题是一定要解决的。制作好的游戏程序必须要附有必要的软件以及详细的使用方法。由于VC++6.0环境文件体积比较大,拷贝不容易,所以拷贝过程中必须要附带必要的库文件或者直接编译程Release版本在拷贝给其他的玩家。
4.2系统测试
测试过程如下:
首先运行程序,可以测试游戏的初始化是否有问题。包括界面的显示问题,位置是否正确,是否表达到了自己所预想的目的。然后再初级,中级,高级之间来回切换,用测试游戏的随机性。小方块的分布有没有存在问题。然后对于提示和重排进行连续使用,观察上方信息的显示基本无误,必要的提示也及时弹出。游戏的每一个功能都要进行检查,而且要进行反复使用才可能发现潜在的问题。
至此,以上,对于图形的现实问题基本检查完毕。之后自己进行游戏若干次,每一级的所有小方块都能完全消除,这说明小方块是成对出现的,随机性也是没有错误的,在游戏结束后弹出提示输入姓名,输入姓名后将此次游戏的得分自动保存到积分榜,查看积分榜,对应以前的分数也能够大小顺序排列。
测试过程表明此游戏程序功能基本正常,运行过程基本没有问题。
5. 结果分析
1.游戏主界面:
图 5-1 游戏主界面图
点击游戏程序图标,可以正常启动游戏,出现以上的主界面。菜单栏有两个菜单选项,点击可以触发相应的功能。
2. 开始游戏界面
图 5-2 游戏开始界面图
点击游戏菜单项,选择新游戏按钮,即可开始游戏。界面将出现当前游戏的级别、关重列剩余次数以及提示剩余次数等相关游戏信息。
图 5-3 游戏中界面图
4.游戏成功界面
6. 总结
通过此次c++课程设计,我更加扎实的掌握了有关c++开发软件的知识,虽然在设计过程中遇到了一些问题,但正是这些问题让我学会了如何思考,如何通过利用身边的网络资源以及书籍来解决。也暴露出了前期我在这方面的知识欠缺和经验不足,但也学会在问题中不断完善自己的专业知识能力,弥补不足。同时让我明白一个道理,实践出真知,只有通过自己的亲自动手制作,才能真正的掌握已学到的知识。
此次课程设计,从理论到实践,可以说得是苦多于甜,但是可以学到很多很多的东西,同时不仅可以巩固了以前所学过的知识,而且学到了很多在书本上所没有学到过的知识。通过这次课程设计使我懂得了理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学的理论知识与实践相结合起来,从理论中得出结论,才能真正为社会服务,从而提高自己的实际动手能力和独立思考的能力。在设计的过程中遇到问题,可以说得是困难重重,但可喜的是最终都得到了解决。这也让我对自己的能力有了更大的肯定,让我以后无论是生活还是学习中都有更大的信心和干劲。
附录 主要程序代码
linkView.cpp
// linkView.cpp : implementation of the CLinkView class
//
#include "stdafx.h"
#include "link.h"
#include "linkDoc.h"
#include "linkView.h"
#include "windows.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CLinkView
IMPLEMENT_DYNCREATE(CLinkView, CView)
BEGIN_MESSAGE_MAP(CLinkView, CView)
//{{AFX_MSG_MAP(CLinkView)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_WM_CHAR()
ON_WM_CANCELMODE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CLinkView construction/destruction
CLinkView::CLinkView()
{
// TODO: add construction code here
}
CLinkView::~CLinkView()
{
}
BOOL CLinkView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CLinkView drawing
void CLinkView::OnDraw(CDC* pDC)
{
CLinkDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
/////////////////////////////////////////////////////////////////////////////
// CLinkView diagnostics
#ifdef _DEBUG
void CLinkView::AssertValid() const
{
CView::AssertValid();
}
void CLinkView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CLinkDoc* CLinkView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CLinkDoc)));
return (CLinkDoc*)m_pDocument;
}
#endif //_DEBUG
//声明外部变量theApp
extern CLinkApp theApp;
//进入新的关卡
void NewStage( CDC * pDC )
{
srand(time(0));
int i , j ;
for( i = 0 ;i<leny ; ++i )
for( j =0 ; j<lenx ; ++j )
theApp.PicArray[i][j].visible = false ;
int x , y ;
//每关出现lenx种图形,每种图形各有leny个 ,本程序中lenx=10 ,leny = 8
for( i=0; i<lenx-1 ; ++i )
{
for( j =0 ; j<leny ; ++j )
{
bool re = true ;
while( re )
{
y = rand()%leny ;
x = rand()%lenx ;
if( theApp.PicArray[y][x].visible == false )
{
theApp.PicArray[y][x].type = i ;
theApp.PicArray[y][x].visible = true ;
theApp.PicArray[y][x].x = x ;
theApp.PicArray[y][x].y = y ;
re = false ;
}
}
}
}
for( i = 0; i<leny ; ++i ) //剩下最后一种图片就不用随机了
for( j =0 ; j<lenx ; ++j )
if( theApp.PicArray[i][j].visible == false )
{
theApp.PicArray[i][j].type = lenx-1 ;
theApp.PicArray[i][j].visible = true ;
theApp.PicArray[i][j].x = j ; //千万注意对应关系!!
theApp.PicArray[i][j].y = i ;
}
CString str ;
str.Format( "您的得分是 %u ,共%d关,现在是第 %d 关", theApp.score ,totalStage,theApp.stage) ;
pDC->TextOut( 150 , 15 , str) ;
theApp.PaintPicture(pDC);
}
/*在图片a与图片b间画线
* CClientDC & dc为当前的dc
*/
void draw_line( picture & a , picture & b , CClientDC & dc ) //CClientDC类不能作按值传递参数
{
CPoint p1( theApp.margin + a.x * 64 + 64 / 2 , theApp.margin + a.y * 64 + 64 / 2 ) ;
CPoint p2( theApp.margin + b.x * 64 + 64/ 2 , theApp.margin + b.y * 64 + 64 / 2 ) ;
dc.MoveTo( p1 );
dc.LineTo( p2 ) ;
}
//将图片a从当前窗口中抹去
void paint_black( picture a , CDC *pDC )
{
CBrush backBrush(RGB(0, 0, 0));
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect);
pDC->PatBlt(theApp.margin + width * a.x , theApp.margin + width * a.y, width , width , PATCOPY);
pDC->SelectObject(pOldBrush);
}
//判断图片a与图片b能否直接相连(a、b间连线的转角数为0 )
bool match_direct( picture a , picture b , CClientDC & dc )
{
//a、b的x或y坐标必须有且只有一个相同才能直接相连
if (! ( a.x==b.x || a.y == b.y ) )
return false ;
//判断a、b间是否有其他图片阻隔
int i ;
bool match_x = false ;
if( a.x == b.x )
{
match_x = true ;
if( a.y > b.y )
for( i=a.y-1 ; i>b.y ; --i )
if( theApp.PicArray[ i][ a.x ].visible == true )
match_x = false ;
if( b.y > a.y )
for( i = b.y -1 ; i>a.y ; --i )
if( theApp.PicArray[ i][ a.x ].visible == true )
match_x = false ;
}
bool match_y = false ;
if( a.y == b.y )
{
match_y = true ;
if( a.x > b.x )
for( i = a.x - 1 ; i>b.x ; --i )
if( theApp.PicArray[ a.y ][ i ].visible == true )
match_y = false ;
if( b.x > a.x )
for( i = b.x -1 ; i>a.x ; --i )
if( theApp.PicArray[ a.y ][ i ].visible == true )
match_y = false ;
}
if( match_x || match_y )
{
// draw_line( a , b , dc ) ; //不能写在这里,否则match_one_corner会不正常
return true ;
}
return false ;
}
//判断a、b间能否通过只有一个转角的折线相连
bool match_one_corner( picture a , picture b , CClientDC & dc)
{
if( theApp.PicArray[ b.y ][ a.x ].visible == false &&\
match_direct( a , theApp.PicArray[ b.y ][ a.x ] , dc) && \
match_direct( b , theApp.PicArray[ b.y ][ a.x ] ,dc) )
{
draw_line( a , theApp.PicArray[ b.y ][ a.x ] ,dc ) ;
draw_line( b , theApp.PicArray[ b.y ][ a.x ] ,dc) ;
return true ;
}
if( theApp.PicArray[ a.y ][ b.x ].visible == false &&\
match_direct( a , theApp.PicArray[ a.y ][ b.x ] ,dc ) && \
match_direct( b , theApp.PicArray[ a.y ][ b.x ] ,dc) )
{
draw_line( a , theApp.PicArray[ a.y ][ b.x ] ,dc ) ;
draw_line( b , theApp.PicArray[ a.y ][ b.x ] ,dc) ;
return true ;
}
return false ;
}
//判断a、b能否通过有2个转角的折线相连
bool match_two_corner( picture a , picture b , CClientDC & dc )
{
int i ;
//转化为a与c能直接相连,而c与b可以通过有一个转角的折线相连的情况
for( i=a.x -1 ; i>=0 ; --i )
if( theApp.PicArray[a.y][i].visible == true )
break;
else
if( match_one_corner( theApp.PicArray[a.y][i] , b ,dc) )
{
draw_line( a , theApp.PicArray[a.y][i] ,dc ) ;
return true ;
}
for( i = a.x +1 ; i<lenx ; ++i )
if( theApp.PicArray[a.y][i].visible == true )
break;
else
if( match_one_corner( theApp.PicArray[a.y][i] , b ,dc ) )
{
draw_line( a , theApp.PicArray[a.y][i] ,dc ) ;
return true ;
}
for( i = a.y - 1 ; i>=0 ; --i )
if( theApp.PicArray[i][a.x].visible == true )
break;
else
if( match_one_corner( theApp.PicArray[i][a.x] ,b ,dc) )
{
draw_line( a , theApp.PicArray[i][a.x] ,dc ) ;
return true ;
}
for( i = a.y + 1 ; i<leny ; ++i )
if( theApp.PicArray[i][a.x].visible == true )
break;
else
if( match_one_corner( theApp.PicArray[i][a.x] ,b ,dc) )
{
draw_line( a , theApp.PicArray[i][a.x] ,dc ) ;
return true ;
}
return false ;
}
//判断a与b能否相连(条件:a与b类型相同,且能用转角不超过3个的折线相连
bool match( picture a , picture b , CClientDC & dc )
{
if( a.type != b.type )
return false ;
//直接相连
if( match_direct( a, b , dc ) )
{
draw_line( a , b , dc ) ;
Sleep(500) ; //连线在窗口中显示0.5 s 后消失
for( int i=0 ;i<leny ; ++i )
for( int j =0 ;j<lenx ; ++j )
if( theApp.PicArray[i][j].visible == false )
paint_black( theApp.PicArray[i][j] , &dc ) ;
return true ;
}
//通过一个或两个转角相连的情况
if( match_one_corner( a , b ,dc ) || match_two_corner( a, b ,dc) )
{
Sleep(500) ;
for( int i=0 ;i<leny ; ++i )
for( int j =0 ;j<lenx ; ++j )
if( theApp.PicArray[i][j].visible == false )
paint_black( theApp.PicArray[i][j] , &dc ) ;
return true ;
}
return false ;
}
//点击左键时判断前后点击的图片能否消去
void CLinkView::OnLButtonDown(UINT nFlags, CPoint point)
{
if( point.x <=theApp.margin || point.y<=theApp.margin ||\
point.x >= theApp.margin +lenx*width || point.y >= theApp.margin +leny*width )
return ;
int x = ( point.x - theApp.margin ) / width ;
int y = ( point.y - theApp.margin ) / width ;
int Px = ( theApp.PreClick.x - theApp.margin) / width ;
int Py = ( theApp.PreClick.y - theApp.margin ) / width ;
//设置画笔的属性,并关联到dc
CClientDC dc(this) ;
int penWidth = 5;
CPen pen(PS_SOLID, penWidth, RGB(255, 0, 0));
dc.SelectObject(&pen);
if( x == Px && y == Py ) //前后两次点击的是同一张图片
return ;
else
if( Px <0 || Py <0 ) //这是第一次点击的情况
return ;
else
if( theApp.PicArray[ y ][ x ].visible == true && \
theApp.PicArray[ Py ][ Px ].visible == true &&\
theApp.PicArray[ y ][ x ].type == theApp.PicArray[ Py ][ Px ].type &&\
match( theApp.PicArray[ y ][ x ] , theApp.PicArray[ Py ][ Px ] , dc )
)
{
theApp.PicArray[ y ][ x ].visible = false ;
theApp.PicArray[ Py ][ Px ].visible = false ;
CDC* pDC=GetDC();
paint_black( theApp.PicArray[ y ][ x ] , pDC );
paint_black( theApp.PicArray[ Py ][ Px ] ,pDC ) ;
theApp.score += 10 ;
CString str ;
str.Format( "您的得分是 %u ,共%d关,现在是第 %d 关", theApp.score ,totalStage,theApp.stage) ;
pDC->TextOut( 150 , 15 , str) ;
theApp.remain_num -= 2 ;
if( theApp.remain_num == 0 ) //剩余图片数为0,则通关
{
theApp.score += theApp.stage * 100 ;
CString str ;
str.Format( "您的得分是 %u ,共%d关,现在是第 %d 关", theApp.score ,totalStage,theApp.stage) ;
pDC->TextOut( 150 , 15 , str) ;
if( theApp.stage == totalStage )
MessageBox( "你已经成功完成所有关卡!!","恭喜!!",MB_OK ) ;
else
{
MessageBox("成功完成游戏!下面进行下一关!","恭喜!!",MB_OK );
theApp.remain_num = lenx * leny ;
theApp.stage ++ ;
NewStage( pDC ) ;
}
}
ReleaseDC( pDC ) ;
}
theApp.PreClick = point ;
}
//重画黑色的背景
BOOL CLinkView::OnEraseBkgnd(CDC* pDC)
{
//设置brush为希望的背景颜色
CBrush backBrush( RGB(0, 0, 0) );
//保存旧的brush
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect);
//画需要的区域
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
//最小化后再次恢复窗口时,需要重画窗口
void CLinkView::OnPaint()
{
CPaintDC dc(this);
CDC* pDC = GetDC();
theApp.PaintPicture( pDC );
ReleaseDC( pDC ) ;
}
//鼠标移动时,经过的的图片要变色
void CLinkView::OnMouseMove(UINT nFlags, CPoint point )
{
//在图片范围内
if( point.x > theApp.margin && point.y > theApp.margin &&\
point.x < theApp.margin + width * lenx && point.y <theApp.margin + width * leny )
{
CDC* pDC = GetDC();
//重画所有图片,主要是为了将上次经过的变色的图片恢复正常状态
theApp.PaintPicture( pDC );
int i = ( point.x - theApp.margin )/width ;
int j = ( point.y - theApp.margin )/width ;
if( theApp.PicArray[j][i].visible == true )
{
//鼠标经过的图片要变色
CBitmap bitmap3 ;
bitmap3.LoadBitmap( 171 + theApp.stage ) ; //171是第一张变色图片的资源号
CDC dcCompatible3 ;
dcCompatible3.CreateCompatibleDC( pDC) ;
dcCompatible3.SelectObject( &bitmap3 ) ;
pDC->BitBlt( theApp.margin + i*width, theApp.margin + j*width,wid
展开阅读全文