【C语言超详细实现贪吃蛇(1)】

C语言实现简易贪吃蛇(详细版)(1)


欢迎来到本博客!今天我们将会一起来学习如何使用C语言实现一个简易版的贪吃蛇游戏!
在本博客中,我们将从头开始一步步地实现这个游戏,包括贪吃蛇的移动、食物的生成、碰撞检测等等。
如果你对C语言、游戏开发或者贪吃蛇游戏有兴趣,那么就跟着我一起来探索吧!
本博客的技术要点有C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。
下面详细介绍如何实现贪吃蛇。(注意终端要使用Windows控制台主机


想要实现贪吃蛇我们需要考虑到:

  1. 制作地图
  2. 制作蛇的身体
  3. 蛇的状态
  4. 按键控制方向
  5. 制作食物
  6. 记录分数
  7. 蛇的加速减速
  8. 游戏的结束条件

我将贪吃蛇分为基础部分、主体部分、和末尾部分三部分进行介绍。

本篇介绍基础部分

这一部分要介绍的内容如目录。
为了方便管理,这里我们将与蛇有关的分数,状态,方向,加速,减速等信息封装为一个结构体

  • 头文件声明:定义游戏所需的常量、数据结构和函数声明

    #pragma once
      #include <stdlib.h>
      #include <stdio.h>
      #include <windows.h>
      #include <stdbool.h>
      #include <wchar.h>
      #include <locale.h>
      #include <time.h>
      #define BODY L'●'
      #define FOOD L'★'
      //判断按键是否被按下
      #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
      //默认蛇身长度
      #define Length 5
      //蛇的状态
      enum STATUS {
      	OK, //正常
      	KILL_BY_WALL, //撞墙
      	KILL_BY_SELF, //撞到自己
      	END_NORMAL //正常退出
      };
      //蛇的移动
      enum DIR{
      	UP = 1,
      	DOWN,
      	LEFT,
      	RIGHT
      };
      //蛇体
      typedef struct body {
      	int x;
      	int y;
      	struct body* next;
      }body, * Body;
      
      //游戏主体
      typedef struct Snake
      {
      	Body _pSnake;//指向蛇头的指针
      	Body _pFood;//指向食物节点的指针
      	enum DIR _dir;//蛇的方向
      	enum STATUS _status;//游戏的状态
      	int _food_weight;//一个食物的分数
      	int _score;      //总成绩
      	int _sleep_time; //休息时间,时间越短,速度越快,时间越长,速度越慢
      }Snake, * pSnake;
      //长 56
      //宽 27
      //定位光标
      void SetPos(short x, short y);
      
      void GameStart(Snake* ps);
      
      void WelcomeToGame();
      
      void CreateMap();
      
      //创建蛇身
      void CreateBody(Snake** ps);
      
      //创建食物
      void CreateFood(Snake* ps);
      
      //蛇的移动
      void SnakeMove(Snake* ps);
      
      //游戏暂停
      void Pause();
      
      //游戏运行
      void GameRun(pSnake ps);
      
      //游戏结束条件 
      void GameOver(pSnake ps);
    

    刚开始大家可能没看懂,不过别急,接下来会进行详细介绍

  • Win32 API 使用:利用 Win32 API 创建游戏窗口,并处理用户输入等交互操作

    Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的的,由于这些函数服务的对象是应用程序(Application),所以便称之为 Application Programming Interface,简称 API 函数。WIN32API 也就是Microsoft Windows32位平台的应⽤程序编程接。

    平常我们运行起来的黑框程序其实就是控制台程序
    我们可以使用 cmd命令来设置控制台窗口的长宽 :设置控制台窗口的大小,30行,100列,使用 title命令 来设置窗口的名字。
    这些能在控制台窗口执行的命令,也可以调⽤C语⾔函数system来执行

    #include <stdio.h>
    	 
    int main()
    {
    		system("mode con cols=100 lines=30");
    		//设置窗口名称
    		system("title 贪吃蛇");
    		getchar();
    		return 0;
    }
    

    效果: 效果

    • GetStdHandle函数

      GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

      HANDLE GetStdHandle(DWORD nStdHandle);
      //列:
      HANDLE hOutput = NULL;
       //获取标准输出的句柄(⽤来标识不同设备的数值)
       hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
      
    • GetConsoleCursorInfo函数

      检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息

      BOOL WINAPI GetConsoleCursorInfo(
      	HANDLE hConsoleOutput,
      	PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
      );
      
      //PCONSOLE_CURSOR_INFO 
      是指向 CONSOLE_CURSOR_INFO结构的指针,
      该结构接收有关主机游标(光标)的信息
        ````
      
    • CONSOLE_CURSOR_INFO函数

      这个结构体,包含有关控制台光标的信息

       typedef struct _CONSOLE_CURSOR_INFO {
      		DWORD dwSize;
      		BOOL bVisible;
       } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
      
      • dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
      • bVisible,游标的可⻅性。如果光标可见,则此成员为 TRUE。
    • SetConsoleCursorInfo函数

      设置指定控制台屏幕缓冲区的光标的大小和可见性。

      BOOL WINAPI SetConsoleCursorInfo(
             HANDLE hConsoleOutput,
             const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
      );
             
            //列;
           //获取标准输出的句柄(⽤来标识不同设备的数值)
      HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
           //隐藏光标操作(创建变量)
      CONSOLE_CURSOR_INFO CursorInfo;
           //获取控制台光标信息
      GetConsoleCursorInfo(hOutput, &CursorInfo);
           //隐藏控制台光标
      CursorInfo.bVisible = false; 
           //设置控制台光标状态
      SetConsoleCursorInfo(hOutput, &CursorInfo);
      
    • 控制台屏幕上的坐标COORD

      COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系
      (0,0) 的原点位于缓冲区的顶部左侧单元格。

      	  typedef struct _COORD {
      		SHORT X;
      		SHORT Y;
      	  } COORD, *PCOORD;
      	  //给坐标赋值
      	  COORD pos = { 10, 15 };
      
    • SetConsoleCursorPosition函数

      设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标 信息放在COORD类型的pos中,调
      ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

       		BOOL WINAPI SetConsoleCursorPosition(
       		HANDLE hConsoleOutput,
       		COORD pos
       		);
       		//例:
       		COORD pos = { 10, 5};
       		HANDLE hOutput = NULL;
       		//获取标准输出的句柄(⽤来标识不同设备的数值)
       		hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
       		//设置标准输出上光标的位置为pos
       		SetConsoleCursorPosition(hOutput, pos);
      
    • GetAsyncKeyState函数

      获取按键情况,GetAsyncKeyState的函数原型如下:

        SHORT GetAsyncKeyState(
         	int vKey
        );
      

      将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
      GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最⾼位是1,说明当前按键的状态是按下,如果最⾼是0,说明当前按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
      如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1

        //定义宏
        #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 1) ? 1 : 0 )
      

      参考: 虚拟键码 (Winuser.h) - Win32 apps
      熟悉这些函数以后我们就可以正式开始了!

  • 制作游戏开始界面

    创建初始化游戏的函数进行封装

    void GameStart(Snake* ps)
    { 
       system("mode con cols=100 lines=30");
       system("title 贪吃蛇");
       HANDLE hOutput = NULL;
       //获取标准输出的句柄(⽤来标识不同设备的数值)
       hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    
       CONSOLE_CURSOR_INFO CursorInfo;
       GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
       CursorInfo.bVisible = false; // 设置光标不可见
       SetConsoleCursorInfo(hOutput, &CursorInfo);
       //1. 打印环境界面和功能介绍
       WelcomeToGame();
       //2. 绘制地图
       CreateMap();
       //等等......
    }
    
    • 打印游戏介绍界面:在开始界面提供一个选项,让玩家了解游戏规则和操作说明
      //定位光标函数
      void SetPos(short x, short y)
      {
       	COORD pos = { x, y };
       	HACCEL hehe = NULL;
       	hehe = GetStdHandle(STD_OUTPUT_HANDLE);
       	SetConsoleCursorPosition(hehe, pos);
      }
      
       void WelcomeToGame()
      {
      	//第一页
          //定位光标
      	SetPos(40, 14);
      	//打印
       	printf("欢迎来到贪吃蛇小游戏\n");
       	//定位光标
       	SetPos(42, 20);
       	//暂停
       	system("pause");
       	//清屏
       	system("cls");
       	
       	//第二页
       	SetPos(25, 14);
       	printf("用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
       	SetPos(42, 20);
       	system("pause");
       	system("cls");
       	
       	//第三页
       	SetPos(39, 15);
       	printf("加速能够得到更高的分数\n");
       	SetPos(42, 20);
       	system("pause");
       	system("cls");
      }
      

      效果展示:

      打印游戏介绍界面

    • 打印游戏地图:在游戏界面打印贪吃蛇的初始地图,包括蛇、食物和边界等元素

      注意:

      头文件<locale.h>本地化
      <locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样行为的部分。
      在标准中,依赖地区的部分有以下几项:
      • 数字量的格式
      • 货币量的格式
      • 字符集
      • ⽇期和时间的表⽰形式
      –类项–
      通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部
      分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,
      指定⼀个类项:
      • LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
      • LC_CTYPE:影响字符处理函数的⾏为。
      • LC_MONETARY:影响货币格式。
      • LC_NUMERIC:影响 printf() 的数字格式。
      • LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
      • LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
      每个类项的详细说明,请参考
      setlocale函数

      char* setlocale (int category, const char* locale);
      

      – setlocale函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
      – setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参
      数是LC_ALL,就会影响所有的类项。
      C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和" "(本地模式)。
      在任意程序执⾏开始,都会隐藏式执⾏调⽤:

         setlocale(LC_ALL, "C");
      

      当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
      当程序运⾏起来后想改变地区就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale
      函数就可以切换到本地模式,这种模式下程序会适应本地环境。

      简而言之: 要打印特殊宽字符墙体 “□” 就要将程序适应本地环境

      //适应本地环境
      setlocale(LC_ALL, " ");
      
      • 制作墙体:
      • 函数名称: CreateMap
        • 函数功能: 创建墙体
        • 输入参数: 无
        • 返回值: 无
         void CreateMap()
         {
         	//上
         	int i = 0;
         	for (i = 0; i < 29; i++)
         	{
         		//打印宽字符固定格式,记住就好。
         		wprintf(L"%lc", L'□');
         	}
         	//下
         	SetPos(0, 26);
         	for (i = 0; i < 29; i++)
         	{
         		wprintf(L"%lc", L'□');
         	}
         	//左
         	for (i = 1; i <= 25; i++)
         	{
         		SetPos(0, i);
         		wprintf(L"%lc", L'□');
         	}
         	//右
         	for (i = 1; i <= 25; i++)
         	{
         		SetPos(56, i);
         		wprintf(L"%lc", L'□');
         	}
         	getchar();
         }
        
        效果:
        地图
      • 制作蛇身:
      • 函数名称: CreateBody
        • 函数功能: 创建贪吃蛇的身体,并初始化蛇的相关属性。
        • 输入参数: Snake** ps - 指向贪吃蛇结构体指针的指针,用于更新蛇的信息。
        • 返回值: 无
        void CreateBody(Snake** ps)
        {
            // 循环创建蛇的每个身体节点
            for (int i = 0; i < Length; i++)
            {
                // 为当前身体节点分配内存空间
                Body P = (Body)malloc(sizeof(body));
                // 设置当前节点的位置坐标
                P->x = X + i * 2;
                P->y = Y;
                P->next = NULL;
                
                // 将当前节点添加到蛇身链表中
                if ((*ps)->_pSnake == NULL)
                {
                    // 如果蛇身链表为空,则将当前节点设为蛇身链表的头节点
                    (*ps)->_pSnake = P;
                }
                else {
                    // 否则将当前节点插入到蛇身链表的头部
                    P->next = (*ps)->_pSnake;
                    (*ps)->_pSnake = P;
                }
            }
            
            // 遍历蛇身链表,将蛇身节点显示在游戏界面上
            Body T = (*ps)->_pSnake;
            while (T)
            {
                SetPos(T->x, T->y);
                wprintf(L"%lc", BODY);
                T = T->next;
            }
            
            // 初始化蛇的其他属性
            (*ps)->_dir = RIGHT;        // 蛇的初始移动方向为向右
            (*ps)->_sleep_time = 200;   // 设置蛇的移动速度
            (*ps)->_status = OK;        // 设置蛇的状态为正常
            (*ps)->_food_weight = 10;   // 设置初始食物的重量
        }
        
      • 制作食物:
      • 函数名称: CreateFood
        • 函数功能: 在游戏界面上生成食物,并确保食物不与蛇身重叠。
        • 输入参数: Snake* ps - 指向贪吃蛇结构体的指针,用于获取蛇的信息。
        • 返回值: 无
        void CreateFood(Snake* ps)
        {
              int y = 0;  // 食物的纵坐标
              int x = 0;  // 食物的横坐标
              int i = 0;  // 用于判断食物位置是否与蛇身冲突的标志
              
              // 使用当前时间作为随机数种子
              srand(time(NULL));
              
              // 在随机位置生成食物,直到食物位置不与蛇身冲突为止
              do 
              {
                  i = 0;  // 重置冲突标志
                  
                  // 随机生成食物的纵坐标
                  y = rand() % 23 + 2;
                  
                  // 随机生成食物的横坐标,确保为偶数
                  do
                  {
                      x = rand() % 55 + 1;
                  } while (x % 2 != 0);
                  
                  // 检查食物位置是否与蛇身冲突
                  Body B = ps->_pSnake;
                  while (B)
                  {
                      if (B->x == x && B->y == y)
                      {
                          i = 1;  // 若冲突,则设置标志为1
                          break;
                      }
                      B = B->next;
                  }
              } while (i);  // 若发现冲突,则重新生成食物位置
              
              // 分配内存空间,创建食物节点
              Body F = (Body)malloc(sizeof(body));
              ps->_pFood = F;
              ps->_pFood->next = NULL;
              ps->_pFood->x = x;
              ps->_pFood->y = y;
              
              // 在游戏界面上显示食物
              SetPos(x, y);
              wprintf(L"%lc", FOOD);
        }
        
        效果:
        贪吃蛇

那么关于贪吃蛇第一部分内容到这里结束了,在下篇博客我们会介绍主体部分:

蛇的移动:根据用户输入控制蛇的移动方向,并更新游戏地图。
食物生成:在地图上随机生成食物,并确保不与蛇身重叠。
碰撞检测:检测蛇头是否与边界或蛇身相撞,以及是否吃到了食物。
分数计算:根据蛇吃到的食物数量计算玩家的得分。
等等…

如有什么问题或疑问请大家在评论区指出~
谢谢大家的观看!

  • 37
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
c语言60个案例项目源码 c语言24点游戏源码 c语言Turbo C下写的俄罗斯方块 c语言五子棋源码 c语言俄罗斯方块 c语言别踩白块儿(双人版)源码 c语言华容道源码 c语言吃逗游戏源码 c语言地空对战游戏 c语言坑爹大冒险 c语言坦克游戏源代码 c语言大丰收游戏源码 c语言奔跑的火柴人游戏源码 c语言实现的象棋源码 c语言实现级玛丽游戏源码 c语言情人节的红玫瑰 c语言打字母游戏源码 c语言打字游戏代码 c语言扔香蕉的大猩猩 c语言拼图游戏源码 c语言支持自己创建迷宫,并求解最短路径 c语言涂格子游戏源码 c语言盒子接球游戏源码 c语言矿井逃生 c语言种地要浇水游戏源码 c语言自创军旗游戏源码 c语言自创推箱子游戏改版 c语言贪吃蛇游戏的双人对战版 c语言连连看游戏源码 c语言青蛙过河小游戏 c语言黑白棋ai游戏源码 纯c语言迷宫源码 c语言对对碰游戏源码 c语言配有图片和音乐的打字母游戏 c语言商品销售系统源码 c语言图书借阅系统源码 c语言图书管理系统源码 c语言学生信息系统 c语言学生成绩管理系统 c语言学生成绩管理系统源码 C语言市管理系统 c语言通讯录管理系统源码 c语言UDP传输系统源码 c语言教工工资管理系统 c语言文本编辑器系统源码 c语言火车票订票管理源码 c语言万年历源码 c语言电子时钟程序 c语言做的一个任务管理器 c语言做的播放器源码 c语言做的绘图板系统 c语言对自己电脑系统测试 c语言力学相关的流体源码 c语言实现图片转化为 ASCII 图 c语言实现如果cmd中的ping c语言实现数字雨 c语言实现水波纹显示效果 c语言实现的汉诺塔演示程序 c语言实现类似弹力球效果 c语言实现非常漂亮祝福烟花效果

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ヾ慈城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值