• 贪吃蛇游戏制作


    贪吃蛇游戏是一个比较简单但非常有趣的游戏,因此从程序编写的角度看,这个游戏编写也不是特别困难。
    这个游戏是在参考了其他人用C语言DOS下写的贪吃蛇游戏写成的,这个DOS版本的C语言程序在半年前看过,现在还记得主要部分,下面就说说这个游戏编写。

    本程序用CPoint作为食物以及蛇节点的定位类型,为方便声明以及便于修改,在CSnake定义前先声明了以下几个内容:

    ///// Snake.h

    typedef CPoint CFood;
             // 这样做的好处是不必关心CFood到底有没有变化过,也许以后修改不用CPoint了呢!

    const INT  SNAKE_NODE_SIZE = 16;  // 以16个像素为一个食物节点大小,也是蛇自身节点的大小
    const INT  FRAME_WIDTH  = 26;       // 定义贪吃蛇外框宽度
    const INT  FRAME_HEIGHT = 20;       // 定义贪吃蛇外框高度
    const INT  SNAKE_LENGTH = FRAME_HEIGHT*FRAME_WIDTH;   // 依据外框宽度和高度,蛇的最大长度定义

    // 下面是贪吃蛇的声明:
    class CSnake : public CObject   // 是否继承CObject对蛇定义是没有影响的,从CObject继承是为将来扩展应用留着 
    {
    public:
     CSnake(CFood& Start, COLORREF Color);   // Start蛇起始点位置,其实此处不应该用CFood来表示起始位置,
                                                                  // 但又不太容易用其他方式来表达,Color表示蛇的颜色
     virtual ~CSnake();

    // Attribute
    public:

    // Operation
    public:
         VOID SetPausing(VOID);                   // 设置暂停
         VOID ResumePausing(VOID);             // 恢复继续
         BOOL IsSnakeTuning(VOID);             // 判断蛇是否在变形之中
         VOID SetSnakeTuning(BOOL bTuning = FALSE);   // TRUE:蛇在变形,FALSE:蛇未变形       
         INT GetSnakeLength(VOID);             // 获取当前蛇的长度
         static BOOL m_bTuning;                 // 控制当前是否在进行拐弯,如果是设置拐弯,暂停Moving。
         VOID Moving(VOID);                      // 蛇移动,依据m_CurrPos设置的方向移动
         BOOL IsFoodInSnake(CFood& Food);        // 判断食物Food是否在现有蛇节点之中,如果是,返回TRUE
         VOID MoveTo(INT x, INT y);                      // 移动蛇到节点(x,y)处,(x,y)是用整数表示的行列坐标
         VOID MoveLeft(VOID);                             // 蛇向左边移动
         VOID MoveRight(VOID);                          // 蛇向右边移动
         VOID MoveUp(VOID);                              // 蛇向上移动
         VOID MoveDown(VOID);                         // 蛇向下移动
         BOOL IsSnakeDead();                            // 判断蛇是否死掉了,死掉的情况为碰到自身或超出边界
         BOOL EatFood(CFood &Food);                // 蛇是否能够吃掉食物,即食物点与蛇头是否在同一点,是,返回TRUE
         VOID DrawSnake(CDC *pDC, CPoint poFrameStart);     // 通过传入的Frame起始点和DC,
                                                                                      // 用m_Color画出蛇自身
         VOID SetColor(COLORREF Color);          // 设置蛇颜色

    private:
         CFood m_Snake[FRAME_WIDTH*FRAME_HEIGHT];   // 记录蛇的每一节的左上角坐标,其实也就是(x,y)行列
         INT  m_nCurrLength;                                            // 记录当前蛇的长度
         enum {LEFT,RIGHT,UP,DOWN};                             // 蛇移动方向枚举,有四个方向,上下左右
         INT  m_nCurrPos;                                                 // 记录当前蛇移动方向
         COLORREF m_Color;                                              // 记录蛇的颜色
         BOOL m_bTuning;                    // 记录蛇是否在变形之中,此时不应该再向前移动
         BOOL m_bPausing;                  // 暂停
    };


    /// 接下来是贪吃蛇的实现定义:
    /// Snake.cpp

    CSnake::CSnake( CFood& Start, COLORREF Color )
    {
         m_nCurrLength = 2;  //开始蛇有两节
         memset(m_Snake,0, sizeof(m_Snake));  // 清空蛇数据

        // 设置蛇位置 
         m_Snake[0] = Start;
         m_Snake[1].x = Start.x + 1;
         m_Snake[1].y = Start.y;
         m_Color = Color;    // 设置蛇颜色
         m_nCurrPos = CSnake::LEFT;  //起初往左边走
         m_bTuning = FALSE;    // 蛇起始时直走
         m_bPausing = FALSE;
    }

    CSnake::~CSnake()
    {
    }

    VOID CSnake::MoveLeft(VOID)
    {

         if(m_nCurrPos == CSnake::RIGHT)  // 如果是往右移动,那么不可以往左再移动了
             return ; 
         
        if( m_bPausing )  // 如果暂停,停止移动
             return ;

     
         CFood &Head = m_Snake[0];
         m_nCurrPos = CSnake::LEFT;  // 记录当前是往左移动了!
         MoveTo(Head.x-1 , Head.y);   // 左边移动,x-1,y保持不变
    }

    VOID CSnake::MoveRight( VOID )
    {
         if(m_nCurrPos == CSnake::LEFT)   // 相反方向移动,不允许
             return ;

         if( m_bPausing )  // 如果暂停,停止移动
             return ;


         CFood &Head = m_Snake[0];
         m_nCurrPos = CSnake::RIGHT;
         MoveTo(Head.x+1, Head.y);  
    }

    VOID CSnake::MoveTo(INT x, INT y)
    {
    // 这是贪吃蛇最核心与关键的函数
    // 蛇移动,最后面的要往前移动,在数据上反应是蛇尾数据为前面一个节点的位置
    // 除了蛇头位置外,其他位置均可如此推断,因此也就是将蛇中前一个数据移动
    // 后退到下一个数据位置
         for(int i=m_nCurrLength-1; i>0; i--)  // 从第一个到最后一个均需要往后移动
         {
              m_Snake[i] = m_Snake[i-1];     // 后移
         }
         m_Snake[0].x = x;   // 蛇头节点位置
         m_Snake[0].y = y;
    }

    VOID CSnake::MoveUp( VOID )
    {
         if(m_nCurrPos == CSnake::DOWN)  // 相反方向移动,不允许
              return ;

         if( m_bPausing )  // 如果暂停,停止移动
              return ;


         CFood &Head = m_Snake[0];
         m_nCurrPos = CSnake::UP;
         MoveTo(Head.x, Head.y-1);   
    }

    VOID CSnake::MoveDown( VOID )
    {
         if(m_nCurrPos == CSnake::UP)    // 相反方向移动,不允许
              return ;

         if( m_bPausing )  // 如果暂停,停止移动
              return ;

         CFood &Head = m_Snake[0];
         m_nCurrPos = CSnake::DOWN;
         MoveTo(Head.x, Head.y+1);   
    }

    BOOL CSnake::IsSnakeDead()
    {
     // 游戏结束有两种情况,一种是蛇碰到自身了,另一种是蛇碰到边界了 
         CFood &Head = m_Snake[0];
     // 蛇头超出界限了
         if( (Head.x >= FRAME_WIDTH ) || (Head.y >= FRAME_HEIGHT) || (Head.x<0) || (Head.y<0))
              return TRUE;
         for(int i=1; i<m_nCurrLength; i++)
         {
              if( Head == m_Snake[i])  // 蛇头遇到自身了
              {
                   return TRUE;
              }
         }
         return FALSE;
    }

    BOOL CSnake::EatFood( CFood &Food )
    // 吃掉食物返回TRUE,否则返回FALSE
    {
         CFood &Head = m_Snake[0];
         if( Food == Head )  // 也就是蛇头碰到Food了
         {
          // 先在蛇尾添加一个节点,等到下一次画出图形时显示
          m_Snake[m_nCurrLength] = m_Snake[m_nCurrLength-1];
          m_nCurrLength++;   // 蛇长度加一
          return TRUE;
         }
         return FALSE;
    }

    VOID CSnake::DrawSnake( CDC *pDC, CPoint poFrameStart )
    {
         CRect rect;
         CBrush brush,*pOldBrush;
         brush.CreateSolidBrush(m_Color);
         pOldBrush = pDC->SelectObject(&brush);  // 选择刷子,刷子颜色为定义颜色

         for(int i=0; i<m_nCurrLength; i++)
         {
          // 设置每一个蛇节点位置,然后画出该节点
              rect.left = poFrameStart.x + m_Snake[i].x * SNAKE_NODE_SIZE;  
              rect.top = poFrameStart.y + m_Snake[i].y * SNAKE_NODE_SIZE;
              rect.right = rect.left + SNAKE_NODE_SIZE;
              rect.bottom = rect.top + SNAKE_NODE_SIZE;

              pDC->Rectangle(&rect);
         }

         // 清理现场
         pDC->SelectObject(pOldBrush);
         brush.DeleteObject();

    }

    BOOL CSnake::IsFoodInSnake(CFood &Food)
    // 如果食物在蛇节点中,返回TRUE,主要是为了生成的食物不在蛇节点中采用
    {
         for(int i=0; i<m_nCurrLength; i++)
         {
              if( Food == m_Snake[i] )        
                    return TRUE;
         }
         return FALSE;
    }

    VOID CSnake::SetColor( COLORREF Color )  // 设置蛇身颜色
    {
         m_Color = Color; 
    }

    VOID CSnake::Moving(VOID)
    {
         if(IsSnakeTuning())   // 如果当前是在换向,那么换向优先,换完后再移动
         {
              SetSnakeTuning(FALSE);
              return ;
         }

          if( m_bPausing )  // 如果暂停,停止移动
              return ;


         switch(m_nCurrPos)     // 依据设置当前移动方向来移动下一个方向
         {
        case CSnake::LEFT:
              MoveLeft();
              break;
        case CSnake::RIGHT:
              MoveRight();
              break;
         case CSnake::UP:
              MoveUp();
              break;
         case CSnake::DOWN:
              MoveDown();
              break;
         }
    }

    INT CSnake::GetSnakeLength(VOID)   // 获取当前蛇自身长度
    {
         return m_nCurrLength;
    }

    VOID CSnake::SetPausing(VOID)
    {
        m_bPausing = TRUE;
    }

    VOID CSnake::ResumePausing(VOID)
    {
       m_bPausing = FALSE;
    }


    /// MyFood.h
    #include "Snake.h"    //下面声明中需要用到CSnake类

    class CMyFood : public CPoint    // 蛇位置记录
    {
    public:
         CMyFood();
         virtual ~CMyFood();
         friend BOOL operator==(CFood& Food, CPoint& point);    // 判断食物是否和当前点在同一个位置
         VOID CreateFood(CSnake &snake);   // 创建食物,参数为蛇,可以避免在蛇自身节点内生成食物
         VOID DrawFood(CDC *pDC, CPoint poFrameStart);       // 依据颜色和边框起始节点画出食物自身
         CMyFood& operator =(CFood &Food);           // 食物点的赋值

         COLORREF m_Color;         // 食物自身的颜色
    };

    /// MyFood.cpp

    CMyFood::CMyFood()
    {
         this->x = 0;   // 食物起始在0,0处
         this->y = 0;
    }

    CMyFood::~CMyFood()
    {

    }

    VOID CMyFood::CreateFood( CSnake &snake )
    {
         CFood Food;    //该食物其实是一个CPoint节点,主要是为了能够判断Food点是否在Snake中设置
         do
         {
              Food.x = rand() % FRAME_WIDTH;
              Food.y = rand() % FRAME_HEIGHT;
        } while( !snake.IsFoodInSnake(Food) ); // 如果不在蛇内部则创建成功,否则重新生成

         this->x = Food.x;
         this->y = Food.y;  // 记录食物位置

    }
    BOOL operator==(CFood& Food, CPoint &point)
    {
           if(Food.x == point.x && Food.y == point.y)
                    return TRUE;
          else
                   return FALSE;
    }

    CMyFood& CMyFood::operator =(CFood &Food)
    {
          this->x = Food.x;
          this->y = Food.y;
          return *this;
    }

    VOID CMyFood::DrawFood( CDC *pDC, CPoint poFrameStart )
    {
         CRect rect;
         CBrush brush,*pOldBrush;
         brush.CreateSolidBrush(m_Color);
         pOldBrush = pDC->SelectObject(&brush);
     
         rect.left   = poFrameStart.x + this->x * SNAKE_NODE_SIZE;  
         rect.top    = poFrameStart.y +  this->y * SNAKE_NODE_SIZE;
         rect.right  = rect.left + SNAKE_NODE_SIZE;
         rect.bottom = rect.top + SNAKE_NODE_SIZE;
         pDC->Rectangle(&rect);
     
         pDC->SelectObject(pOldBrush);
         brush.DeleteObject();
    }

    做完主体设计后,接下来需要做界面内容。
    用VC6.0 创建一个MFC应用的下对话框,我设计的工程名称为:GreedySnake,因此对话框名称为CGreedySnakeDlg,在该对话框类中声明下面内容:
    public:
         CMyFood m_myFood;           // 声明食物,按理说应该声明为一个单件,为简单起见,此处就不那么声明了
         CSnake *m_pSnake;            // 蛇
         CPoint m_poLeftFrame,m_poRightFrame;
         CSnake* GetSnake();
         VOID DeleteSnake();
         CSnake* CreateSnake(COLORREF Color);

         CWinThread pThead;
         BOOL    m_bThreadRunning;
         static UINT ThreadProc(LPVOID lpParam);

    在对话框初始化函数OnInitDialog()中加入下面内容
    ....
         m_pSnake = NULL;
         m_myFood.x = FRAME_WIDTH/2;
         m_myFood.y = FRAME_HEIGHT/2;  //蛇从边框中间开始
     
         m_poLeftFrame.x = 20;
         m_poLeftFrame.y = 20;
         m_poRightFrame.x = m_poLeftFrame.x + FRAME_WIDTH * SNAKE_NODE_SIZE;
         m_poRightFrame.y = m_poLeftFrame.x + FRAME_HEIGHT* SNAKE_NODE_SIZE; 
         m_myFood.m_Color = RGB(244,0,0);  // 红色
         pThread = NULL;
         m_bThreadRuning = FALSE;
    ...

    CSnake* CGreedySnakeDlg::CreateSnake(COLORREF Color)
    // 创建蛇自身
    {
         DeleteSnake();
         m_pSnake = new CSnake(m_myFood,Color);
         return m_pSnake;
    }

    VOID CGreedySnakeDlg::DeleteSnake()
    // 删除蛇自身
    {
         if(m_pSnake)
              delete m_pSnake;
         m_pSnake = NULL;
    }

    CSnake* CGreedySnakeDlg::GetSnake()
    // 获取蛇
    {
         if ( NULL = m_pSnake)
        {
                CreateSnake();
         }
         return m_pSnake;
    }

    void CGreedySnakeDlg::OnStartGame()
    // 游戏开始
    {
         // TODO: Add your command handler code here
         m_bTheadRunning = TRUE;
         pThread = AfxBeginThread(CGreedySnakeDlg::ThreadProc, (LPVOID)this );
    }

    void CGreedySnakeDlg::OnStopGame()
    // 游戏结束
    {
         // TODO: Add your command handler code here
         m_bTheadRunning = FALSE;
         WaitForSingleObject(pThread->m_pMainWnd->GetSafeHwnd(),INFINITE);  // 等待子线程退出
         DeleteSnake();
    }

     

    void CGreedySnakeDlg::OnPausingGame()
    {
     // TODO: Add your command handler code here
         CSnake *pSnake = GetSnake();
         pSnake->SetPausing();
     
    }

    void CGreedySnakeDlg::OnExitApp()
    {
         // TODO: Add your command handler code here
         this->DeleteSnake();
         SendMessage(WM_CLOSE);

    }


    UINT CGreedySnakeDlg::ThreadProc(LPVOID lpParam)
    {
         CGreedySnakeDlg *pDlg = (CGreedySnakeDlg*)lpParam;
         CSnake* pSnake = pDlg->CreateSnake(RGB(0,0,200));   // 默认色为蓝色
         CMyFood  SnakeFood;  // 创建食物
         SnakeFood.CreateFood(*pSnake); 
     
         CClientDC dc(pDlg);
         CPoint &poLeftFrame = pDlg->m_poLeftFrame;
         CPoint &poRightFrame = pDlg->m_poRightFrame;
         CRect rect(poLeftFrame,poRightFrame);
         pSnake->Moving();
     
         INT m_SnakeEated = 0;
         CString strFormate(IDS_FOOD_EATED);
         CString strEated;
         CRect rectTip;

         while(!pSnake->IsSnakeDead() && pDlg->m_bThreadRunning)  // 在蛇还没有死掉时
         {
              if(pSnake->EatFood(SnakeFood))  // 食物被吃掉了
          {
               m_SnakeEated++;
               SnakeFood.CreateFood(*pSnake);  
               strEated.Format("吃掉了 %d 颗食物\r\n",m_SnakeEated);
               //AfxMessageBox(strFormate);
          }
         // 先画背景色,在画当前色
          dc.Rectangle(&rect);
          pSnake->DrawSnake(&dc,  poLeftFrame);
          SnakeFood.DrawFood(&dc, poLeftFrame);
          rectTip.left = poRightFrame.x +20;
          rectTip.top =  20;
          rectTip.right = rectTip.left + 150;
          rectTip.bottom = rectTip.top + 50;
          dc.DrawText(strEated,&rectTip, DT_CENTER);
          Sleep(100);
          pSnake->Moving();
         }
         AfxEndThread(0);
         return 0;
    }

    BOOL CGreedySnakeDlg::PreTranslateMessage(MSG* pMsg)
    {
         // TODO: Add your specialized code here and/or call the base class
         if(pMsg->message == WM_KEYDOWN)
         {
              CSnake *pSnake = GetSnake();
              if( NULL == pSnake)
                   return CDialog::PreTranslateMessage(pMsg);
              switch(pMsg->wParam)
              {
               //   CGreedySnakeView::bKEYDOWN 只针对UP、DOWN、LEFT、RIGHT四种状态有效
              case VK_UP:
                   pSnake->SetSnakeTuning(TRUE);
                   pSnake->MoveUp();
                   break;
              case VK_DOWN:
                   pSnake->SetSnakeTuning(TRUE);
                   pSnake->MoveDown();
                   break;
              case VK_LEFT:
                   pSnake->SetSnakeTuning(TRUE);
                   pSnake->MoveLeft();
                   break;
              case VK_RIGHT:
                   pSnake->SetSnakeTuning(TRUE);
                   pSnake->MoveRight();
                   break;
               case VK_SPACE:
                   if(!bPausing)
                        pSnake->SetPausing();
                   else
                        pSnake->ResumePausing();
                   bPausing = !bPausing;
                   break;
               case 'R':
                       OnSpeeding();
                       break;
                case 'T':
                       OnSlow();
                  default:
                       break;
            } 
         }
     }
         return CDialog::PreTranslateMessage(pMsg);
    }

    // 这是一个贪吃蛇的主体框架,已经可以运行,但还有一些细节可以修改与改进!

    //  这些都是快捷键或者说菜单项中的实现函数,都非常简单

    void CGreedySnakeDlg::OnResumeGame()
    {
         // TODO: Add your command handler code here
         GetSnake()->ResumePausing();
    }

    void CGreedySnakeDlg::OnSetSnakeColor()
    {
         // TODO: Add your command handler code here
         CColorDialog dlgColor;
         dlgColor.DoModal();
         GetSnake()->SetColor(dlgColor.GetColor());
    }

    void CGreedySnakeDlg::OnSetFoodColor()
    {
         // TODO: Add your command handler code here
         CColorDialog dlgColor;
         dlgColor.DoModal();
         m_myFood.SetColor(dlgColor.GetColor());
    }

    void CGreedySnakeDlg::OnSpeeding()
    {
         // TODO: Add your command handler code here
         TimeSleep -= 10;
    }

    void CGreedySnakeDlg::OnSlow()
    {
         // TODO: Add your command handler code here
         TimeSleep += 10;
    }

    BOOL CGreedySnakeDlg::DestroyWindow()
    {
         // TODO: Add your specialized code here and/or call the base class
         OnStopGame();
         return CDialog::DestroyWindow();
    }

    有一个严重的问题的是:
       Cstrcore.h中出现CString类型的内存泄露,总共有2个类对象没有释放,整个程序在ThreadProc函数中存在CString中的变量申明,因此判断为ThreadProc函数中应该不是正常条件下自己退出的,需要ExitThread函数调用退出,或者在主线程退出的时候,并没有及时关闭Thread,需要进行改进!!!!
     
    程序在Windows 2k或XP下VC6.0下编译通过,并可以正常运行!

    [b]ubunoon[/b]
    http://www.cnblogs.com/ubunon
    [size=12] 2008-05-14 [/size]
    [b]All Rights Reversed [/b]
    [b]@=ubunoon[/b]



    /*
    *
    * Copyright (c) 2011 Ubunoon.
    * All rights reserved.
    *
    * email: netubu#gmail.com replace '#' to '@'
    * http://www.cnblogs.com/ubunoon
    * 欢迎来邮件定制各类验证码识别,条码识别,图像处理等软件
    * 推荐不错的珍珠饰品,欢迎订购 * 宜臣珍珠(淡水好珍珠) */
  • 相关阅读:
    大牛思考方式
    web面试题大全
    github上最全的资源教程-前端涉及的所有知识体系
    java switch语句注意的事项
    Lucene Payload 的研究与应用
    hive array、map、struct使用
    黑马程序员--正则表达式
    [置顶] 读源码练内功(一):guava之eventbus
    自定义Java Annotations实例以及用Java Reflection来解析自定义的Annotation
    Solr之NamedList 简单介绍与实例解析
  • 原文地址:https://www.cnblogs.com/ubunoon/p/GreedySnakeRelease001.html
Copyright © 2020-2023  润新知