MFC简单小游戏之扫雷

              我这几天学了点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学习笔记。。。。。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值