前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >贪吃蛇

贪吃蛇

作者头像
DeROy
发布2020-05-12 14:28:34
8150
发布2020-05-12 14:28:34
举报
文章被收录于专栏:编程学习基地编程学习基地

1

前言

终极目标:打造酷炫贪吃蛇游戏

制作环境:

VS2015(支持VC++2010,VS各个版本)

easyx图形库(稍微改下VC6.0也可以实现)

本来我想将贪吃蛇放到链表的时候拿来做案例,结果

还有好多消息过期查看不了了

我想说的是,安排!

贪吃蛇真正实现的核心代码也就100来行,为了搞一些花里胡哨的东西,我硬是将代码弄成了300多行,但效果是,这游戏还挺好玩的。

2

游戏设计

贪吃蛇的制作思路就是蛇头带动蛇尾移动,主要还是看蛇头和蛇头的下一个位置。蛇头的下一个位置只有空地,墙,食物,和自己的身体。

普通模式:蛇头的下一个位置是墙、自己的身体,游戏gameover!

穿墙模式:蛇头的下一个位置是自己的身体,游戏gameover!蛇头的下一个位置是墙,将下一个位置换成墙另一边的空地(这个时候蛇头带动蛇尾移动就不需要考虑那么多了)。

无敌模式:可穿墙,咬到自己的身体也可以继续游戏(屏蔽了gameover功能)。

我们先来分析蛇头下一个位置的可能结果

  • 空地

蛇头当然是直接过去

代码语言:javascript
复制
case SPACE://直接移动
    map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE;//地图蛇尾所在地置空
    for (int i = SnakeSize - 1; i > 0; i--)            //蛇尾到蛇头整体移动一位
    {
      snake[i] = snake[i - 1];
    }
    map[snake[0].X][snake[0].Y] = SNAKE;            //蛇头置 蛇
    snake[0] = next;                      //将下一个位置赋值给蛇头
    map[snake[0].X][snake[0].Y] = HEAD;              //设置头
    break;

普通模式就直接gameover了,穿墙模式就需要设计蛇头的下一个为墙另一边的空地,然后蛇头带动蛇尾移动过去。

代码语言:javascript
复制
case WALL:
    if (mode)    //模式1模式2可穿墙
    {
      map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE;//蛇尾置空
      for (int i = SnakeSize - 1; i > 0; i--)            //蛇尾到蛇头整体移动一位
      {
        snake[i] = snake[i - 1];
      }
      map[snake[0].X][snake[0].Y] = SNAKE;            //蛇头置 蛇
      switch (SnakeDir)                      //穿墙
      {
      case'A':next.Y = COL - 2; break;
      case 'D':next.Y = 1; break;
      case 'W': next.X = ROW - 2; break;
      case 'S':next.X = 1; break;
      default:
        break;
      }
      snake[0] = next;                      //蛇头移动到新位置
      map[snake[0].X][snake[0].Y] = HEAD;              //新的蛇头所在的位置
    }
    else {
      MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
      exit(0);
    }
    break;
  • 食物

蛇吃掉食物,身体变长,然后重新出现一个食物

代码语言:javascript
复制
case FOOD://食物        蛇尾不变
    for (int i = SnakeSize; i > 0; i--)              //蛇尾到蛇头整体移动一位
    {
      snake[i] = snake[i - 1];                
    }
    map[snake[0].X][snake[0].Y] = SNAKE;            //蛇头 置 蛇
    snake[0] = next;                      //将下一个位置赋值给蛇头
    score++;      //分数加一        
    (SnakeSize)++;    //蛇尺度加一
    map[snake[0].X][snake[0].Y] = HEAD;              //地图上重置蛇头
    addfood();
    break;
  • 身体

普通模式,穿墙模式gameover!无敌模式,就屏蔽掉gameover功能

代码语言:javascript
复制
case SNAKE:
    if (mode == 2)    //模式二无敌
    {
      break;
    }
    else {
      MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
      exit(0);
    }
    break;
  • 还有就是方向,当蛇往右走的时候你不能将方向变成向左。
代码语言:javascript
复制
 case'A':
  case'a':
  case 75:
    if (SnakeDir != 'D') SnakeDir = 'A';  //蛇不能后退
    break;
  • 添加食物

食物出现的地方只能是空地,不能出现在墙上,自己的身体上

代码语言:javascript
复制
void addfood()
{
  int row, col;
  do
  {  
    row = rand() % (ROW - 1) + 1;
    col = rand() % (COL - 1) + 1;
  } while (map[row][col] != SPACE); //当map[row][col]为空地时结束循环
  map[row][col] = FOOD;              //空地变食物
}

大致思路就是这个样子滴,贪吃蛇就是判断蛇头的下一个位置是什么东西,然后分别考虑。需要区分的是地图有一个数组map存放,蛇有另外一个数组snake存放蛇的位置(为了控制蛇的移动)。

3

游戏三部曲

1、加载游戏数据(初始化 init();)

2、绘制图形(绘图 DrawMap();)

3、玩家操作(数据更新 move();ChangeDir();)

代码语言:javascript
复制
init();    //初始化游戏
while(1)
{
 DrawMap();    //绘制游戏
 move();            //数据更新

}

先把大纲列出来

01

主函数 main()

定义两个时间控制移动速度

代码语言:javascript
复制
DWORD t1, t2;

主函数将大致思路拟好

代码语言:javascript
复制
int main()
{
  initgraph(640, 480);  //窗口大小
  init();               //初始化
  while (1)
  {
    DrawMap();
    if (kbhit())
    {
      ChangeDir();
      move();
      t2 = GetTickCount();  //从新获取时间,为了更好的游戏体验
      t1 = t2;
    }
    t2 = GetTickCount();  //不断获取时间,控制移动速度
    if (t2 - t1 > 50)
    {
      move();
      t1 = t2;
    }
  }
  closegraph();
  return 0;
}

然后呢需要定义一些变量来管理

代码语言:javascript
复制
#define ROW 46
#define COL 64
//枚举
enum game
{
  SPACE, WALL, SNAKE, FOOD, HEAD//空地  墙  蛇  食物
};
/************全局变量************/
int mode = 0;      //游戏模式
int score = 0;     //分数
DWORD t1, t2;      //定义两个时间控制移动速度
int map[ROW][COL];    //地图大小
COORD snake[1024];    //蛇      typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;
size_t SnakeSize;    //蛇的尺度    typedef unsigned int     size_t;
char SnakeDir;      //蛇移动方向

02

初始化函数 init()

我们需要初始化那些东西呢!

  1. 初始化地图,将墙给定义出来
  2. 地图上初始化蛇的位置,先来条小蛇3个格子
  3. 初始化蛇的一些属性,长度,方向,还有游戏分数
  4. 添加食物
代码语言:javascript
复制
void init()
{
  srand((unsigned)time(NULL));
  setbkcolor(WHITE);  //设置背景颜色

  memset(map, SPACE, sizeof(map));
  //每一行的 第一个 和 最后一个 是墙
  for (int i = 0; i < ROW; i++)
  {
    map[i][0] = map[i][COL - 1] = WALL;
  }
  //每一列的 第二个 和 倒数第二 个是墙
  for (int j = 1; j < COL - 1; j++)
  {
    map[0][j] = map[ROW - 1][j] = WALL;
  }
  //定义蛇头和蛇的身体
  map[3][5] = HEAD;
  map[3][4] = map[3][3] = SNAKE;
  //初始化蛇
  SnakeSize = 3;  //蛇 长
  SnakeDir = 'D';  //蛇方向向右
  snake[0].X = 3;
  snake[0].Y = 5;
  snake[1].X = 3;
  snake[1].Y = 4;
  snake[2].X = 3;
  snake[2].Y = 3;
  addfood();
}

03

绘图函数 DrawMap()

绘制地图就用填充矩形绘图函数fillrectangle();这样可以填充一些颜色,让蛇看起来很nice,梦凡还将蛇头弄成彩色的,感觉逼味十足。地图上的空地,食物,蛇墙用来枚举,这样表达起来很清楚。

代码语言:javascript
复制
void DrawMap()
{
  BeginBatchDraw();  //开始绘图
  setbkcolor(WHITE);  //设置背景颜色为白色
  settextcolor(RGB(238,0,0));
  cleardevice();    //清屏
  WCHAR arr[10];    //保存成绩
  wsprintf(arr, L"总分:%d", score);  //将成绩格式化输出到字符串arr中 
  outtextxy(0, 0, arr);        //显示成绩
  for (int y = 0; y < ROW; y++)    //y轴方向向下
  {
    for (int x = 0; x < COL; x++)  //x轴方向下上
    {
      switch (map[y][x])
      {
      case SPACE:
        break;
      case WALL:
        setlinecolor(BLACK);  
        setfillcolor(RGB(238, 233, 233));  //灰色
        fillrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
        break;
      case SNAKE:
        setlinecolor(RGB(0, 245, 255));    //绿色 
        setfillcolor(WHITE);
        fillrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
        break;
      case HEAD:
        //画七彩蛇头
        switch (rand() % 7)
        {
        case 0:
          setfillcolor(RGB(255, 0, 0));    //红色 255 0 0
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 1:
          setfillcolor(RGB(255, 165, 0));    //橙  255 165 0 
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 2:
          setfillcolor(RGB(255, 255, 0));    //黄  255 255 0
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 3:
          setfillcolor(RGB(0, 255, 0));    //绿色  0, 255, 0
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 4:
          setfillcolor(RGB(0, 255, 255));    //青  0 255 255
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 5:
          setfillcolor(RGB(0, 0, 255));    //蓝  0 0 255
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        case 6:
          setfillcolor(RGB(160, 32, 240));  //紫  160 32 240
          solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
          break;
        default:
          break;
        }
        break;
      case FOOD:
        setfillcolor(RGB(255, 0, 0));      //红色
        solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
        break;
      default:
        break;
      }
    }
  }
  EndBatchDraw();
}

04

数据更新函数 move()

数据更新,不断获取玩家操作,蛇每隔0.05s移动一次

  • 先来获取玩家操作
代码语言:javascript
复制
void ChangeDir()
{
  switch (getch())
  {
  case'A':
  case'a':
  case 75:
    if (SnakeDir != 'D') SnakeDir = 'A';  //蛇不能后退
    break;
  case'D':
  case'd':
  case 77:
    if (SnakeDir != 'A') SnakeDir = 'D';
    break;
  case'W':
  case'w':
  case 72:
    if (SnakeDir != 'S') SnakeDir = 'W';
    break;
  case'S':
  case's':
  case 80:
    if (SnakeDir != 'W') SnakeDir = 'S';
    break;
  case 32:
    getch();
    break;
  default:
    break;
  }
}
  • 蛇的移动,需要先判断蛇头的下一个位置是什么
代码语言:javascript
复制
 COORD next;    //蛇头的下一个位置
  switch (SnakeDir)
  {
  case'A':
    next.X = snake[0].X;
    next.Y = snake[0].Y - 1;
    break;
  case'W':
    next.X = snake[0].X - 1;
    next.Y = snake[0].Y;
    break;
  case'D':
    next.X = snake[0].X;
    next.Y = snake[0].Y + 1;
    break;
  case'S':
    next.X = snake[0].X + 1;
    next.Y = snake[0].Y;
    break;
  default:
    break;
  }
  • 然后根据下一个位置进行相应的匹配,开头已经说明了各项功能,这里就不列举了。
代码语言:javascript
复制
switch (map[next.X][next.Y])
  {
  case SPACE://直接移动
    break;
  case WALL:
    break;
  case SNAKE:
    break;
  case FOOD://食物        蛇尾不变
    break;
  default:break;
  }

4

函数说明

  • 先来介绍一个easyx提供的结构体COORD,很简单
代码语言:javascript
复制
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
  • 用的还算多的变量 size_t
代码语言:javascript
复制
typedef unsigned int     size_t;  //保证变量为正整数
  • 获取时间的函数 GetTickCount();

返回值为DWORD类型

代码语言:javascript
复制
typedef unsigned long       DWORD;

特点:精准度高

5

程序打包

为什么需要程序打包?

当你的程序有依赖库,各种素材文件时需要打包保证用户不会因为失误删掉一些对程序有影响的文件。

6

优化设计

优化的话就是利用链表代替数组来创作贪吃蛇,数组有局限性,定义了多大就多大,本程序定义的是1024,如果哪位小伙伴分数达到了1024分就会出现Bug。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程学习基地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档