
蛇的活动区域是由若干个小方格组成的。当这些小方格呈现灰色时,便表示蛇身。设整个区域由m×n个方格组成,最左上角的方格坐标为(0, 0)。蛇是由若干个邻的方格组成的,将这些方格的坐标依蛇头至蛇尾的次序存入到一个数组中便代表了蛇身。当蛇在游戏区域中“游动”一格时,所对应的数组 *** 作应该是,将新的位置坐标插入到数组头部,同时将数组中最后一个元素删除。这项工作可以用一个一般的数组来完成,但当进行插入 *** 作时需要自己移动数组中的元素;也可以使用CArray来完成这些工作,CArray的成员函数提供了需要的 *** 作,这样做简单一些。
4. 解题步骤
(1)新建工程Snake,在MFC的向导第一步选择Single Document,按Finish结束。
(2)选择ResourceView窗口,打开菜单编辑器,在顶层菜单上添加一个名为“游戏”的d出式菜单,该菜单下再添加一个名为“开始”的子菜单,其ID为ID_GAME_START,如图1所示。
图1 菜单的设计
(3)在ClassWizard中为刚才编辑好的菜单添加消息处理函数。打开ClassWizard,选中Message Maps页。在Class Name中选择CSnakeView,在Object ID中选择ID_GAME_ START,在Messages中选择COMMAND,添加消息处理函数。
(4)在ClassWizard中添加键盘消息处理函数。打开ClassWizard,选中Message Maps页。在Class Name中选择CSnakeView,在Object ID中选择CSnakeView,在Messages中选择WM_KeyDown,添加消息处理函数。
(5)在ClassWizard中定时器消息添加处理函数。打开ClassWizard,选中Message Maps页。在Class Name中选择CSnakeView,在Object ID中选择CSnakeView,在Messages中选择WM_Timer,侍粗局添加消息处理函数。
(6)编辑生成的代码,完成程序。
4. 源程序清单
(1) 选择ClassView窗口,双击CSnakeView类,添加如下成员变量。并添加头文件:
#include <Afxtempl.h>
class CSnakeView : public CView
{
//此处略去若干行由系统生成的代码
private:
void ReDisplay(CPoint pPoint)
void IniGame()
void IniAim()
int m_nLeft, m_nTop, m_nWidth, m_nHeight, m_nSize// 起始坐标,宽/高度老让(格数),每格大小
int m_nDirect// 当前方向
// 1-右,2-左,3-下,4-上
CPoint m_pAim// 当前目标坐标
CArray <CPoint, CPoint >m_aBody// 蛇身
int m_nGameStatus// 游戏状态:0-未开始,1-开始
int m_nCount// 吃掉目标数
int m_nTime, m_nTime1// 用时间
}
(2)在CSnakeView. Cpp文件,添加3个成员函数IniAim、IniGame和ReDiaplay:
// 该函数随机产生一个供蛇吃的目标,如果该目标恰巧与蛇身重合,则重新产生一个
void CSnakeView::IniAim()
{
int uX, uY// 目标位置
while (1)
{
uX=rand ( ) % m_nHeight
uY=rand ( ) % m_nWidth
int uTag = 0// 0-不与蛇身重合,1-重合
for (int i=0i <= m_aBody. GetUpperBound ( )i++)
{
CPoint uPoint = m_aBody. GetAt (i)
if (uPoint. x == uX ||uPoint. y == uY ) // 目标与蛇身重合
{
uTag = 1
break
}
}
if (uTag == 0)
break
}
m_pAim = CPoint (uX, uY)// 产生的目标存放在成员变量中
}
// 该函数对游戏初始化,定义游戏的初始状态
void CSnakeView::IniGame()
{
// 游凳茄戏区域
m_nLeft = 20
m_nTop = 20
m_nWidth = 40
m_nHeight = 30
m_nSize = 10
// 游初始状态
m_nGameStatus = 0
m_nDirect = 1
m_nCount = 0
// 初始化蛇身
m_aBody. RemoveAll ( )
m_aBody. Add ( CPoint (2, 7) )
m_aBody. Add ( CPoint (2, 6) )
m_aBody. Add ( CPoint (2, 5) )
m_aBody. Add ( CPoint (2, 4) )
// 计时器清零
m_nTime = 0
m_nTime1 = 0
// 初始化随机数发生器
srand ( (unsigned) time (NULL) )
// 产生一个目标
IniAim ( )
}
// 刷新游戏区域中pPoint处的一个小方格
void CSnakeView::ReDisplay(CPoint pPoint)
{
InvalidateRect (CRect (m_nLeft + pPoint. y * m_nSize, m_nTop + pPoint. x * m_nSize,
m_nLeft + (pPoint. y + 1) * m_nSize, m_nTop + (pPoint. x + 1) * m_nSize) )
}
(3)修改CSnakeView的构造函数,完成游戏的初始化。
CSnakeView::CSnakeView()
{
IniGame()
}
(4)在OnDraw中加入代码,显示游戏界面。
void CSnakeView::OnDraw(CDC* pDC)
{
CSnakeDoc* pDoc = GetDocument()
ASSERT_VALID(pDoc)
// TODO: add draw code for native data here
// 画游戏区域
pDC ->SelectStockObject (WHITE_BRUSH)
pDC ->Rectangle (CRect (m_nLeft - 1, m_nTop - 1, m_nLeft + m_nWidth * m_nSize + 1,
m_nTop + m_nHeight * m_nSize + 1) )
// 显示当前用时
CString uStr
uStr. Format ("当前用时:% d", m_nTime)
pDC ->TextOut (m_nLeft + m_nWidth * m_nSize + 30, 40, uStr)
// 显示当前得分
uStr. Format ("当前得分: % d", m_nCount)
pDC ->TextOut (m_nLeft + m_nWidth * m_nSize + 30, 140, uStr)
// 显示目标
pDC ->SelectStockObject (LTGRAY_BRUSH)
pDC ->Rectangle (CRect (m_nLeft + m_pAim. y * m_nSize, m_nTop + m_pAim. x * m_nSize,
m_nLeft + (m_pAim. y + 1) * m_nSize, m_nTop + (m_pAim. x + 1 ) *m_nSize ))
// 画蛇
for (int i=0i<= m_aBody. GetUpperBound ()i++)
{
CPoint uPoint = m_aBody. GetAt (i)
pDC ->Rectangle (CRect (m_nLeft + uPoint. y * m_nSize, m_nTop + uPoint. x * m_nSize,
m_nLeft + (uPoint. y + 1) * m_nSize, m_nTop + (uPoint. x + 1) * m_nSize) )
}
}
(5)为游戏菜单下的开始项的消息映射函数添加代码。
void CSnakeView::OnGameStart()
{
// 启动游戏,启动定时器
IniGame ( )
m_nGameStatus = 1
SetTimer (1, 100, NULL)
Invalidate ( )
}
(6)为键盘按键消息处理函数添加代码。
// 根据按下的方向键设置代表不同方向的值
void CSnakeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch (nChar)
{
case 38:
m_nDirect = 4
break
case 40:
m_nDirect = 3
break
case 37:
m_nDirect = 2
break
case 39:
m_nDirect = 1
break
}
CView::OnKeyDown(nChar, nRepCnt, nFlags)
}
(7)为定时器消息处理函数添加代码。
void CSnakeView::OnTimer(UINT nIDEvent)
{
m_nTime1++// 计时
if (m_nTime1 == 10) // 达到1秒
{
m_nTime++
m_nTime1 = 0
Invalidate ( )
}
CPoint uPoint = m_aBody. GetAt (0)// 蛇头的位置
int uTag = 0//是否失败
switch (m_nDirect) // 判断下一步蛇是否出界
{
case 1: // right
uPoint. y++
if (uPoint. y >= m_nWidth)
uTag = 1
break
case 2: // left
uPoint. y--
if (uPoint. y <0)
uTag = 1
break
case 3: // down
uPoint. x++
if (uPoint. x >= m_nHeight)
uTag = 1
break
case 4: // up
uPoint. x--
if (uPoint. x <0)
uTag = 1
break
}
if (uTag ==0) // 判断蛇是否碰到了自身
{
for (int i=0i <= m_aBody.GetUpperBound()i++)
{
CPoint uPoint1 = m_aBody. GetAt (i)
if (uPoint1. x == uPoint. x &&uPoint1.y == uPoint. y)
{
uTag = 1
break
}
}
}
if (uTag == 0)
{
m_aBody. InsertAt (0, uPoint)// 新的蛇头的位置
ReDisplay (uPoint)
if (uPoint. x == m_pAim. x &&uPoint. y == m_pAim. y) // 碰上目标
{
m_nCount++
IniAim ( )
Invalidate ( )
}
else
{
CPoint uPoint1 = m_aBody. GetAt (m_aBody.GetUpperBound ( ) )
m_aBody. RemoveAt (m_aBody.GetUpperBound ( ) )
ReDisplay (uPoint1)
}
}
else // 游戏结束
{
KillTimer(1)
AfxMessageBox("Fail!")
}
CView::OnTimer(nIDEvent)
}
MFC中开线程确实是个麻烦的问题,最大的问题在于开出来的线程函数不能直接使用MFC的一些机制,比如一个基于对话框工程,dlgcpp中写了一个线程函数,那么这个函数不能使用当前dlg类的所有成员,函数,所以直接调用控件变量是不行的,另外获取指针的方法也不行,只有在MFC类的成员函数中才能使用GetDlgItem(IDC_EDIT1),在其他函数中只能使用HWNDGetDlgItem(HWNDhDlg,intnIDDlgItem),使用后GetDlgItem(NULL,IDC_EDIT1)->只d出一个unused当然法还是有的,思路是先全局定义一些变量,在工作者线程中去不断改变变量的值,把变量值反馈给对话框的任高伏务交给OnTimer(),因为你的要求是自动刷新,那么也就是每隔一定时间刷新,那么OnTimer()就必不可少了。我给你个例子,要写的代码非常少就不发工程了,步骤如下建一个基于对话框工程,添加一个按钮,一个editbox,editbox绑定一个control变量c_edit然后在oninitdialog中加intiInstallResultiInstallResult=SetTimer(1,1,NULL)同样是这个cpp文件中的开头加intnum=0(全局)再在全局位置添加线程函数UINTfun(LPVOIDpParam){while(1){++numif(num>=1000)num=0}return0}为按钮添加单击事件,加入代码AfxBeginThread(fun,NULL)最后为对话框添加OnTimer事件,代码如下CStringss.Format(_T("%d"),num)c_edit.SetWindowTextW(s)CDialog::OnTimer(nIDEvent)这个程序运行后OnTimer就已经启动,会不断把num值写入editbox,点击按钮后,fun线程启动,不断累加num,直到1000时归零,也就是editbox不断在0-1000之间滚动其实想想工作者线程中也并不需要MFC机制,能处理一些数据就可以了,标准cc++的库还是可以正常使用的,当然至于不能使用MFC的机制估计很有可能是我没发现用法,比如获取戚逗携指没指针那里虽然只d出一个unused,但是毕竟有这个函数有它的作用的,只不过我不知道用法而已,另外发送消息机制我是实在不会,也不知道能不能正常使用,不过不出意外估计也是不能直接拿来用的,不像mfc那么方便这是开线程的一种思路,我也不知道一般软件开发这种情况是用的什么技术,至于怎么在线程中使用MFC机制,就看你有没有这个兴趣往那个方向去研究了1.算法1.首先,用一个结构体数组来标记蛇的X位置和Y位置,还有每一节的方向。用一变量标识蛇的长度。
2.在蛇非转弯的移动时用定时器来自动移动,不管蛇是哪种形状,都只需在每次移动时先将各节向后移动(蛇尾舍弃,新的蛇尾由蛇尾的上一节代替):如蛇本身为snake[0]到snake[3],就是将snake[0]到snake[2]一起移动到snake[1]到snake[3]: 将 snake[2]的XY坐标赋值snake[3]的XY坐标 ,snake[1]的XY坐标 赋值给snake[2]的XY坐标 ,snake[0]的XY坐标 赋值给snake[1]的XY坐标 。再判断蛇头的方向,然后将蛇头顺着这个方向向前移动一格就是蛇头snake[0]的XY坐标 。而蛇除蛇头外各节的方向由函数SetDirection()来确定(明显此种情况,蛇头的方向不变),SetDirection()的思想是根据蛇的每一节的前一节的相对位置来确定本节的方向。液培(其实这个察肆函数是多余的,真正用到的只有蛇头的方向)。
3.蛇在转弯时,也是各节一次向后移,蛇头的位置顺着转弯的方向移动,方向由转弯方向确定。
4.蛇在吃到食物时,长度加一,蛇头的位置变成食物的位置,方向不变。蛇的本身每节的XY位置都向后移。如蛇本身为snake[0]到snake[3], 就是将snake[0]到snake[3]一起移动到snake[1]到snake[4]。
5.基于对话框的应用程序,响应按键消息需在PreTranslateMessage里,而不是像文档视图模式那样在OnKeyDown里响应。
6.每次蛇在转弯时只能有一种方向按键能响应,即要么左右转,要么上下转。蛇头方向向左或向右时,只能上下转;蛇头方向向上或向下时,只能左右转。
7.食物的位置由rand函数随机产生。
2.添加如下函数和变量
1 void HuaFangGe(int bianChang, int gridShumu) //如在400*400的方格里绘制20*20个格子,则bianChang = 400;gridShumu = 20
2 void InitSnackSite() //初始化蛇的位置
3
4 int snakeLength //表示蛇的长度
5 int foodX //食物的X位置
6 int foodY //食物的Y位闹没唯置
7 bool start //标志是否开始
8 bool reStart //标志是否重新开始
9
10 struct SNAKE
11 {
12 int x
13 int y
14 char direction //某位置的方向为前一个位置相对于该位置的方向,由SetDirection()确定
15 }snake[200]
16
17 void DrawRed(int x, int y) //指定点0*0到20*20,画相应颜色,下同(红头绿身蓝尾)
18 void DrawGreen(int x, int y)
19 void DrawBlue(int x, int y)
20 void DrawBlack(int x, int y) //根据SetFoodXY()所确定的foodX和foodY来画食物。
21
22 void DrawSnakeFood() //根据数组snakeSite数组的标识信息类绘制蛇的形状位置颜色。
23 void SetFoodXY() //随机绘制食物的XY位置
24
25 bool leftRight //确定是否能上下走(蛇本身在上下走,再按上下就无用了)
26 bool upDown //确定是否能左右走(蛇本身在左右走,再按左右就无用了)
27
28 void MoveSite() //蛇移动过程中,设置“除蛇头”外各节的x和y的位置,顺序前移。
29 void SetDirection() //蛇移动过程中,设置“除蛇头”外各节的方向
30
31 void TurnLeft() //当蛇左转时
32 void TurnRight() //当蛇右转时
33 void GoUp() //当蛇向上时
34 void GoDown() //当蛇向下时
可以参考这里:http://www.cnblogs.com/jncpp/archive/2012/07/24/2606908.html
【原创】基于MFC的 贪吃蛇 小游戏的实现,附源码下载
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)