VS2010/MFC/对话框程序
一、 定义snake类。初始长度为5,定义枚举DIRECTION表示蛇要移动的方向,初始值为LEFT,CPOINT型数组,表示蛇的组成,每个点都是一个矩形的左上角点的坐标,蛇由snakelength个宽高为10的小正方形方块连接组成。
构造函数中带有两个参数宽和高,用于表明游戏区域的大小,用来确定生成食物的范围和判断出界的条件。
#ifndef _CSNAKE_ #define _CSNAKE_ #include "stdafx.h" //蛇最大长度 #define MAXSIZE 5000 //蛇初始长度 #define INITSIZE 5 enum DIRECTION{ RIGHT = 0, UP, LEFT, DOWN }; class CSnake{ public: CSnake(int width, int height); ~CSnake(); //随机生成食物 void GenerateFood(); //出界,回绕检测 bool CollisionDetect(); //吃到食物 bool EatFood(); //移动蛇 void Move(); public: //snake的长度 int snakelength; //snake的组成 CPoint snake[MAXSIZE]; //食物 CPoint food; //尾巴 CPoint snaketail; //方向 DIRECTION direction; //边届宽 int width; //边界高 int height; }; #endif
详细:
1. 初始化。
CSnake::CSnake(int width, int height) { snakelength = INITSIZE; for (int i = 0; i < snakelength; i++) { snake[i].x = 150 + i*10; snake[i].y = 150; } snaketail = snake[snakelength-1]; direction = LEFT; this->width = width; this->height = height; }
2. 食物生成
随机数产生食物的位置,要注意的是由于蛇上每个点的坐标都为10的倍数,所有产生的食物的坐标也应该是10的倍数,可以先整除10,然后乘以10。
void CSnake::GenerateFood() { for (;;) { food.x = rand() % width; food.y = rand() % height; food.x /= 10; food.y /= 10; food.x *= 10; food.y *= 10; for (int i = 0; i < snakelength; i++) { if (food.x == snake[i].x && food.y == snake[i].y) continue; } break; } }
3. 碰撞检测
bool CSnake::CollisionDetect() { //出界 if (snake[0].x < 0 || snake[0].x >= width || snake[0].y < 0 || snake[0].y >= height) { return true; } //回绕 for (int i = 1; i < snakelength; i++) { if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) { return true; } } return false; }
4. 吃到食物
判断碰到食物后,应该将蛇的长度加1,然后在蛇的尾部加上一个方块,位置为之前尾部上一次的坐标,保存在snaketail中。
bool CSnake::EatFood() { if (snake[0] == food) { snakelength++; snake[snakelength-1] = snaketail; return true; } return false; }
5. 蛇的移动
蛇的移动先移动蛇的身体, 将snake数组所有点(除snake[0])前移一位,采用从后往前移动的方式,所以是倒着写的循环。同时更新蛇尾snaketail的值。
然后再移动蛇头,根据蛇的移动方向direction,改变snake[0]的值。
void CSnake::Move() { //移动身体 for (int i = snakelength-1 ; i > 0; i--) { snake[i].x = snake[i-1].x; snake[i].y = snake[i-1].y; } snaketail = snake[snakelength-1]; //移动头 switch (direction) { case RIGHT: snake[0].x += 10; break; case LEFT: snake[0].x -= 10; break; case UP: snake[0].y -= 10; break; case DOWN: snake[0].y += 10; break; default: break; } }
二、 界面部分
1. 定义
//定时器ID
#define TIMERMOVE 100
//蛇移动速度(越大越慢)
#define SNAKESPEED 200
SNAKESPEED是定时器的周期,单位毫秒,可以控制蛇的移动速度。
2. OnInitDialog()中
有两个尺寸,一个是窗口的,一个是客户区的,两个尺寸间差一个边框的,由于创建窗口时按照窗口大小,而蛇的运动区域为客户区,所以需要创建时需要算好边框的距离。
//获取窗口大小 CRect wRect; GetWindowRect(&wRect); int wWidth = wRect.Width(); int wHeight = wRect.Height(); //获取客户区大小 CRect rect; GetClientRect(&rect); int cWidth = rect.Width(); int cHeight = rect.Height(); //计算边缘大小 int borderWidth = wWidth - cWidth; int borderHeight = wHeight - cHeight;
然后是其他初始化,注意到在SetWindowPos中的两个参数,在400的基础上加上了边框的大小。
//设置窗口位置大小 SetWindowText("snake"); HWND hWnd = ::FindWindow(NULL, "snake"); ::SetWindowPos(hWnd, NULL, 0, 0, 400+borderWidth, 400+borderHeight, SWP_NOZORDER); CenterWindow(); //装载菜单,用来控制游戏的重新开始 m_Menu.LoadMenu(IDR_MENU1); SetMenu(&m_Menu); //创建蛇对象 csnake = new CSnake(rect.Width(), rect.Height()); //生成食物 csnake->GenerateFood(); //开启定时器 SetTimer(TIMERMOVE, SNAKESPEED, 0);
3. OnPaint()中。负责失败,吃到食物的业务逻辑处理和绘制蛇及食物。
CClientDC dc(this); CRect crect; GetClientRect(&crect); //绘制前逻辑处理 if (csnake->CollisionDetect() == true) { KillTimer(TIMERMOVE); MessageBox("游戏失败!"); } if (csnake->EatFood() == true) { csnake->GenerateFood(); } //填充客户区背景 dc.FillRect(crect, &CBrush(RGB(0, 255, 255))); //画蛇 for (int i = 0; i < csnake->snakelength; i++) { CRect rect(csnake->snake[i].x, csnake->snake[i].y, csnake->snake[i].x+10, csnake->snake[i].y+10); dc.Rectangle(rect); //可以给蛇填充背景 //dc.FillRect(rect, &CBrush(RGB(0, 0, 255))); } //画食物 CRect rect(csnake->food.x,csnake->food.y,csnake->food.x+10, csnake->food.y+10); dc.Rectangle(rect); //填充食物背景 dc.FillRect(rect, &CBrush(RGB(255,0,0)));
4. 定时器处理。没到一次时钟,调用一次Move()。然后用Invalidate(true)让窗口重绘。
void CsnakeDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 switch (nIDEvent) { case TIMERMOVE: csnake->Move(); break; default: break; } Invalidate(TRUE); CDialogEx::OnTimer(nIDEvent); }
5. 键盘响应。
void CsnakeDlg::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: 在此添加消息处理程序代码和/或调用默认值 switch (nChar) { case VK_DOWN: if (csnake->direction != UP) csnake->direction = DOWN; break; case VK_LEFT: if (csnake->direction != RIGHT) csnake->direction = LEFT; break; case VK_UP: if (csnake->direction != DOWN) csnake->direction = UP; break; case VK_RIGHT: if (csnake->direction != LEFT) csnake->direction = RIGHT; break; default: break; } CDialogEx::OnKeyUp(nChar, nRepCnt, nFlags); }
6. 添加菜单项控制游戏失败后重新开始。
a. 在资源视图中在下面的文件上右击选“添加资源”,选择Menu,然后“新建”,新建一个“开始”-“新游戏”两级菜单,从属性中读到子菜单的ID,然后在类向导中在命令中搜到ID,添加处理函数,编辑代码,添加代码如下,最后在OnInitDialog()中添加m_Menu.LoadMenu(IDR_MENU1);SetMenu(&m_Menu);
void CsnakeDlg::OnNewgame() { // TODO: 在此添加命令处理程序代码 CRect rect; GetClientRect(&rect); delete csnake; csnake = new CSnake(rect.Width(), rect.Height()); csnake->GenerateFood(); SetTimer(TIMERMOVE, SNAKESPEED, 0); }
(完)