贪吃蛇代码实现与剖析(C语言)(上)

简介: 贪吃蛇代码实现与剖析(C语言)

首先说明:

1.

这个贪吃蛇代码只有在Windows中执行才会起效果

我用的是Windows系统中的VS2019编译器

2.

我们先给出贪吃蛇的完整代码,是为了让大家提起接下来往后仔细看完这篇博客的热情

3.

这个贪吃蛇代码的前置知识:

1.C语言:函数,结构体,枚举,指针,动态内存管理(free,malloc…),宏

2.数据结构:链表

1.温馨提示

想要执行这个代码,在VS2019中需要调整一下控制台的属性

我们先在VS2019中随意跑一段简单的hello world调出控制台来进行属性的调整

默认情况下:我们的控制台是这个样子的

我们需要修改一下这个控制台的属性

然后就会出现这个

只有这样,我们才可以更好的实现这个窗口

否则,同样的代码在这个控制台窗口下就会出现这种样子

而我们修改了之后的样子是这样的

所以我们才要去修改这个控制台窗口的属性

2.最终实现版本的样子

1.游戏开始-欢迎界面

2.游戏运行界面

3.游戏结束界面

4.选择是否继续玩

1.选择继续

输入Y/y并按下回车即可继续玩

然后回到游戏最开始

2.选择退出游戏

3.完整代码

大家可以先在自己的VS中执行一下玩一玩

1.Snake.h

#pragma once
#include <stdio.h>
#include <Windows.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define INIT_X 24
#define INIT_Y 6
typedef struct SnakeNode
{
  struct SnakeNode* next;
  int x;
  int y;
}SNode;
enum Direction
{
  UP,
  DOWN,
  LEFT,
  RIGHT
};
enum GameState
{
  OK,
  EXIT_NORMAL,
  KILL_BY_WALL,
  KILL_BY_SELF
};
typedef struct Snake
{
  SNode* _pSnake;//蛇头节点
  SNode* _pFood;//食物
  enum Direction _dir;//蛇移动的方向
  enum GameState _state;//当前游戏状态
  int _score;//当前得分
  int _foodWeight;//每个食物的分数
  int _sleepTime;//蛇的休息时间,影响加速和减速和暂停
}Snake;
void SetPos(short x, short y);
void GameStart(Snake* ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(Snake* ps);
void CreateFood(Snake* ps);
void GameRun(Snake* ps);
void PrintHelpInfo();
void SnakeMove(Snake* ps);
//判断是否撞墙
void IfKillByWall(Snake* ps,int x, int y);
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y);
void EatFood(SNode* pNextNode, Snake* ps);
void NoFood(SNode* pNextNode, Snake* ps);
//暂停函数
void pause();
void GameEnd(Snake* ps);

2.Snake.c

#include "Snake.h"
//设置控制台光标位置的函数
void SetPos(short x, short y)
{
  COORD pos = { x,y };
  HANDLE handle = NULL;
  //获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
  handle = GetStdHandle(STD_OUTPUT_HANDLE);
  //设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中
  //调用SetConsoleCursorPosition函数将光标位置设置到指定的位置
  SetConsoleCursorPosition(handle, pos);
}
//system("mode con cols=120 lines=35");
void WelcomeToGame()
{
  SetPos(45, 12);
  printf("欢迎来到贪吃蛇小游戏");
  SetPos(45, 18);
  system("pause");
  system("cls");
  SetPos(45, 12);
  printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速");
  SetPos(45, 13);
  printf("加速能够得到更高的分数");
  SetPos(45, 18);
  system("pause");
  system("cls");
}
void CreateMap()
{
  //上
  SetPos(0, 0);
  for (int i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
  //左
  for (int i = 0; i <= 25; i++)
  {
    SetPos(0, i);
    wprintf(L"%lc", WALL);
  }
  //右
  for (int i = 0; i <= 25; i++)
  {
    SetPos(56, i);
    wprintf(L"%lc", WALL);
  }
  //下
  SetPos(0, 26);
  for (int i = 0; i <= 56; i += 2)
  {
    wprintf(L"%lc", WALL);
  }
}
void InitSnake(Snake* ps)
{
  //初始化蛇身
  for (int i = 0; i < 5; i++)
  {
    SNode* newnode = (SNode*)malloc(sizeof(SNode));
    if (newnode == NULL)
    {
      perror("InitSnake():: malloc fail");
      exit(-1);
    }
    newnode->next = NULL;
    newnode->x = INIT_X + 2 * i;
    newnode->y = INIT_Y;
    if (ps->_pSnake == NULL)
    {
      ps->_pSnake = newnode;
    }
    else
    {
      newnode->next = ps->_pSnake;
      ps->_pSnake = newnode;
    }
  }
  //打印蛇身
  SNode* cur = ps->_pSnake;
  while (cur)
  {
    SetPos(cur->x, cur->y);
    wprintf(L"%lc", BODY);
    cur = cur->next;
  }
  //初始化其他属性
  ps->_dir = RIGHT;
  ps->_state = OK;
  ps->_foodWeight = 10;
  ps->_score = 0;
  ps->_sleepTime = 200;
}
void CreateFood(Snake* ps)
{
  //创建食物
  while (1)
  {
    //保证初始化到墙内
    //x:2~54
    int x = rand() % 53 + 2;//0~52+2  ->  2~54
    //y:1~25
    int y = rand() % 25 + 1;//0~24+1  ->  1~25
    //保证初始化的x必须为偶数
    if (x % 2 != 0)
    {
      continue;
    }
    //保证初始化时不跟蛇身重合
    SNode* cur = ps->_pSnake;
    bool flag = false;
    while (cur)
    {
      //跟蛇身重合
      if (cur->x == x && cur->y == y)
      {
        flag = true;
        break;
      }
      cur = cur->next;
    }
    //没有跟蛇身重合
    if (!flag)
    {
      SNode* newnode = (SNode*)malloc(sizeof(SNode));
      if (newnode == NULL)
      {
        perror("CreateFood():: malloc fail");
        exit(-1);
      }
      newnode->next = NULL;
      newnode->x = x;
      newnode->y = y;
      ps->_pFood = newnode;
      break;
    }
  }
  //打印食物
  SetPos(ps->_pFood->x, ps->_pFood->y);
  wprintf(L"%lc", FOOD);
}
void GameStart(Snake* ps)
{
  WelcomeToGame();
  CreateMap();
  InitSnake(ps);
  CreateFood(ps);
}
void PrintHelpInfo()
{
  SetPos(65, 17);
  printf("不能穿墙,不能咬到自己");
  SetPos(65, 18);
  printf("用↑.↓.←.→ 分别控制蛇的移动");
  SetPos(65, 19);
  printf("F1为加速,F2为减速");
  SetPos(65, 20);
  printf("Esc: 退出游戏  space:暂停游戏");
  SetPos(65, 22);
  printf("编写者:wzs");
}
void EatFood(SNode* pNextNode, Snake* ps)
{
  pNextNode->next = ps->_pSnake;
  ps->_pSnake = pNextNode;
  //打印蛇身
  SNode* cur = ps->_pSnake;
  while (cur)
  {
    SetPos(cur->x,cur->y);
    wprintf(L"%lc", BODY);
    cur = cur->next;
  }
  //释放食物节点
  free(ps->_pFood);
  ps->_pFood = NULL;
  //加分
  ps->_score += ps->_foodWeight;
  //创建新食物
  CreateFood(ps);
}
void NoFood(SNode* pNextNode, Snake* ps)
{
  pNextNode->next = ps->_pSnake;
  ps->_pSnake = pNextNode;
  //释放最后一个节点
  SNode* 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 IfKillByWall(Snake* ps,int x,int y)
{
  if (x == 0 || x == 56 || y == 0 || y == 26)
  {
    ps->_state = KILL_BY_WALL;
  }
}
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y)
{
  SNode* cur = ps->_pSnake->next;
  while (cur)
  {
    if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
    {
      ps->_state = KILL_BY_SELF;
      break;
    }
    cur = cur->next;
  }
}
void SnakeMove(Snake* ps)
{
  //1.根据蛇头的坐标和方向,计算下一个节点的坐标
  int x = ps->_pSnake->x;
  int y = ps->_pSnake->y;
  switch (ps->_dir)
  {
  case UP:
    y--;
    break;
  case DOWN:
    y++;
    break;
  case LEFT:
    x -= 2;
    break;
  case RIGHT:
    x += 2;
    break;
  }
  //创建下一个节点
  SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
  if (pNextNode == NULL)
  {
    perror("SnakeMove():: malloc fail");
    exit(-1);
  }
  pNextNode->x = x;
  pNextNode->y = y;
  pNextNode->next = NULL;
  //判断下一个是不是食物
  if (x == ps->_pFood->x && y == ps->_pFood->y)
  {
    //下一个位置有食物
    EatFood(pNextNode, ps);
  }
  //下一个位置没有食物
  else
  {
    NoFood(pNextNode, ps);
  }
  //判断是否撞墙
  IfKillByWall(ps, x, y);
  //判断是否咬到自己
  IfKillBySelf(ps, x, y);
}
//暂停函数
void pause()
{
  while (1)
  {
    Sleep(200);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
void GameRun(Snake* ps)
{
  PrintHelpInfo();
  do
  {
    SetPos(65, 10);
    printf("得分: %d , 每个食物得分: %d ", ps->_score, 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_SPACE))
    {
      pause();
    }
    //Esc退出
    else if (KEY_PRESS(VK_ESCAPE))
    {
      ps->_state = EXIT_NORMAL;
      break;
    }
    //加速
    else if (KEY_PRESS(VK_F1))
    {
      //防止一直加速导致sleepTime<0出现bug
      if (ps->_sleepTime >= 50)
      {
        ps->_sleepTime -= 30;
        ps->_foodWeight += 2;//加速时食物的分值会增加
      }
    }
    //减速
    else if (KEY_PRESS(VK_F2))
    {
      //防止一直减速导致程序运行太慢出现卡顿影响用户体验
      if (ps->_sleepTime < 350)
      {
        ps->_sleepTime += 30;
        ps->_foodWeight -= 2;
        //防止太慢时食物得分减为负数
        if (ps->_sleepTime == 350)
        {
          ps->_foodWeight = 1;
        }
      }
    }
    //蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
    Sleep(ps->_sleepTime);
    SnakeMove(ps);
  } while (ps->_state == OK);
}
void GameEnd(Snake* ps)
{
  SetPos(65, 24);
  switch (ps->_state)
  {
  case EXIT_NORMAL:
    printf("玩家选择退出,游戏结束");
    break;
  case KILL_BY_WALL:
    printf("很遗憾,撞墙了,游戏结束");
    break;
  case KILL_BY_SELF:
    printf("很遗憾,咬到自己了,游戏结束");
    break;
  default:
    break;
  }
  //释放蛇身的节点
  SNode* cur = ps->_pSnake;
  while (cur)
  {
    SNode* del = cur;
    cur = cur->next;
    free(del);
  }
}

3.test.c文件

#include "Snake.h"
//初始化光标信息等
void Init()
{
  HANDLE handle = NULL;
  //获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
  handle = GetStdHandle(STD_OUTPUT_HANDLE);
  CONSOLE_CURSOR_INFO CursorInfo;//CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息
  GetConsoleCursorInfo(handle, &CursorInfo);//检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
  CursorInfo.bVisible = false;//隐藏控制台光标
  SetConsoleCursorInfo(handle, &CursorInfo);//设置指定控制台屏幕缓冲区的光标的大小和可见性
}
int main()
{
  setlocale(LC_ALL, "");
  system("mode con cols=120 lines=35");
  system("title 贪吃蛇");
  Init();
  char input = 0;
  do
  {
    Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
    srand((unsigned int)time(NULL));
    GameStart(&snake);
    GameRun(&snake);
    GameEnd(&snake);
    SetPos(65, 26);
    printf("要在玩一局吗?(Y/N)");
    input = getchar();
    getchar();//清理'\n'
    system("cls");//清屏
    SetPos(45,12);
    if (input == 'n' || input == 'N')
    {
      printf("欢迎再次在玩");
    }
    else if (input == 'Y' || input == 'y')
    {
      printf("游戏即将开始,祝您玩的开心");
      SetPos(45, 14);
      system("pause");
      system("cls");//清屏
    }
  } while (input == 'y' || input == 'Y');
  SetPos(32, 0);
  return 0;
}
相关文章
|
1月前
|
存储 搜索推荐 C语言
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(二)
|
1月前
|
存储 程序员 编译器
深入C语言指针,使代码更加灵活(一)
深入C语言指针,使代码更加灵活(一)
|
1月前
|
C语言
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
|
2月前
|
定位技术 API C语言
C语言——实现贪吃蛇小游戏
本文介绍了一个基于Windows控制台的贪吃蛇游戏的实现方法。首先,需调整控制台界面以便更好地显示游戏。接着,文章详细描述了如何使用Win32 API函数如`COORD`、`GetStdHandle`、`GetConsoleCursorInfo`等来控制控制台的光标和窗口属性。此外,还介绍了如何利用`GetAsyncKeyState`函数实现键盘监听功能。文中还涉及了`&lt;locale.h&gt;`库的使用,以支持本地化字符显示。
60 1
C语言——实现贪吃蛇小游戏
|
1月前
|
C语言 定位技术 API
【C语言】实践:贪吃蛇小游戏(附源码)(二)
【C语言】实践:贪吃蛇小游戏(附源码)
【C语言】实践:贪吃蛇小游戏(附源码)(二)
|
2月前
|
安全 C语言
在C语言中,正确使用运算符能提升代码的可读性和效率
在C语言中,运算符的使用需要注意优先级、结合性、自增自减的形式、逻辑运算的短路特性、位运算的类型、条件运算的可读性、类型转换以及使用括号来明确运算顺序。掌握这些注意事项可以帮助编写出更安全和高效的代码。
50 4
|
1月前
|
存储 定位技术 API
C语言项目实战:贪吃蛇
C语言项目实战:贪吃蛇
|
1月前
|
C语言
【C语言】实践:贪吃蛇小游戏(附源码)(三)
【C语言】实践:贪吃蛇小游戏(附源码)
|
1月前
|
存储 API C语言
【C语言】实践:贪吃蛇小游戏(附源码)(一)
【C语言】实践:贪吃蛇小游戏(附源码)
|
1月前
|
C语言
C语言练习题代码
C语言练习题代码