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