《2022年完整word版,C++扫雷》由会员分享,可在线阅读,更多相关《2022年完整word版,C++扫雷(15页珍藏版)》请在金锄头文库上搜索。
1、扫雷1 1 游戏实现扫雷,是附带在Window 里面的游戏, 是个简单的游戏。因此我们就从扫雷开始我们的游戏旅程。 很多人都玩过这个游戏,只是不知道怎么用程序实现。不过还有人不知道怎么玩,下面就先说说游戏的规则:开始:按左键开始游戏,按按钮或菜单重新开始。左键:按下时,是雷则结束,非雷则显示数字。数字:代表此数字周围一圈八格中雷的个数。右键:奇次按下表示雷,偶数按下表示对上次的否定。结束:左键按到雷结束,找出全部雷结束。接下来就该介绍游戏的编程过程了。不过要先交代一下一些内容。添加位图。精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 1 页,共 1
2、5 页添加全局变量。画初始界面。添加函数。为什么要按这种次序呢?因为我们在画初始界面时,可能要用到位图或变量,而变量的定义又可能要对位图进行定义。这样的步骤的好处还有:在做一步之后都可以运行,有错就改,无错就做下一步。上图是扫雷的一个画面。下面就一步一步地演示,以编程的思路进行,当然,由于编程过程中有一些函数中的代码是分成两三次写的,我们就不重复, 全部代码在第一次讲到时列出,而后面讲到时就只是提一下。新建单文档工程2_1。2 2 资源编辑添加位图:前十二幅是在雷区的,后四幅是按钮。为了便于加载,必须各自保证其连续性。另外,为什么不添加一个按钮而用位图呢?是因为即使我们添加了按钮也要添加四幅位
3、图!位图的 ID 号:按扭位图:30*30 IDB_ANNIU1 、IDB_ANNIU 2、IDB_ANNIU3、 IDB_ANNIU4 雷区位图:14*14 ID 号按下图依次为:IDB_BITMAP14。 。 。 。 。 。IDB_BITMAP25 位图:下图(图2-1) 。精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 2 页,共 15 页图 2-1 位图的 ID 号:按扭位图 30*30 IDB_BITMAP1 、IDB_BITMAP2、IDB_BITMAP3 、 IDB_BITMAP4 雷区位图14*14 ID 号按下图依次为:IDB_B
4、ITMAP5 IDB_BITMAP16 12345678910111213141516icon 3 3 变量函数精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 3 页,共 15 页定义新类:对于雷,我们是单独定义一个类,这样有利于程序的操作。class Lei Mine public: /显示哪一个位图int weitu; bitmap/这个位置相应的值int shumu; index; 并有如下规定(图2-2) :图 2-2 视图类变量:接着是在View 类添加变量和函数:/剩下雷数int leftnum; /雷数int leinum; mine
5、num/结束int jieshu; endflag/计时short second; /开始计时int secondstart; /位图数组CBitmap m_Bitmap12; bitmap/按扭位图数组CBitmap m_anniu4; button/雷区行数int m_RowCount; row/雷区列数int m_ColCount; line/最大雷区Lei lei5050; Mine mine/这个位置周围雷数为0 void leizero(); /计时器函数afx_msg void OnTimer(UINT nIDEvent); /鼠标按下左键afx_msg void OnLButt
6、onDown(UINT nFlags, CPoint point); /鼠标按下右键afx_msg void OnRButtonDown(UINT nFlags, CPoint point); /初始化函数精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 4 页,共 15 页afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); /鼠标左键松开afx_msg void OnLButtonUp(UINT nFlags, CPoint point); 4 4 具体实现删去状态栏和工具栏:开始执行程序, 就
7、能见到一个有状态栏和工具栏的大的单文档,与上图不同, 所以我们第一步就是整理框架:打开下面函数,把里面的一些语句去掉。如下所示:int CMainFrame:OnCreate(LPCREATESTRUCT lpCreateStruct) if (CFrameWnd:OnCreate(lpCreateStruct) = -1) return -1; /* if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYB
8、Y | CBRS_SIZE_DYNAMIC) | !m_wndToolBar.LoadToolBar(IDR_MAINFRAME) TRACE0(Failed to create toolbarn); return -1; / fail to create if (!m_wndStatusBar.Create(this) | !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT) TRACE0(Failed to create status barn); return -1; / fail to cre
9、ate / TODO: Delete these three lines if you dont want the toolbar to / be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); */ return 0; 设置窗口大小:运行附加的代码,还能看到扫雷游戏的框架是不能调大小的,而且总是显示在最前面,这又是怎么实现的呢?在下面函数里添加语句,你能说出前三句是什么意思吗?注释已经被我去掉了,如果不知道,不如
10、按一下F1。BOOL CMainFrame:PreCreateWindow(CREATESTRUCT& cs) if( !CFrameWnd:PreCreateWindow(cs) ) return FALSE; / TODO: Modify the Window class or styles here by modifying 精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 5 页,共 15 页/ the CREATESTRUCT cs cs.dwExStyle=cs.dwExStyle|WS_EX_TOPMOST; / cs.style=WS
11、_SYSMENU|WS_OVERLAPPED|WS_MINIMIZEBOX;/; /设置窗口大小:400*340 cs.cx=400; cs.cy=340; return TRUE; 构造函数:由于构造函数是程序运行时就执行的,所以, 除了对变量赋值之外,我们还可以把游戏的核心结构即内部数组赋值:先是把全部格子的位图和雷数赋值为0,然后调用随机函数按指定雷数赋值为-1,最后把不是雷的格子的雷数赋值为相应的值。CMy2_1View:CMy2_1View() / TODO: add construction code here for(int ii=0;ii16;ii+) m_Bitmapii.L
12、oadBitmap(IDB_BITMAP14+ii); for(int jj=0;jj4;jj+) m_anniujj.LoadBitmap(IDB_ANNIU1+jj); /计时second=0; /1 时开始计时secondstart=0; /行数m_RowCount=25; /列数m_ColCount=16; /雷数leinum=80; /剩余雷数leftnum=leinum; /jieshu=1 时停止jieshu=0; int aa=0; /初始化为0 for(int i=0;im_RowCount;i+) for(int j=0;jm_ColCount;j+) leiij.shum
13、u=0; leiij.weitu=0; /获取当前时间CTime time=GetCurrentTime(); int s; /获取秒数s=time.GetSecond(); /设置 40 个雷do 精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 6 页,共 15 页 /以当前秒数为产生随机算法int k=(rand()*s)%m_RowCount; int l=(rand()*s)%m_ColCount; /为了避免一个位置同时算两个雷/只允许当前位置不是雷时赋值为雷if(leikl.shumu!=-1) leikl.shumu=-1; aa+;
14、 while(aa!=leinum); /给方格赋值,计算雷数for(int a=0;am_RowCount;a+) for(int b=0;bm_ColCount;b+) if(leiab.shumu=0) for(int c=a-1;ca+2;c+) for(int d=b-1;d=0&c=0&dFillRect(myrect1,&mybrush1); /画黑框CBrush mybrush; mybrush.CreateSolidBrush(RGB(0,0,0); CRect myrect(20,10,70,40); pDC-FillRect(myrect,&mybrush); CRect
15、 myrect2(325,10,375,40); pDC-FillRect(myrect2,&mybrush); CPen mypen; 精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 7 页,共 15 页CPen*myoldPen; mypen.CreatePen(PS_SOLID,2,RGB(255,255,255); myoldPen=pDC-SelectObject(&mypen); /画黑框的白线pDC-MoveTo(20,40); pDC-LineTo(70,40); pDC-LineTo(70,10); pDC-MoveTo(325,
16、40); pDC-LineTo(375,40); pDC-LineTo(375,10); /画雷区边线/左上角是白线,右下角是黑线,以显示立体感for(int i=0;im_RowCount;i+) for(int j=0;jMoveTo(10+i*15,50+j*15+14); pDC-LineTo(10+i*15,50+j*15); pDC-LineTo(10+i*15+14,50+j*15); pDC-SelectObject(myoldPen); CPen mypen2; CPen*myoldPen2; mypen2.CreatePen(PS_SOLID,1,RGB(0,0,0); m
17、yoldPen2=pDC-SelectObject(&mypen2); for(int ii=0;iim_RowCount;ii+) for(int jj=0;jjMoveTo(10+ii*15,50+jj*15+14); pDC-LineTo(10+ii*15+14,50+jj*15+14); pDC-LineTo(10+ii*15+14,50+jj*15); pDC-SelectObject(myoldPen2); CDC Dc; if(Dc.CreateCompatibleDC(pDC)=FALSE) AfxMessageBox(Cant create DC); /显示按钮Dc.Sele
18、ctObject(m_anniu0); pDC-BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); /判断显示什么位图/weitu=1 已按下的数字区/weitu=2 显示旗/weitu=3 显示问号for(int a=0;am_RowCount;a+) for(int b=0;bBitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); if(leiab.weitu=2) Dc.SelectObject(m_Bitmap9); pDC-BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY
19、); 精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 8 页,共 15 页 if(leiab.weitu=3) Dc.SelectObject(m_Bitmap10); pDC-BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); /结束if(jieshu=1&leiab.shumu=-1) Dc.SelectObject(m_Bitmap11); pDC-BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); Dc.SelectObject(m_anniu3);
20、 pDC-BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); /显示黑框里的数字int nOldDC=pDC-SaveDC(); pDC-SetTextColor(RGB(255,0,0); pDC-SetBkColor(RGB(0,0,0); CFont font; if(0=font.CreatePointFont(160,Comic Sans MS) AfxMessageBox(Cant Create Font); pDC-SelectObject(&font); CString str; /利用判断显示位数,不够三位前面加0 if(leftnumTextOu
21、t(25,10,str); if(second10) str.Format(00%d,second); else if(secondTextOut(330,10,str); pDC-RestoreDC(nOldDC); 运行一下, 外观已经出来了,只是还不能玩。那我们就来添加一些功能函数,使它可以玩。当然, 如果你对程序已经有一定的经验的话,你就会指出上面的函数太长了。这并不太符合我们编程的要求。我们编程有一个讲究,就是尽量使函数的代码少,一般为一页左右,便于查看。 那么,我们可以把上面的函数细分为几个小函数,然后在这个函数里面分别调用。按下鼠标左键:用 if 语句判断,如果在按钮上面,则显示
22、按钮按下位图;如果在扫雷区,先把按钮位图改为张口位图,再判断按下的是否是雷,是就结束,重画,以显示所有的雷;否则,重画相应格子以显示数字。精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 9 页,共 15 页void CMy2_1View:OnLButtonDown(UINT nFlags, CPoint point) / TODO: Add your message handler code here and/or call default /获取指针pdc CDC *pDC=GetDC(); CDC Dc; if(Dc.CreateCompati
23、bleDC(pDC)=FALSE) AfxMessageBox(Cant create DC); /显示按下按钮if(point.x180&point.x10&point.yBitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); if(point.x=10)&(point.x=50)&(point.yBitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); / secondstart 为 1 时计时有效secondstart=1; /鼠标坐标转换为数组坐标int a=(point.x-10)/15; int b=(point.y-50)/15; i
24、f(leiab.weitu=0|leiab.weitu=3) if(leiab.shumu=-1) jieshu=1; /结束时,释放Timer KillTimer(1); /重画,因为这次重画将显示全部的雷,/不能用部分重画Invalidate(); else leiab.weitu=1; CRect rect; rect.left=a*15+10; rect.right=a*15+25; rect.top=b*15+50; rect.bottom=b*15+65; InvalidateRect(&rect); CView:OnLButtonDown(nFlags, point); 如果你现
25、在运行的话,你会发现按下按钮时并不还原,这就涉及到鼠标函数:OnLButtonUp(UINT nFlags, CPoint point) 松开鼠标左键:精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 10 页,共 15 页松开左键时,显示按钮没有按下的位图;再判断,如果结束,就要显示失败的位图;另外,如果是在按钮上松开按钮,即表示我们已经按下了重新开始的按钮,必须调用重新开始函数 OnStart()。由于 OnStart() 函数是与菜单里的开始共有的,此处先保留不说,若有必要运行, 可以先去掉最后两行。void CMy2_1View:OnLBut
26、tonUp(UINT nFlags, CPoint point) / TODO: Add your message handler code here and/or call default CDC *pDC=GetDC(); CDC Dc; if(Dc.CreateCompatibleDC(pDC)=FALSE) AfxMessageBox(Cant create DC); /显示按钮Dc.SelectObject(m_anniu0); pDC-BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); if(jieshu=1) /显示按扭位图Dc.SelectObjec
27、t(m_anniu2); pDC-BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); /如果按下的是按扭,重新开始if(point.x180&point.x10&point.y=10)&(point.x=50)&(point.y=290) int a=(point.x-10)/15; int b=(point.y-50)/15; if(leiab.weitu=0|leiab.weitu=3) leiab.weitu=2; leftnum-; else if(leiab.weitu=2) 精选学习资料 - - - - - - - - - 名师归纳总结 - - - -
28、- - -第 11 页,共 15 页 leiab.weitu=3; leftnum+; /重画剩下雷数CRect rect2; rect2.left=20; rect2.right=70; rect2.top=10; rect2.bottom=40; InvalidateRect(&rect2); /重画打击格子CRect rect; rect.left=a*15+10; rect.right=a*15+25; rect.top=b*15+50; rect.bottom=b*15+65; InvalidateRect(&rect); CView:OnRButtonDown(nFlags, po
29、int); 显示没有雷的区域:运行, 玩一下, 你会发现当按下的是一个周围没有雷的格子是它并不会象Window 里面的扫雷游戏一样显示它周围的格子雷数。怎么实现呢?添加一个如下函数:/扫描,如果是已经被按下且雷数为0,显示它周围的八个格,并重画void CMy2_1View:leizero() for(int i=0;im_RowCount;i+) for(int j=0;jm_ColCount;j+) if(leiij.shumu=0&leiij.weitu=1) for(int n=i-1;ni+2;n+) for(int m=j-1;m=0&n=0&m0) secondstart+; /
30、二十次为一秒if(secondstart=20) secondstart=1; second+; /重画时间CRect rect3; rect3.left=325; rect3.right=375; rect3.top=10; rect3.bottom=40; InvalidateRect(&rect3); CView:OnTimer(nIDEvent); 扫雷游戏就这样就是了。下面是附加内容, 它将说明菜单的添加和重新开始函数的算法。5 5 附加内容修改菜单:游戏已经可以玩了,只是点到雷之后就完了,无法重新开始。还有,菜单还没有改。下面就修改菜单并实现重新开始功能:把菜单改为如下图2-3。精
31、选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 13 页,共 15 页图 2-3 并在 View() 函数中按下图添加OnStart()函数(图2-4) :图 2-4 开始函数:OnStart()函数其实只是构造函数的再版。void CMy2_1View:OnStart() SetTimer(1,50,NULL); / TODO: Add your command handler code here second=0;/计时secondstart=0;/1 时开始计时/ num=0; leftnum=leinum;/ 剩余雷数jieshu=0;/ji
32、eshu=1 时停止int aa=0; /初始化 0 for(int i=0;im_RowCount;i+) 精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 14 页,共 15 页for(int j=0;jm_ColCount;j+) leiij.shumu=0; leiij.weitu=0; /设置 leinum 个雷do int k=rand()%m_RowCount; int l=rand()%m_ColCount; if(leikl.shumu!=-1) leikl.shumu=-1; aa+; while(aa!=leinum); /给方
33、格赋值for(int a=0;am_RowCount;a+) for(int b=0;bm_ColCount;b+) if(leiab.shumu=0) for(int c=a-1;ca+2;c+) for(int d=b-1;d=0&c=0&dm_ColCount) if(leicd.shumu=-1) leiab.shumu+; Invalidate(); 这样,整个程序完成了。6 6 小结当然,这个游戏比Window 自带的简单。 但就目前来说, 离它其实也不远。添加菜单项,并相应修改参数值:m_RowCount 、 m_ColCount 、leinum,并重新初始化界面就行了。本书的例子都只是一些最基本的游戏算法,它教你怎样去实现游戏,而至于游戏的扩展,我只是提一些建议,让你自己去实现。精选学习资料 - - - - - - - - - 名师归纳总结 - - - - - - -第 15 页,共 15 页