手把手教你实现贪吃蛇(下)

简介: 手把手教你实现贪吃蛇(下)

手把手教你实现贪吃蛇(上):https://developer.aliyun.com/article/1389416

核心逻辑实现分析(重点)

       咱们从三个方面来讲解贪吃蛇的核心逻辑分析:test.c主函数,snake.h包含头文件,snake.c主函数的实现,跟我们玩链表差不多,都是老套路了那贪吃蛇的实现正式上路咯,启程。

🌙snake.h头文件的实现

       像头文件这个东西必然是要包含一些头文件的,当然这只是它的一部分作用,像我们定义的链表,为了在各个源文件都能使用,我们就在头文件实现链表。如果函数很多,就把函数头包函数在头文件中,方便调用,那咱们看看在snake.h中有啥?



//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.h>
//定义图案
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//初始化蛇的位置
#define POS_X 24
#define POS_Y 5
//键位的移动
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//枚举键位
enum  DIRECTION
{
  UP = 1,//上
  DOWN,//下
  LEFT,//左
  RIGHT//右
};
//枚举游戏状态
enum GAME_STATUS
{
  OK,//正常运行
  END_NORMAL,//按ESC退出
  KILL_BY_WALL,
  KILL_BY_SELF
};
//贪吃蛇每个节点的描述
typedef struct SnakeNode
{
  int x;
  int y;
  struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;
//贪吃蛇组成
typedef struct Snake
{
  pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
  pSnakeNode _pFood;//指向食物结点的指针
  int _Score;//贪吃蛇累计的总分
  int _FoodWeight;//一个食物的分数
  int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
  enum DIRECTION _Dir;//描述蛇的方向
  enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);
//定位坐标
void SetPos(short x, short y);
//游戏开始的欢迎界面
void WelComeToGame();
//打印地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏的正常运行
void GameRun(pSnake ps);
//打印帮助信息
void PrintHelpInfo();
//游戏暂定和恢复
void Pause();
//蛇的移动
void SnakeMove(pSnake ps);
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);
//蛇是否撞墙
void KillByWall(pSnake ps);
//蛇是否自杀
void KillBySelf(pSnake ps);
//游戏结束后的善后处理
void GameEnd(pSnake ps);

🌙snake.c源文件的实现

       在这个文件中咱们实现贪吃蛇的函数:实现时当然需要包含snake.h的文件啦,需要用头文件里面的链表等.....

🌠游戏开始 - 初始化游戏


//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{
  //设置控制台的大小
  system("mode con cols=100 lines=30");
  system("title 贪吃蛇");
  //光标影藏掉
  HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //影藏光标操作
  CONSOLE_CURSOR_INFO CursorInfo;
  GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  CursorInfo.bVisible = false; //隐藏控制台光标
  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
  //打印欢迎界面
  WelComeToGame();
  //创建地图
  CreateMap();
  //初始化贪食蛇
  InitSnake(ps);
  //创建食物
  CreateFood(ps);
}
💫设置光标的坐标

       光标的移动需要多次实现,为了我们方便使用移动光标位置,所以我们直接实现这个函数。不会有人跟我说光标是啥吧,看我打不死你,看到没:

设置光标位置在win32 API已经介绍咯。

//设置光标的坐标
void SetPos(short x, short y)
{
  COORD pos = { x, y };
  HANDLE hOutput = NULL;
  //获取标准输出的句柄(用来标识不同设备的数值)
  hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //设置标准输出上光标的位置为pos
  SetConsoleCursorPosition(hOutput, pos);
}
💫打印欢迎界面

其实这个函数大家可以自己设置,把欢迎界面整的好看些,像博主就比较懒就简单设计一下咯。

//打印欢迎界面
void WelComeToGame()
{
  //定位光标
  SetPos(40, 14);
  printf("欢迎来到贪吃蛇小游戏");
  SetPos(40, 25);
  system("pause");//pause是暂停
  system("cls");
  SetPos(20, 14);
  printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
  SetPos(40, 25);
  system("pause");
  system("cls");
}
💫创建地图/初始化贪食蛇

大家眼睛擦亮点,计算坐标时不要计算错了,可以根据自己设计自己喜欢的图:

       切记一定要把蛇初始状态需要是偶数倍:初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有一半儿出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。

//创建地图
void CreateMap()
{
  //上
  SetPos(0, 0);
  int i = 0;
  for (i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
  //下
  SetPos(0, 26);
  for (i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
  //左
  for (i = 1; i <= 25; i++)
  {
    SetPos(0, i);
    wprintf(L"%lc", WALL);
  }
  //右
  for (i = 1; i <= 25; i++)
  {
    SetPos(56, i);
    wprintf(L"%lc", WALL);
  }
}
//初始化贪食蛇
void InitSnake(pSnake ps)
{
  //初始化蛇身 0 0 0 0 0 
  pSnakeNode cur = NULL;
  //循环5个节点
  for (int i = 0; i < 5; i++)
  {
    //开辟空间
    cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    //判断是否开辟成功
    if (cur == NULL)
    {
      perror("InitSnake:malloc");
      exit(-1);
    }
    //初始化蛇的位置在(24,5)
    cur->x = POS_X + i * 2;
    cur->y = POS_Y;
    cur->next = NULL;
    //开始链接
    //头插
    if (ps->_pSnake == NULL)
    {
      ps->_pSnake = cur;
    }
    else
    {
      cur->next = ps->_pSnake;
      ps->_pSnake = cur;
    }
  }
  //打印蛇身
  cur = ps->_pSnake;//指向贪吃蛇头结点的指针
  while (cur)
  {
    //定位坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //指向下一个节点
    cur = cur->next;
  }
  //对贪吃蛇的每个组成都初始化
  ps->_Status = OK;//游戏的状态
  ps->_Score = 0;//贪吃蛇累计的总分
  ps->_pFood = NULL;//指向食物结点的指针
  ps->_SleepTime = 200;//每走一步休息的时间
  ps->_FoodWeight = 10;//一个食物的分数
  //(我们默认蛇开始向右走)
  ps->_Dir = RIGHT;//描述蛇的方向
}
💫创建食物

       关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。

在创建食物时我们需要注意一些事项:小本本拿好咯

  • 食物可能与创建的蛇相同:就必须从新创建食物。(这里采用again)
  • 食物需要开辟空间的:方便和蛇链接嘛,毕竟是单链表
  • 食物的一些初始化
//创建食物
void CreateFood(pSnake ps)
{
  //初始化
  int x = 0;
  int y = 0;
again:
  do
  {
    x = rand() % 53 + 2;
    y = rand() % 25 + 1;
  } while (x % 2 != 0);//x坐标必须是2的倍数
  //坐标不能和蛇的身体冲突
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    //比较坐标
    if (cur->x == x && cur->y == y)
    {
      goto again;
    }
    cur = cur->next;
  }
  //给食物开辟空间
  pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
  //判断
  if (pFood == NULL)
  {
    perror("CreateFood()::malloc()");
    return;
  }
  //初始化
  pFood->x = x;
  pFood->y = y;
  ps->_pFood = pFood;
  //打印食物
  SetPos(x, y);
  wprintf(L"%lc", FOOD);
}
🌠游戏运行 - 游戏的正常运行过程


//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{
  //打印帮助信息
  PrintHelpInfo();
  //循环方向,直到方向正确
  do
  {
    //打印得分
    SetPos(64, 10);
    printf("得分:%05d", ps->_Score);
    SetPos(64, 11);
    printf("每个食物的分数:%2d", ps->_FoodWeight);
    //判断蛇方向,
    if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
    {
      ps->_Dir = UP;
    }
    else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
    {
      ps->_Dir = DOWN;
    }
    else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
    {
      ps->_Dir = LEFT;
    }
    else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
    {
      ps->_Dir = RIGHT;
    }
    else if (KEY_PRESS(VK_ESCAPE))
    {
      ps->_Status = END_NORMAL;
      break;
    }
    else if (KEY_PRESS(VK_SPACE))
    {
      //暂停蛇移动
      Pause();
    }
    else if (KEY_PRESS(VK_F3))//加速
    {
      if (ps->_SleepTime >= 80)
      {
        ps->_SleepTime -= 30;
        ps->_FoodWeight += 2;
      }
    }
    else if (KEY_PRESS(VK_F4))//减速
    {
      if (ps->_SleepTime < 320)
      {
        ps->_SleepTime += 30;
        ps->_FoodWeight -= 2;
      }
    }
    //蛇动的时间
    Sleep(ps->_SleepTime);
    //蛇的移动
    SnakeMove(ps);
  } while (ps->_Status == OK);
}
💫打印帮助信息

这个函数根据自己的需要而写,这个就是看大家的创意咯

//打印帮助信息
void PrintHelpInfo()
{
  SetPos(64, 15);
  printf("1.不能撞墙,不能咬到自己");
  SetPos(64, 16);
  printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
  SetPos(64, 17);
  printf("3.F3加速,F4减速");
  SetPos(64, 18);
  printf("4.ESC-退出, 空格-暂停游戏");
  SetPos(64, 20);
  printf("旧言专用版权");
}
💫判断蛇头到达的坐标处是否是食物

这个函数其实很挫,判断一下x和y坐标就行。

//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
  if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}
💫吃掉食物

直接就是头插法:不会的打板子

吃完食物记得创建食物

//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{
  //头插(把食物当做蛇头)
  pnext->next = ps->_pSnake;
  ps->_pSnake = pnext;
  //打印蛇
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    //找坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //找下一个节点
    cur = cur->next;
  }
  //释放食物空间
  free(ps->_pFood);
  //加分数
  ps->_Score += ps->_FoodWeight;
  //创建食物
  CreateFood(ps);//新创建食物
}
💫不吃食物

运转到下一个坐标,开始覆盖,用头插,把前面的蛇尾置为空。

//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
  //头插
  pnext->next = ps->_pSnake;
  ps->_pSnake = pnext;
  //打印蛇身
  pSnakeNode cur = ps->_pSnake;
  while (cur->next->next)//找最后一个节点就不再打印
  {
    //找坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //找下一个节点
    cur = cur->next;
  }
  //本来的尾的图案需要覆盖成空格
  SetPos(cur->next->x, cur->next->y);
  printf("  ");
  //释放空间
  free(cur->next);
  cur->next = NULL;
}
💫蛇是否撞墙

这个函数还是比较简单的,判断墙体的坐标是否和蛇移动到的坐标相同

//蛇是否自杀
void KillBySelf(pSnake ps)
{
  //遍历一遍蛇的坐标就行
  pSnakeNode cur = ps->_pSnake->next;
  while (cur)
  {
    if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
    {
      //游戏状态为自杀
      ps->_Status = KILL_BY_SELF;
    }
    //找下一个节点
    cur = cur->next;
  }
}
💫蛇的移动

蛇既然要移动到下一个坐标,所以需要开辟下一个位置的空间,再判断蛇是否到食物。

等到蛇移动到下一个坐标再判断蛇是否撞墙自杀。

//蛇的移动
void SnakeMove(pSnake ps)
{
  //开辟蛇移动到下一个格子的空间
  pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  //判断
  if (pNext == NULL)
  {
    perror("SnakeMove()::malloc()");
    exit(-1);
  }
  pNext->next = NULL;
  //蛇移动方向
  switch (ps->_Dir)
  {
  case UP:
    pNext->x = ps->_pSnake->x;
    pNext->y = ps->_pSnake->y - 1;
    break;
  case DOWN:
    pNext->x = ps->_pSnake->x;
    pNext->y = ps->_pSnake->y + 1;
    break;
  case LEFT:
    pNext->x = ps->_pSnake->x - 2;
    pNext->y = ps->_pSnake->y;
    break;
  case RIGHT:
    pNext->x = ps->_pSnake->x + 2;
    pNext->y = ps->_pSnake->y;
    break;
  }
  //判断蛇头到达的坐标处是否是食物
  if (NextIsFood(ps, pNext))
  {
    //吃掉食物
    EatFood(ps, pNext);
  }
  else
  {
    //不吃食物
    NoFood(ps, pNext);
  }
  //蛇是否撞墙
  KillByWall(ps);
  //蛇是否自杀
  KillBySelf(ps);
}
💫暂停蛇移动

让蛇可以有视觉效果,打印时,休息一会儿。

//暂停蛇移动
void Pause()
{
  while (1)
  {
    Sleep(100);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
🌠游戏结束 - 游戏善后(释放资源)

等于释放内存,这个表示我们熟的来:

//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{
  //判断游戏状态
  SetPos(20, 12);
  switch (ps->_Status)
  {
  case END_NORMAL:
    printf("您主动退出游戏\n");
    break;
  case KILL_BY_SELF:
    printf("自杀了,游戏结束\n");
    break;
  case KILL_BY_WALL:
    printf("撞墙了,游戏结束\n");
    break;
  }
  //释放蛇身的结点
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    pSnakeNode del = cur;
    cur = cur->next;
    free(del);
  }
  ps->_pSnake = NULL;
}

🌙test.c源文件的实现

直接上代码好叭

//包含头文件
#include"snake.h"
void test()
{
  int ch = 0;
  do
  {
    Snake snake = { 0 };//创建了贪吃蛇
    //1. 游戏开始 - 初始化游戏
    GameStart(&snake);
    //2. 游戏运行 - 游戏的正常运行过程
    GameRun(&snake);
    //3. 游戏结束 - 游戏善后(释放资源)
    GameEnd(&snake);
    SetPos(20, 18);
    printf("再来一局吗?(Y/N):");
    ch = getchar();
    getchar();// 清理掉\n
  } while (ch == 'Y' || ch == 'y');
  SetPos(0, 27);
}
int main()
{
  //设置程序适应本地环境
  setlocale(LC_ALL, "");//设置语言环境
  srand((unsigned int)time(NULL));//游戏睡眠状态
  //调用函数
  test();
  return 0;
}

🌞总代码

💧snake.c
#define _CRT_SECURE_NO_WARNINGS 1
//包含头文件
#include"snake.h"
//设置光标的坐标
void SetPos(short x, short y)
{
  COORD pos = { x, y };
  HANDLE hOutput = NULL;
  //获取标准输出的句柄(用来标识不同设备的数值)
  hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //设置标准输出上光标的位置为pos
  SetConsoleCursorPosition(hOutput, pos);
}
//打印欢迎界面
void WelComeToGame()
{
  //定位光标
  SetPos(40, 14);
  printf("欢迎来到贪吃蛇小游戏");
  SetPos(40, 25);
  system("pause");//pause是暂停
  system("cls");
  SetPos(20, 14);
  printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
  SetPos(40, 25);
  system("pause");
  system("cls");
}
//创建地图
void CreateMap()
{
  //上
  SetPos(0, 0);
  int i = 0;
  for (i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
  //下
  SetPos(0, 26);
  for (i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
  //左
  for (i = 1; i <= 25; i++)
  {
    SetPos(0, i);
    wprintf(L"%lc", WALL);
  }
  //右
  for (i = 1; i <= 25; i++)
  {
    SetPos(56, i);
    wprintf(L"%lc", WALL);
  }
}
//初始化贪食蛇
void InitSnake(pSnake ps)
{
  //初始化蛇身 0 0 0 0 0 
  pSnakeNode cur = NULL;
  //循环5个节点
  for (int i = 0; i < 5; i++)
  {
    //开辟空间
    cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    //判断是否开辟成功
    if (cur == NULL)
    {
      perror("InitSnake:malloc");
      exit(-1);
    }
    //初始化蛇的位置在(24,5)
    cur->x = POS_X + i * 2;
    cur->y = POS_Y;
    cur->next = NULL;
    //开始链接
    //头插
    if (ps->_pSnake == NULL)
    {
      ps->_pSnake = cur;
    }
    else
    {
      cur->next = ps->_pSnake;
      ps->_pSnake = cur;
    }
  }
  //打印蛇身
  cur = ps->_pSnake;//指向贪吃蛇头结点的指针
  while (cur)
  {
    //定位坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //指向下一个节点
    cur = cur->next;
  }
  //对贪吃蛇的每个组成都初始化
  ps->_Status = OK;//游戏的状态
  ps->_Score = 0;//贪吃蛇累计的总分
  ps->_pFood = NULL;//指向食物结点的指针
  ps->_SleepTime = 200;//每走一步休息的时间
  ps->_FoodWeight = 10;//一个食物的分数
  //(我们默认蛇开始向右走)
  ps->_Dir = RIGHT;//描述蛇的方向
}
//创建食物
void CreateFood(pSnake ps)
{
  //初始化
  int x = 0;
  int y = 0;
again:
  do
  {
    x = rand() % 53 + 2;
    y = rand() % 25 + 1;
  } while (x % 2 != 0);//x坐标必须是2的倍数
  //坐标不能和蛇的身体冲突
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    //比较坐标
    if (cur->x == x && cur->y == y)
    {
      goto again;
    }
    cur = cur->next;
  }
  //给食物开辟空间
  pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
  //判断
  if (pFood == NULL)
  {
    perror("CreateFood()::malloc()");
    return;
  }
  //初始化
  pFood->x = x;
  pFood->y = y;
  ps->_pFood = pFood;
  //打印食物
  SetPos(x, y);
  wprintf(L"%lc", FOOD);
}
//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{
  //设置控制台的大小
  system("mode con cols=100 lines=30");
  system("title 贪吃蛇");
  //光标影藏掉
  HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //影藏光标操作
  CONSOLE_CURSOR_INFO CursorInfo;
  GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  CursorInfo.bVisible = false; //隐藏控制台光标
  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
  //打印欢迎界面
  WelComeToGame();
  //创建地图
  CreateMap();
  //初始化贪食蛇
  InitSnake(ps);
  //创建食物
  CreateFood(ps);
}
//打印帮助信息
void PrintHelpInfo()
{
  SetPos(64, 15);
  printf("1.不能撞墙,不能咬到自己");
  SetPos(64, 16);
  printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
  SetPos(64, 17);
  printf("3.F3加速,F4减速");
  SetPos(64, 18);
  printf("4.ESC-退出, 空格-暂停游戏");
  SetPos(64, 20);
  printf("旧言专用版权");
  //getchar();
}
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
  if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext)
{
  //头插(把食物当做蛇头)
  pnext->next = ps->_pSnake;
  ps->_pSnake = pnext;
  //打印蛇
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    //找坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //找下一个节点
    cur = cur->next;
  }
  //释放食物空间
  free(ps->_pFood);
  //加分数
  ps->_Score += ps->_FoodWeight;
  //创建食物
  CreateFood(ps);//新创建食物
}
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
  //头插
  pnext->next = ps->_pSnake;
  ps->_pSnake = pnext;
  //打印蛇身
  pSnakeNode cur = ps->_pSnake;
  while (cur->next->next)//找最后一个节点就不再打印
  {
    //找坐标
    SetPos(cur->x, cur->y);
    //打印
    wprintf(L"%lc", BODY);
    //找下一个节点
    cur = cur->next;
  }
  //本来的尾的图案需要覆盖成空格
  SetPos(cur->next->x, cur->next->y);
  printf("  ");
  //释放空间
  free(cur->next);
  cur->next = NULL;
}
//蛇是否撞墙
void KillByWall(pSnake ps)
{
  if (ps->_pSnake->x == 0 ||
    ps->_pSnake->x == 56 ||
    ps->_pSnake->y == 0 ||
    ps->_pSnake->y == 26)
    ps->_Status = KILL_BY_WALL;
}
//蛇是否自杀
void KillBySelf(pSnake ps)
{
  //遍历一遍蛇的坐标就行
  pSnakeNode cur = ps->_pSnake->next;
  while (cur)
  {
    if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
    {
      //游戏状态为自杀
      ps->_Status = KILL_BY_SELF;
    }
    //找下一个节点
    cur = cur->next;
  }
}
//蛇的移动
void SnakeMove(pSnake ps)
{
  //开辟蛇移动到下一个格子的空间
  pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  //判断
  if (pNext == NULL)
  {
    perror("SnakeMove()::malloc()");
    exit(-1);
  }
  pNext->next = NULL;
  //蛇移动方向
  switch (ps->_Dir)
  {
  case UP:
    pNext->x = ps->_pSnake->x;
    pNext->y = ps->_pSnake->y - 1;
    break;
  case DOWN:
    pNext->x = ps->_pSnake->x;
    pNext->y = ps->_pSnake->y + 1;
    break;
  case LEFT:
    pNext->x = ps->_pSnake->x - 2;
    pNext->y = ps->_pSnake->y;
    break;
  case RIGHT:
    pNext->x = ps->_pSnake->x + 2;
    pNext->y = ps->_pSnake->y;
    break;
  }
  //判断蛇头到达的坐标处是否是食物
  if (NextIsFood(ps, pNext))
  {
    //吃掉食物
    EatFood(ps, pNext);
  }
  else
  {
    //不吃食物
    NoFood(ps, pNext);
  }
  //蛇是否撞墙
  KillByWall(ps);
  //蛇是否自杀
  KillBySelf(ps);
}
//暂停蛇移动
void Pause()
{
  while (1)
  {
    Sleep(100);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{
  //打印帮助信息
  PrintHelpInfo();
  //循环方向,直到方向正确
  do
  {
    //打印得分
    SetPos(64, 10);
    printf("得分:%05d", ps->_Score);
    SetPos(64, 11);
    printf("每个食物的分数:%2d", ps->_FoodWeight);
    //判断蛇方向,
    if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
    {
      ps->_Dir = UP;
    }
    else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
    {
      ps->_Dir = DOWN;
    }
    else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
    {
      ps->_Dir = LEFT;
    }
    else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
    {
      ps->_Dir = RIGHT;
    }
    else if (KEY_PRESS(VK_ESCAPE))
    {
      ps->_Status = END_NORMAL;
      break;
    }
    else if (KEY_PRESS(VK_SPACE))
    {
      //暂停蛇移动
      Pause();
    }
    else if (KEY_PRESS(VK_F3))//加速
    {
      if (ps->_SleepTime >= 80)
      {
        ps->_SleepTime -= 30;
        ps->_FoodWeight += 2;
      }
    }
    else if (KEY_PRESS(VK_F4))//减速
    {
      if (ps->_SleepTime < 320)
      {
        ps->_SleepTime += 30;
        ps->_FoodWeight -= 2;
      }
    }
    //蛇动的时间
    Sleep(ps->_SleepTime);
    //蛇的移动
    SnakeMove(ps);
  } while (ps->_Status == OK);
}
//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{
  //判断游戏状态
  SetPos(20, 12);
  switch (ps->_Status)
  {
  case END_NORMAL:
    printf("您主动退出游戏\n");
    break;
  case KILL_BY_SELF:
    printf("自杀了,游戏结束\n");
    break;
  case KILL_BY_WALL:
    printf("撞墙了,游戏结束\n");
    break;
  }
  //释放蛇身的结点
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    pSnakeNode del = cur;
    cur = cur->next;
    free(del);
  }
  ps->_pSnake = NULL;
}
💧snake.h
#pragma once
//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.h>
//定义图案
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//初始化蛇的位置
#define POS_X 24
#define POS_Y 5
//键位的移动
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//枚举键位
enum  DIRECTION
{
  UP = 1,//上
  DOWN,//下
  LEFT,//左
  RIGHT//右
};
//枚举游戏状态
enum GAME_STATUS
{
  OK,//正常运行
  END_NORMAL,//按ESC退出
  KILL_BY_WALL,
  KILL_BY_SELF
};
//贪吃蛇每个节点的描述
typedef struct SnakeNode
{
  int x;
  int y;
  struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;
//贪吃蛇组成
typedef struct Snake
{
  pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
  pSnakeNode _pFood;//指向食物结点的指针
  int _Score;//贪吃蛇累计的总分
  int _FoodWeight;//一个食物的分数
  int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
  enum DIRECTION _Dir;//描述蛇的方向
  enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);
//定位坐标
void SetPos(short x, short y);
//游戏开始的欢迎界面
void WelComeToGame();
//打印地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏的正常运行
void GameRun(pSnake ps);
//打印帮助信息
void PrintHelpInfo();
//游戏暂定和恢复
void Pause();
//蛇的移动
void SnakeMove(pSnake ps);
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);
//蛇是否撞墙
void KillByWall(pSnake ps);
//蛇是否自杀
void KillBySelf(pSnake ps);
//游戏结束后的善后处理
void GameEnd(pSnake ps);
💧test.c
#define _CRT_SECURE_NO_WARNINGS 1
//包含头文件
#include"snake.h"
void test()
{
  int ch = 0;
  do
  {
    Snake snake = { 0 };//创建了贪吃蛇
    //1. 游戏开始 - 初始化游戏
    GameStart(&snake);
    //2. 游戏运行 - 游戏的正常运行过程
    GameRun(&snake);
    //3. 游戏结束 - 游戏善后(释放资源)
    GameEnd(&snake);
    SetPos(20, 18);
    printf("再来一局吗?(Y/N):");
    ch = getchar();
    getchar();// 清理掉\n
  } while (ch == 'Y' || ch == 'y');
  SetPos(0, 27);
}
int main()
{
  //设置程序适应本地环境
  setlocale(LC_ALL, "");//设置语言环境
  srand((unsigned int)time(NULL));//游戏睡眠状态
  //调用函数
  test();
  return 0;
}

🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

目录
相关文章
|
C++
学习C++笔记385
C++ 预处理器
59 0
|
编译器 C++
学习C++笔记378
C++ 预处理器
52 0
|
C++
学习C++笔记363
C++ 命名空间
65 0
|
存储 C++
学习C++笔记349
C++ 动态内存
51 0
|
C++
学习C++笔记309
C++ 多态
66 0
|
C++
学习C++笔记298
C++ 重载运算符和重载函数
63 0
|
C++
学习C++笔记292
C++ 继承
71 0
|
前端开发 C++
学习C++笔记265
C++ 数据结构
72 0
|
C++
学习C++笔记223
C++ 字符串
68 0
|
C++
学习C++笔记224
C++ 字符串
79 0