贪吃蛇

1

前言

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

制作环境:

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

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

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

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

我想说的是,安排!

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

2

游戏设计

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

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

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

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

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

  • 空地

蛇头当然是直接过去

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了,穿墙模式就需要设计蛇头的下一个为墙另一边的空地,然后蛇头带动蛇尾移动过去。

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;
  • 食物

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

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功能

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

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

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();)

init();    //初始化游戏
while(1)
{
 DrawMap();    //绘制游戏
 move();            //数据更新

}

先把大纲列出来

01

主函数 main()

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

DWORD t1, t2;

主函数将大致思路拟好

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

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

#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. 添加食物
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,梦凡还将蛇头弄成彩色的,感觉逼味十足。地图上的空地,食物,蛇墙用来枚举,这样表达起来很清楚。

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移动一次

  • 先来获取玩家操作
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;
  }
}
  • 蛇的移动,需要先判断蛇头的下一个位置是什么
 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;
  }
  • 然后根据下一个位置进行相应的匹配,开头已经说明了各项功能,这里就不列举了。
switch (map[next.X][next.Y])
  {
  case SPACE://直接移动
    break;
  case WALL:
    break;
  case SNAKE:
    break;
  case FOOD://食物        蛇尾不变
    break;
  default:break;
  }

4

函数说明

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

返回值为DWORD类型

typedef unsigned long       DWORD;

特点:精准度高

5

程序打包

为什么需要程序打包?

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

6

优化设计

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

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:DeRoy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 学生信息管理系统(管理员)

    我一直以为学生信息管理系统是开源的,网上一搜一大把的那种。毕竟这种程序学完C之后都可以自己写一个,只有界面好看与否的问题。

    DeROy
  • printf不一样的玩法

    DeROy
  • 线性表

    DeROy
  • Swift3.0 - 异常错误

    酷走天涯
  • 2-物联网开发标配方案(51单片机程序介绍+WIFI程序介绍)

    杨奉武
  • 【开源】简单4步搞定QQ登录,无需什么代码功底 下 ~ Net程序员的福利

    【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】下 ~ Net程序员的福利 上一节我们通过通用封装说了下QQ登录的申请和通用讲解,【开源】简单4步搞...

    逸鹏
  • 远程定位追踪联网车辆以及利用思路分析

    暑假到了,在家闲着无聊,想着考个驾照吧,到了驾校后报了名,看着其他学员在模拟考试,车里面好像有个工控机,看到车刚压线了,那个工控机就发出声音”车轮扎道路边缘线,...

    FB客服
  • 渗透测试SHELL语句参考

    SQL注入一句话: select '<? php eval($_POST[xiaobai]);?>' into outfile 'd:/wwwroot/1....

    奶糖味的代言
  • Redis数据库安装

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

    JiekeXu之路
  • 2019年学习Python-day3作业

    qq317062516

扫码关注云+社区

领取腾讯云代金券