正文

Windows 版贪吃蛇2007-12-07 16:57:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/lingdlz/31304.html

分享到:

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;}

阅读(5823) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册