MFC简单小游戏之扫雷

来源:互联网 发布:搞笑照片贴图软件 编辑:程序博客网 时间:2024/06/13 02:37
             我这几天学了点MFC编程,在网上偶然看见了用MFC编写的扫雷,老看理论不实践是不行的,就自己模仿编写了个扫雷小游戏,编写过程中果然发现很多问题.
        首先是前几天学MFC时知道的函数忘完了,在用时,一MSDN一百度才发现还有这么个东西存在(⊙﹏⊙),再来就是看教程时以为很熟悉的WM—类型的消息处理函数,以及加成员变量等在真正自己处理时都没那么简单,还有就是MFC的各个类之间的关联消息处理根本不知道!!!在做这个小游戏的时候,加一些消息处理以及成员变量,都需要看网上教程中加在哪,我曾自己试着加,各种茫然~~~
        总之通过这个小游戏编写,发现自己很多问题,告诫自己以后看书,看资料要切忌浮躁!!!
        开始编写步骤(我用的VS2010):

1.创建MFC单文档应用程序

2.切换到资源视图

        点击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学习笔记。。。。。

1 0
原创粉丝点击