Windows 版贪吃蛇
--我的第一个windows 程序
一:游戏共六个界面,默认界面如下:
二:游戏简介
1)该游戏界面共提供了六个,您可以自己修改界面。
2)游戏在老版贪吃蛇有了更新,提供了各种不同的果实,不同果实有
不同的作用,详情请看“帮助”菜单里的“游戏规则”,或者在游戏
区点击右键,选择“游戏规则”。
3)为了提高趣味兴,游戏设置了七个级别,每升一级,改变蛇的颜色
并自动切换下一个界面。
三:游戏的实现方法
该游戏虽然是用 C++写的,但并没有用MFC,只是调用了一些API函数
其中蛇的移动,以及果实的随机产生,均使用定时器,另外蛇的喷火
功能也是用定时器实现的,本来是用多线程,但是多线程就是在优先
级最低的情况下运行也超快,如果要用空循环来延时倒不如定时器来
的简洁。
游戏主要参考了skyblue 的 <<Visual C++经典游戏程序设计>>
但是却又有本质上的不同,主要不同如下:
1)类的封装方法,skyblue 的程序设计了两个类,我的设计了三个
(snake,fruit,interface),skyblue 的全局变量太多,而我只设计
了一个 interface 类的全局对象
2)从 bitmap 文件里提取资源,在 skyblue 里的实现非常机械,而我
用一个枚举简洁的实现,并且在位图内容布局改变的情况下,只需
修改少量代码。
3)蛇的移动算法实现也不同,skyblue 用动态内存,个人认为
他的实现方法非常笨拙,^_-
由于本人缺少美术修养,“蛇”就取自 skyblue,界面不是很好,本想
找几张绚丽的图片做为开始和结束,但是没找到合适的,我的搞不出来的
^_^,就随便取了几种颜色,呵呵 !!!
代码很长,下面是三个类的接口代码:
(中英文杂糅的注释希望不会给您的阅读带来不便)
// interface.h: interface for the CInterface class.
//
//////////////////////////////////////////////////////////////////////
#ifndef _CINTERFACE_H_
#define _CINTERFACE_H_
#include <windows.h>
#define VBLOCK_TOTAL 28
#define HBLOCK_TOTAL 28
#define IDT_SNAKE_MOVE WM_USER // “蛇”移动定时器
#define IDT_FRUIT_SET WM_USER+1 // 产生“水果”定时器
#define IDT_FIRE_MOVE WM_USER+2 // 喷火时火种移动定时器
#define IDM_GAME_START WM_USER+3
// 画刷数量,其实就是界面数量,与磁盘位图文件有关
#define MAX_BRUSH 6
// 定义“墙”,“空”,这两个值必须不在枚举类型:BMP_POS,EnumFoodType中
#define BLOCK_WALL 1000
#define BLOCK_NONE 1001
#define MAX_FIRE 10 // 屏幕上同时出现的最多火种数目
// 贪吃蛇移动一步的结果
#define MOVE_HIT_WALL 100
#define MOVE_GET_FRUIT 101
#define MOVE_COMMON 102
#define MOVE_WIN_GAME 103
#define MOVE_LOST_GAME 104
#define MOVE_PAUSE 105
#define MOVE_HIT_BODY 106
// 快捷键对应的虚键码
#define ID_START_GAME 0X53
#define ID_PAUST_GAME 0X44
#define ID_EXIT VK_ESCAPE
#define ID_SOUND_SWITCH 0X46
#define ID_USER_FACE 0X47
#define ID_DEFAULT_FACE 0X48
#define ID_LOW_SPEED_FRUIT 0X42
#define ID_FIRE_FRUIT VK_SPACE
#define ID_RESTART_GAME 0X52
#include "Fruit.h"
#include "snake.h"
#define MAX_FIRE 10 // 一次能同时喷出火的最大数目
// “火”的数据结构
struct FIRETYPE{
// 起始喷火位置
UINT xStart, yStart;
// 当前的绘制位置, 即 m_ptBoard 的两个下标值
UINT x, y;
// 喷火的四个方向,上,下,左,右,用方向键的虚码表示, 值为 -1 表示本次喷火结束
INT dest;
// 当前喷火的组成部分 , 值 为 0, 1, 2, 3
UINT uDrawFirePart;
// 是否是擦除状态,喷到边缘的时候,回擦以恢复屏幕
BOOL bErase;
FIRETYPE& operator=(FIRETYPE& other){
xStart = other.xStart;
yStart = other.yStart;
x = other.x;
y = other.y;
dest = other.dest;
uDrawFirePart = other.uDrawFirePart;
bErase = other.bErase;
return *this;
}
};
// 声音事件定义
enum ENUM_MAKE_SOUND{
SOUND_GET_COMM_FRUIT, SOUND_GET_VIRUS_FRUIT,
SOUND_GET_FIRE_FRUIT, SOUND_GET_LIFE_FRUIT,
SOUND_GET_LOW_SP_FRUIT, SOUND_GET_TH_WALL_FRUIT,
SOUND_GAME_START, SOUND_GAME_OVER,
SOUND_MAKE_FIRE, SOUND_EXIT_GAME,
SOUND_CHANGE_FACE, SOUND_THROUGH_WALL,
SOUND_WIN_GAME, SOUND_LOAD_GAME,
SOUND_ADD_LEVEL,
};
// CInterface will draw all element of game window
class CInterface {
public:
CInterface();
//
//// 整体窗口的相关数据
private:
UINT m_uWndClientWidth;
UINT m_uWndClientHeight;
HWND m_hMainWnd;
HDC m_hdcMemClient;
HDC m_hdcClient;
HINSTANCE m_hInstance;
public:
UINT GetClientWidth();
UINT GetClientHeight();
VOID SethMainWnd(HWND hWnd);
HWND GethMainWnd();
VOID SetHdcMemClient();
HDC GetHdcMemClient();
VOID SetHdcClient(HDC hdc);
HDC GetHdcClient();
VOID SetInstance(HINSTANCE hinst);
HINSTANCE GetInstance();
VOID WaitToContinue(UINT up);
// init game ,load and set resources
VOID GameInit();
VOID RestartGame();
// set userface, if bDefault is TRUE use default style
VOID SetInterfaceStyle(BOOL bDefault=FALSE);
// update the userface when run when needed
VOID UpdateInterface();
VOID LoadRuleBmp(); // 载入游戏规则位图
//
//// 墙的相关数据
public:
const static UINT m_uWallLeft;
const static UINT m_uWallRight;
const static UINT m_uWallTop;
const static UINT m_uWallBottom;
private:
// index 0 save the default wall brush
HBRUSH m_hbrushWall[MAX_BRUSH];
INT m_nCurWallBrush;
INT m_nTotalWallBrush;
BOOL m_bGamePause;
public:
VOID SetWallBrush();
HBRUSH GetNextWallBrush();
//
//// 游戏区域相关
public:
const static UINT m_uBlockWidth ;
const static UINT m_uBlockHeight;
const static UINT m_uVBlockTotal;
const static UINT m_uHBlockTotal;
private:
CSnake m_snake;
CFruit m_fruit;
// index 0 save the default game area brush
HBRUSH m_hbrushGmArea[MAX_BRUSH];
INT m_nCurGmAreaBrush;
INT m_nTotalGmAreaBrush;
UINT m_uFruitType; // Get fruit when snake move
// Save element type in game area
// value may be BLOCK_WALL, enum BMP_POS define in snake.h,
// or enum EnumFoodType define in fruit.h
UINT m_ptBoard[VBLOCK_TOTAL+2][HBLOCK_TOTAL+2];
public:
BOOL m_bGameOver; // data member present the game whether over or not
BOOL IsFruit(UINT y, UINT x);
VOID SetFruitType(UINT uFruit);
VOID SetGmAreaBrush();
HBRUSH GetNextGmAreaBrush();
VOID DrawSnake();
VOID MoveSnake(UINT dest);
// set the value of m_ptBoard's element index by pt
// set value to ps.emPos
VOID SetGameBoard(UINT x, UINT y, UINT uElement);
UINT GetGameBoard(UINT x, UINT y);
VOID ResetGmArea(POINT& rt);
VOID DrawFruit();
VOID SubFruit(EnumFoodType eFruit);
// game area board index to window client coordinate
VOID IndexToScreen(POINT& pt);
//
//// 得分栏相关
private:
// index 0 save the default wall brush
HBRUSH m_hbrushScArea[MAX_BRUSH];
INT m_nCurScAreaBrush;
INT m_nTotalScAreaBrush;
public:
VOID SetScAreaBrush();
HBRUSH GetNextScAreaBrush();
VOID DrawScArea(HBRUSH hbr);
VOID PrintScText();
//
//// 喷火相关
private:
HDC m_hdcMemFireBmp;
UINT m_uCurFireCount; // 当前喷出的火的数目
FIRETYPE m_FireStat[MAX_FIRE]; // 当前火的相关信息
// 设置一个信号,使 MoveAllFire() 和SetFire() 函数不能同时被调用
// 这样可能不行,因为不清楚定时器的实现机理,随便选了一个
// 可能真正需要的是 信号量 机制
BOOL m_bSignal;
public:
VOID LoadFireBmp();
VOID MoveAllFire(); // 定时器调用该函数“移动”所有的火
BOOL SetFire();
VOID DrawFire(UINT i);
//
//// 计器相关
private:
// 蛇移动的初始延时,决定了蛇的移动速度,移动速度可以动态改变
// 延时在: m_uMinTimeDelay 和 m_uInitSnakeMoveDelay 之间
const static UINT m_uInitSnakeMoveDelay;
const static UINT m_uFireMoveDelay; // 火种移动时间的延迟
const static UINT m_uMinTimeDelay; // 移动速度的最小延时
const static UINT m_uSpeedAjust; // 速度调整值
// 水果产生延时,该值不可以改变
const static UINT m_uFruitTimer;
UINT m_uSnakeTimer; // current snake move delay time
public:
VOID DealOnTime(UINT uIdTimer);
VOID HighSnakeMoveSpeed();
BOOL LowSnakeMoveSpeed();
//
//// 音效相关
private:
BOOL m_bSoundOn;
public:
BOOL IsSoundOn();
VOID SetSound(BOOL bFlag);
VOID MakeSound(ENUM_MAKE_SOUND event); // 根据 event 事件播放声音
};
#endif // ~_CINTERFACE_H_
// Fruit.h: interface for the CFruit class.
//
//////////////////////////////////////////////////////////////////////
#ifndef _CFRUIT_H_
#define _CFRUIT_H_
#include <windows.h>
// must bigger than the max element in BPM_POS (define in snake.h)
#define ENUM_FRUIT_START 500
// max the number of each fruit
#define MAX_FRUIT 10
enum EnumFoodType {
// FRUIT_START define in interface.h
VIRUS_FRUIT = ENUM_FRUIT_START,
COMM_FRUIT, LOW_SPEED_FRUIT,
FIRE_FRUIT, LIFE_FRUIT,
THROUGH_WALL_FRUIT,
ENUM_FRUIT_END,
};
class CFruit {
private:
// 蛇得到的果实
UINT m_uCommFruit; // 普通的果实
UINT m_uVirusFruit; // 毒果
UINT m_uLifeFruit; // 生命果
UINT m_uLowSpeedFruit; // 减速果
UINT m_uFireFruit; // 喷火果
UINT m_uThroughWallFruit; // 穿墙果
// 得到果实增加的分数
const static INT m_uCommScore;
const static INT m_uVirusScore;
const static INT m_uLifeScore;
const static INT m_uLowSpeedScore;
const static INT m_uFireScore;
const static INT m_uThroughWallScore;
// 当前游戏区域中各类果实的数目
UINT m_uCommFruitInBoard;
UINT m_uVirusFruitInBoard;
UINT m_uLifeFruitInBoard;
UINT m_uLowSpeedFruitInBoard;
UINT m_uFireFruitInBoard;
UINT m_uThroughWallFruitInBoard;
INT m_nScore;
HDC m_hdcMemFruitBmp; // the bitmap of fruit's compatible
// fruit in bitmap file fruit.bmp postion
POINT m_ptPosInBmp[ENUM_FRUIT_END-ENUM_FRUIT_START];
public:
// Load bitmap file fruit.bmp and init m_hdcMemFruitBmp
VOID LoadFruitBmp();
VOID Init(); // init member data
INT GetScore(); // compute score and return it
VOID GetPosInBmp(); // get fruit's postion in bitmap file: fruit.bmp
VOID DrawFruit(UINT x, UINT y, UINT xx, UINT yy);
VOID SetFruitToBoard(); // set fruit in game board by random mode
VOID AddFruit(EnumFoodType eFruit);
VOID SubFruit(EnumFoodType eFruit);
VOID SubFruitInBoard(EnumFoodType eFruit);
VOID AddFruitInBoard(EnumFoodType eFruit);
UINT GetFruit(EnumFoodType eFruit);
BOOL CheckFruitCount(EnumFoodType efruit);
VOID DrawFruitInBoard(UINT x, UINT y, UINT ft);
};
#endif // ~_CFRUIT_H_
// snake.h: interface for the snake class.
//
//////////////////////////////////////////////////////////////////////
#ifndef _CSNAKE_H_
#define _CSNAKE_H_
#include <windows.h>
// the max length of the snake .
#define MAX_LEN 100
// the enum BMP_POS lie on the bitmap: snake.bmp
enum BMP_POS{
ENUM_SNAKE_START = 0,
TAIL_DOWN = ENUM_SNAKE_START, TAIL_RIGHT, HEAD_UP, HEAD_LEFT, HEAD_RIGHT,
HEAD_DOWN, BODY_DOWN, BODY_RIGHT, DOWN_RIGHT, RIGHT_UP, UP_RIGHT,
RIGHT_DOWN, TAIL_UP, TAIL_LEFT,
ENUM_SNAKE_END,
// make the same picture use different value
// so you can use this value : in switch() case, see function move()
BODY_UP = MAX_LEN + BODY_DOWN, BODY_LEFT = MAX_LEN + BODY_RIGHT,
LEFT_UP = MAX_LEN + DOWN_RIGHT, DOWN_LEFT = MAX_LEN + RIGHT_UP,
LEFT_DOWN = MAX_LEN + UP_RIGHT, UP_LEFT = MAX_LEN + RIGHT_DOWN,
};
// save the snake's each part in window,(x,y) is present an index, see in CInterface.
struct SNAKE_POS{
int x;
int y;
BMP_POS emPos;
SNAKE_POS& operator=(SNAKE_POS& other){
x = other.x;
y = other.y;
emPos = other.emPos;
return *this;
}
};
class CSnake {
private:
// snake part index in game board and in bitmap file,see CInterface : m_ptBoard[]
// the last element will save tail's index , because move snake with no food will
// erase the old tail, if the element's x,y equal -1 tail need't erase
SNAKE_POS m_SnakePos[MAX_LEN+1];
// current length of snake include head and tail
UINT m_uBodyLen;
// snake part in bitmap file : snake.bmp
POINT m_ptPosInBmp[ENUM_SNAKE_END];
// the bitmap of snake 's compatible
HDC m_hdcMemSnakeBmp;
// m_uSnakeLevel present current snake, different level have different snake
UINT m_uSnakeLevel;
BOOL m_bCheckDraw; // USE IN fuction Draw();
UINT m_uCurDest; // destination of the snake move
public:
const static UINT m_uAddLevelNeedScore; // 每升一级所需的分数
const static UINT m_uWinGameNeedLevel; // 赢得游戏需要的级别
public:
VOID SetNewHeadAndNewTail(SNAKE_POS& newhead);
// return snake postion data by index i
SNAKE_POS& GetSnakePos(UINT i);
UINT GetSnakeLevel();
VOID AddSnakeLevel();
// LOAD snake bitmap and init m_hdcMemSnakeBmp
VOID LoadSnakeBmp();
// get snake body in bitmap file: snake.bmp, different level have different snake
VOID GetPosInBmp(UINT level=0);
// Init CSnake data member
VOID Init();
// Draw the snake in screen(window)
VOID SetCheckDraw(BOOL bFlag);
VOID Draw();
INT move(UINT dest);
// return m_bCurDest's value
UINT GetMoveDest();
};
#endif // ~ _CSNAKE_H_
所有代码超过 70 kB, 这里只列出了部分,完整代码下载如下:
链接: windows 版贪吃蛇下载 (不保证长期有效)
编程爱好者 群共享: 28011342 (长期有效)
------江南孤峰 2007--12--7 -----
测试时发现有时喷火不正常,怀疑是互锁机制的问题,修改如下(只对于提供链接的那个,传上去后,已经不在我控制范围内了^_^,群里的已改正)
在文件 Interface.h 中
// 设置一个信号,使 MoveAllFire() 和SetFire() 函数不能同时被调用
LONG m_bSignal; (原来是 BOOL m_bSignal; )
在文件 Interface.cpp 中
//
// 定时器消息处理函数, 两个函数需要修改,改后如下所示:
VOID CInterface::DealOnTime(UINT uIdTimer){
if(uIdTimer==(UINT)IDT_FIRE_MOVE && !InterlockedExchange(&m_bSignal,TRUE)){
MoveAllFire();
InterlockedExchange(&m_bSignal,FALSE); // 解除锁定
}
else if((UINT)IDT_FRUIT_SET==uIdTimer)
m_fruit.SetFruitToBoard();
else{
MoveSnake(m_snake.GetMoveDest());
UpdateInterface();
}
}
//
// 喷火
BOOL CInterface::SetFire(){
SNAKE_POS head = m_snake.GetSnakePos(0);
// 没有喷火果, 或者蛇在边缘,或者火的数目超过最大限制
// 或者定时器正在调用 MoveAllFire() 函数 则不能喷火
if(m_fruit.GetFruit(FIRE_FRUIT)<=0||head.x==1||head.x==HBLOCK_TOTAL||
head.y==1||head.y==VBLOCK_TOTAL||m_uCurFireCount>=MAX_FIRE||
InterlockedExchange(&m_bSignal,TRUE)) // 实现 和 MoveAllFire() 函数的互斥访问
return FALSE;
if(m_uCurFireCount==0){ // 当前火种为0,则处理计时器,否则表示已经处理过定时器了
KillTimer(m_hMainWnd,IDT_SNAKE_MOVE); // 暂停蛇的自动移动
SetTimer(m_hMainWnd,IDT_FIRE_MOVE,m_uFireMoveDelay,(TIMERPROC)NULL);
}
switch(head.emPos){
case HEAD_UP:
m_FireStat[m_uCurFireCount].x = head.x-1;
m_FireStat[m_uCurFireCount].y = head.y-1;
m_FireStat[m_uCurFireCount].dest = VK_UP;
break;
case HEAD_DOWN:
m_FireStat[m_uCurFireCount].x = head.x-1;
m_FireStat[m_uCurFireCount].y = head.y+1;
m_FireStat[m_uCurFireCount].dest = VK_DOWN;
break;
case HEAD_LEFT:
m_FireStat[m_uCurFireCount].x = head.x-1;
m_FireStat[m_uCurFireCount].y = head.y-1;
m_FireStat[m_uCurFireCount].dest = VK_LEFT;
break;
case HEAD_RIGHT:
m_FireStat[m_uCurFireCount].x = head.x+1;
m_FireStat[m_uCurFireCount].y = head.y-1;
m_FireStat[m_uCurFireCount].dest = VK_RIGHT;
break;
default:
return FALSE;
}
m_FireStat[m_uCurFireCount].xStart = head.x; // 记录喷火时的蛇头位置,擦除火时需要该位置
m_FireStat[m_uCurFireCount].yStart = head.y;
m_FireStat[m_uCurFireCount].bErase = FALSE;
m_FireStat[m_uCurFireCount].uDrawFirePart = 0;
m_uCurFireCount++; // 统计火种数目
DrawFire(m_uCurFireCount-1);
InterlockedExchange(&m_bSignal,FALSE); // 解除锁定
if(IsSoundOn())
MakeSound(SOUND_MAKE_FIRE);
return TRUE;
}
评论