MFC简单小游戏之扫雷
来源:互联网 发布:搞笑照片贴图软件 编辑:程序博客网 时间:2024/06/13 02:37
1.创建MFC单文档应用程序
点击Bitmap,按顺序加入14*14的位图12张,同样按顺序加入30*30的位图4张,如图:
ANNIU系列位图自绘为:,,,。
BITMAP系列自绘为:,到的数字,后几幅为,,。
定义新类:
对于雷,我们是单独定义一个类,这样有利于程序的操作。
class Lei
{
public:
//显示哪一个位图
int weitu;
//这个位置相应的值
int shumu;
};
视图类变量: (最好在类向导中自动添加,直接在代码中写要修改好几个地方)
接着是在View类添加变量和函数:
//剩下雷数
int leftnum;
//雷数
int leinum;
//结束
int jieshu;
//计时
short second;
//开始计时
int secondstart;
//位图数组
CBitmap m_Bitmap[12];
//按扭位图数组
CBitmap m_anniu[4];
//雷区行数
int m_RowCount;
//雷区列数
int m_ColCount;
//最大雷区
Lei lei[50][50];
//这个位置周围雷数为0
void leizero();
//计时器函数
afx_msg void OnTimer(UINT nIDEvent);
//鼠标按下左键
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//鼠标按下右键
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
//初始化函数
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//鼠标左键松开
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
删去工具栏:(在CMainFrame::OnCreate函数中注释掉创建工具栏)可以在return 0前加上这几句,自定义自己的图标
m_hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));//加载自定义图标
SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcon);//将当前图标变换为自己定义的
设置窗口显示在最前端:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWndEx::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
cs.dwExStyle=WS_EX_TOPMOST;
// cs.style=WS_SYSMENU|WS_OVERLAPPED|WS_MINIMIZEBOX;//加上的话就不能调窗口大小了
//设置窗口大小:
cs.cx=395;
cs.cy=320;
cs.lpszName=_T("扫雷");//换标题
return TRUE;
}
构造函数:
由于构造函数是程序运行时就执行的,所以,除了对变量赋值之外,我们还可以把游戏的核心结构即内部数组赋值:先是把全部格子的位图和雷数赋值为0,然后调用随机函数按指定雷数赋值为-1,最后把不是雷的格子的雷数赋值为相应的值。
CMy2_1扫雷View::CMy2_1扫雷View(): leftnum(0){// TODO: 在此处添加构造代码for(int ii=0;ii<12;ii++) m_Bitmap[ii].LoadBitmap(IDB_BITMAP14+ii);//给每个m_Bitmap[]数组加载图像 for(int jj=0;jj<4;jj++) m_anniu[jj].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;i<m_RowCount;i++) { for(int j=0;j<m_ColCount;j++) { lei[i][j].shumu=0; lei[i][j].weitu=0; } } //获取当前时间 CTime time=GetCurrentTime(); int s; //获取秒数 s=time.GetSecond(); //设置80个雷 do { //以当前秒数为产生随机算法 int k=(rand()*s)%m_RowCount; int l=(rand()*s)%m_ColCount; //为了避免一个位置同时算两个雷 //只允许当前位置不是雷时赋值为雷 if(lei[k][l].shumu!=-1) { lei[k][l].shumu=-1; aa++; } }while(aa!=leinum); //给方格赋值,计算雷数 for(int a=0;a<m_RowCount;a++) for(int b=0;b<m_ColCount;b++) if(lei[a][b].shumu==0) { for(int c=a-1;c<a+2;c++)//给所选数字的一周赋值 for(int d=b-1;d<b+2;d++) if(c>=0&&c<m_RowCount&&d>=0&&d<m_ColCount)//没有出边界 if(lei[c][d].shumu==-1)//所选的数周围有雷,则这个数加一 lei[a][b].shumu++; }}
界面函数:
现在,可以开始画界面了。如下函数:
很明显,前面部分是用画的方法画出整个界面,但是,后面for循环显示的位图并不是现在画界面的内容,为什么要写呢?
这是为了用户框重画的需要,当我们的游戏玩了一半后最小化,或是把部分窗口移出屏幕,或是执行了新的应用程序覆盖了原来的程序时,必须重画。我们调用重画函数,它都要重新执行OnDraw(CDC* pDC)函数,那么,此时它就必须把已经显示出来的位图也显示出来。而开始时雷区位图是不可见的,并不影响界面的初始化。
void CMy2_1扫雷View::OnDraw(CDC* pDC){CMy2_1扫雷Doc* pDoc = GetDocument();ASSERT_VALID(pDoc);if (!pDoc)return;// TODO: 在此处为本机数据添加绘制代码 //画背景 CBrush mybrush1; mybrush1.CreateSolidBrush(RGB(192,192,192));//嘛颜色,运行看看? CRect myrect1(0,0,1200,800);//以0,0,位置开始,整个背景 pDC->FillRect(myrect1,&mybrush1); //画黑框 CBrush mybrush; mybrush.CreateSolidBrush(RGB(0,0,0)); CRect myrect(20,10,70,40); pDC->FillRect(myrect,&mybrush);//两个显示数字的黑框 CRect myrect2(325,10,375,40); pDC->FillRect(myrect2,&mybrush); CPen mypen; 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,40); pDC->LineTo(375,40); pDC->LineTo(375,10); //画雷区边线 //左上角是白线,右下角是黑线,以显示立体感(可以吧背景分成ROW*COL个小正方形) for(int i=0;i<m_RowCount;i++) for(int j=0;j<m_ColCount;j++) {//左上角 pDC->MoveTo(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)); myoldPen2=pDC->SelectObject(&mypen2); for(int ii=0;ii<m_RowCount;ii++) for(int jj=0;jj<m_ColCount;jj++) {//右下角 pDC->MoveTo(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) MessageBox(_T("Can't create DC")); //显示按钮 Dc.SelectObject(m_anniu[0]);//显示的表情娃娃脸 pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY);//显示图形 /*判断显示什么位图,鼠标点下时显示 weitu=1已按下的数字区 weitu=2显示旗 weitu=3显示问号*/ for(int a=0;a<m_RowCount;a++) for(int b=0;b<m_ColCount;b++) { if(lei[a][b].weitu==1) { Dc.SelectObject(m_Bitmap[lei[a][b].shumu]); pDC->BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); } if(lei[a][b].weitu==2) { Dc.SelectObject(m_Bitmap[9]); pDC->BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); } if(lei[a][b].weitu==3) { Dc.SelectObject(m_Bitmap[10]); pDC->BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); } //结束 if(jieshu==1&&lei[a][b].shumu==-1) { Dc.SelectObject(m_Bitmap[11]); pDC->BitBlt(a*15+10,b*15+50,160,160,&Dc,0,0,SRCCOPY); Dc.SelectObject(m_anniu[3]); 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,_T("Comic Sans MS"))) { MessageBox(_T("Can't Create Font")); } pDC->SelectObject(&font);//选择字体 CString str; //利用判断显示位数,不够三位前面加0 if(leftnum<10) str.Format(_T("00%d"),leftnum);// else str.Format(_T("0%d"),leftnum); pDC->TextOut(25,10,str); if(second<10) str.Format(_T("00%d"),second); else if(second<100) str.Format(_T("0%d") ,second); else str.Format(_T("%d") ,second); pDC->TextOut(330,10,str); pDC->RestoreDC(nOldDC);}我们可以把上面的函数细分为几个小函数,然后在这个函数里面分别调用。
按下鼠标左键:
用if语句判断,如果在按钮上面,则显示按钮按下位图;如果在扫雷区,先把按钮位图改为张口位图,再判断按下的是否是雷,是就结束,重画,以显示所有的雷;否则,重画相应格子以显示数字。
(类向导->添加消息处理->找到WM-LBUTTONDOWN,添加,编辑)void CMy2_1扫雷View::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //获取指针pdc//MessageBox(_T("确实按下了!")); CDC *pDC=GetDC(); CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox(_T("Can't create DC")); //显示按下按钮 if(point.x>180&&point.x<210&&point.y>10&&point.y<40) { Dc.SelectObject(m_anniu[3]); pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); } if((point.x>=10)&&(point.x<=385)&&(point.y>=50)&&(point.y<=290)) { if(jieshu==1) return; //显示张口按钮 Dc.SelectObject(m_anniu[1]); pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); // secondstart为1时计时有效 secondstart=1; //鼠标坐标转换为数组坐标 int a=(point.x-10)/15; int b=(point.y-50)/15; if(lei[a][b].weitu==0||lei[a][b].weitu==3) { if(lei[a][b].shumu==-1) { jieshu=1; //结束时,释放Timer KillTimer(1);//重画,因为这次重画将显示全部的雷,//不能用部分重画 Invalidate(); } else { lei[a][b].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);<pre name="code" class="cpp">松开鼠标左键:
(添加WM_LBUTTONUP消息响应函数)
void CMy2_1扫雷View::OnLButtonUp(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(_T("Can't create DC")); //显示按钮 Dc.SelectObject(m_anniu[0]); pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); if(jieshu==1) { //显示按扭位图 Dc.SelectObject(m_anniu[2]); pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY); } //如果按下的是按扭,重新开始(屏幕中间的小黄人) if(point.x>180&&point.x<210&&point.y>10&&point.y<40) OnStart(); CView::OnLButtonUp(nFlags, point);}
按下鼠标右键:
如果是雷,我们按右键,显示旗子,并减少一个剩下雷数;如果我们认为那旗子的格子不是雷,我们按右键,显示问号,并在剩下雷数加上1。有关函数:
void CMy2_1扫雷View::OnRButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if(jieshu==1) //结束,返回 return; if((point.x>=10)&&(point.x<=385)&&(point.y>=50)&&(point.y<=290)) { int a=(point.x-10)/15;//当前坐标转换到对应行和列 int b=(point.y-50)/15; if(lei[a][b].weitu==0||lei[a][b].weitu==3) { lei[a][b].weitu=2;//赋值为2,重绘时调用OnDraw()函数在里面具体操作 leftnum--; } else if(lei[a][b].weitu==2) { lei[a][b].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, point);}
显示没有雷的区域:
运行,玩一下,你会发现当按下的是一个周围没有雷的格子是它并不会象Window里面的扫雷游戏一样显示它周围的格子雷数。怎么实现呢?
添加一个如下函数:
void CMy2_1扫雷View::leizero(){ for(int i=0;i<m_RowCount;i++) for(int j=0;j<m_ColCount;j++) if(lei[i][j].shumu==0&&lei[i][j].weitu==1)//当前位置是空的格子 { for(int n=i-1;n<i+2;n++)//当前格子的一周 for(int m=j-1;m<j+2;m++) if(n>=0&&n<25&&m>=0&&m<m_ColCount)//不超过边界 if(lei[n][m].shumu!=-1&&lei[n][m].weitu==0) {//不显示为雷的格子 lei[n][m].weitu=1;//显示这个位置的格子 CRect rect; rect.left=n*15+10; rect.right=n*15+25; rect.top=m*15+50; rect.bottom=m*15+65; InvalidateRect(&rect); } }}
再运行,效果是有的,只是它只显示一部分,即这个周围的几个。那么我们应该怎样使它显示全部呢?可以利用计时器函数。
计时器函数:
OnTimer(UINT nIDEvent)函数,同时也可以实现计时显示。添加OnCreate(LPCREATESTRUCT lpCreateStruct)和 OnTimer(UINT nIDEvent):
int CMy2_1扫雷View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here //20次为一秒 SetTimer(1,50,NULL); return 0; }
void CMy2_1扫雷View::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 //结束,返回 if(jieshu==1) return; //显示个数为0的方格 leizero(); //计时 if(secondstart>0) secondstart++; //二十次为一秒 if(secondstart==2) { secondstart=1; second++; //重画时间 CRect rect3; rect3.left=325; rect3.right=375; rect3.top=10; rect3.bottom=40; InvalidateRect(&rect3); } CView::OnTimer(nIDEvent); }
修改菜单:
游戏已经可以玩了,只是点到雷之后就完了,无法重新开始。还有,菜单还没有改。下面就修改菜单并实现重新开始功能:
设置完后,在view类添加函数OnStart();
添加代码(其实就是初始化):
void CMy2_1扫雷View::OnStart() { SetTimer(1,50,NULL); // TODO: 在此添加命令处理程序代码 second=0;//计时 secondstart=0;//1时开始计时// num=0; leftnum=leinum;//剩余雷数 jieshu=0;//jieshu=1时停止 int aa=0; //初始化0 for(int i=0;i<m_RowCount;i++) { for(int j=0;j<m_ColCount;j++) { lei[i][j].shumu=0; lei[i][j].weitu=0; } } //设置leinum个雷 do { int k=rand()%m_RowCount; int l=rand()%m_ColCount; if(lei[k][l].shumu!=-1) { lei[k][l].shumu=-1; aa++; } }while(aa!=leinum); //给方格赋值 for(int a=0;a<m_RowCount;a++) for(int b=0;b<m_ColCount;b++) if(lei[a][b].shumu==0) { for(int c=a-1;c<a+2;c++) for(int d=b-1;d<b+2;d++) if(c>=0&&c<m_RowCount&&d>=0&&d<m_ColCount) if(lei[c][d].shumu==-1) lei[a][b].shumu++; } Invalidate(); }OK,运行就行了,其他方面你可以自由发挥。。。
有时候会运行出错,在网上查是没有数据库的原因,这个我也不懂。。。
出错再运行就行了。。。
我的界面:
,
左下角状态栏鼠标位置显示啥的,是我前几天学习过程中学的自己实践一下,本文没有介绍,很简单,可以问度娘。。。。。。
完~,菜鸟MFC学习笔记。。。。。
- MFC简单小游戏之扫雷
- 扫雷小游戏简单易懂
- 简单的扫雷小游戏
- C#小游戏之扫雷
- 小游戏之扫雷
- C++小游戏之扫雷
- 小游戏之扫雷的实现
- MFC自制的扫雷小游戏所得心得
- java的扫雷小游戏(超简单)
- VB6实现简单的扫雷小游戏
- C语言简单实现扫雷小游戏~~~
- 扫雷小游戏c语言简单版
- 扫雷小游戏
- 扫雷小游戏
- 扫雷小游戏
- 扫雷小游戏
- 扫雷小游戏
- 扫雷小游戏
- Mac OS 的一点历史: Mac OS, Mac OSX 与Darwin
- ubuntu virtualbox启动不了
- Spring多数据源的动态切换
- 20140810eclipse js报错解决办法
- 剑指offer 2.3 数据结构1-数组求sizeof()
- MFC简单小游戏之扫雷
- Test
- Codeforces 455B A Lot of Games 字典树上博弈
- wireshark抓无线包报错The capture session could not be initiated .
- js中创建简单对象
- 自定义一个字节缓冲区的简单示例
- wireshark的使用教程--用实践的方式帮助我们理解TCP/IP中的各个协议是如何工作的 .
- Extjs加载百度地图,显示不全的问题
- poj1308