正文

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

阅读(5179) | 评论(0)


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

评论

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