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 timepublic: 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.bmpenum 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 movepublic: 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;}

评论