代码改变世界

贪吃蛇游戏制作

2008-05-14 09:58  ubunoon  阅读(2078)  评论(1编辑  收藏  举报

贪吃蛇游戏是一个比较简单但非常有趣的游戏,因此从程序编写的角度看,这个游戏编写也不是特别困难。
这个游戏是在参考了其他人用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]