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

2023-11-12

一、需要掌握的知识:

C语言基础语法(结构体、指针、链表)、<windows.h>库、<stdlib.h>库、<time.h>库中的一些函数(不需要额外学习,本文后面会讲贪吃蛇需要用到的相关函数

由于作者水平有限,我尽可能讲清楚,相关函数有不理解的地方还请大家发挥自习能力,查阅相关资料进行学习

下面就让我们一步步的实现贪吃蛇这个小游戏

二、具体实现

1.结构体

用来表示蛇与食物

typedef struct Node{
	int x;
	int y;
	Node* next;
}node;

2.头文件与全局变量

现在只需要大概浏览,下面讲函数不清楚的时候再回头看一下

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>

node* head;  //蛇头
node food;  //食物 
int reCreateFood;  //判断食物是否被吃掉
int fail;  //如果蛇头碰到蛇身或边界则失败 
int score, add = 10;  //定义分数和每个食物的分数 
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};  //(dx[0], dy[0])为(0, 1),即代表“上”
int d = -1;  //表示蛇前进的方向,与上一行对应,0为上,1为下,2为左,3为右
//开局时初始化为-1,表示还未按方向键 

3.Pos( )函数

这个函数的作用是改变控制台(黑框)光标位置
别着急!看不懂的地方我都会解释!):
(看完解释还是比较模糊的只需要理解这个函数是用来改变控制台光标的即可)

Pos(10, 10);
printf("&");

上面的意思就是在控制台(黑框)的(10, 10)位置打印字符’&’

下面不想看的可以直接看下一个函数

函数定义如下

void Pos(int x, int y){
	COORD pos;  //位置坐标 
	pos.X = x;
	pos.Y = y;
	
	HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE);  //获取输出句柄 
	SetConsoleCursorPosition(handleOutput, pos);   //定位 
}

先来看COORD
COORD是定义在<window.h>库里的结构体,定义如下:

typedef struct _COORD{
    short X;
    short Y;
}COORD;

大家只需要把COORD理解成**坐标(x, y)**即可

再看这句:

HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE);  //获取输出句柄

先来理解一下句柄

  句柄(Handle)是一种数据结构或对象,用于标识或引用其他对象或资源。它通常是一个整数或指针,用于在程序中唯一标识或操作某个对象
  句柄的作用是隐藏底层对象的具体细节,使得程序可以通过引用句柄来访问和操作对象,而无需了解对象的内部结构或实现方式。这样可以提高程序的模块性和灵活性,并且减少对对象的直接访问,从而提高系统的安全性和效率。

句柄有三种:输入句柄、输出句柄、错误句柄
这句话的作用就是获得一个输出句柄handleOutput

再看最后一句:

SetConsoleCursorPosition(handleOutput, pos);   //定位 

这个函数定义在<windows.h>库中,顾名思义,set(设置)console(控制台)cursor(光标)position(位置)
需要传递两个参数,一个是刚才获得的输出句柄,另一个是光标设置的坐标

4.border( )函数

这个函数的作用是打印边界,效果如下:
在这里插入图片描述

void border(){  //创建以(0, 0), (59, 25)为顶点的矩形边界
	for(int i = 0; i < 60; i ++ ){
		Pos(i, 0);  //上行 
		printf("▇");
		Pos(i, 25);  //下行 
		printf("▇"); 
	} 
	
	for(int i = 1; i < 25; i ++ ){
		Pos(0, i);  //左列 
		printf("▇");
		Pos(59, i);  //右列 
		printf("▇");
	}
}

5.getRandomPos( )函数

获得一个随机生成的坐标

void getRandomPos(int *x, int *y){
	srand((unsigned int)time(NULL));  //随着时间变化产生不同种子
	//保证坐标在边界内 
    *x = rand() % 58 + 1;
    *y = rand() % 24 + 1;
}

6.initSnake( )函数

实现蛇的初始化

void initSnake(){ 
	head = (node*)malloc(sizeof(node));
	//保证在边界内
	head->x = rand() % 58 + 1;   
	head->y = rand() % 24 + 1;
	head->next = NULL;
	
	Pos(head->x, head->y);
	//蛇用字符'*'表示
	printf("*");
}

7.inSnake( )函数

判断坐标(x, y)是否在蛇身上(食物不能创建在蛇身上/蛇头不能在蛇身上)

int inSnake(int x, int y){
	node* p = head->next;
	while(p){  //从头遍历蛇身 
		if(x == p->x && y == p->y)
			return 1;
		p = p->next;
	}
	return 0;
} 

8.createFood( )函数

创建食物

void createFood(){
	int x, y;
	getRandomPos(&x, &y);
	if(inSnake(x, y)) createFood();  //重新生成
	food.x = x;
	food.y = y;
	Pos(x, y);
	printf("$");
}

9.getDirection( )函数

获取蛇前进的方向

int getDirection(){
	//不能往反方向走 
	if((GetAsyncKeyState(VK_UP) & 0x8000) && d != 1) d = 0;
	else if((GetAsyncKeyState(VK_DOWN) & 0x8000) && d != 0) d = 1;
	else if((GetAsyncKeyState(VK_LEFT) & 0x8000) && d != 3) d = 2;
	else if((GetAsyncKeyState(VK_RIGHT) & 0x8000) && d != 2) d = 3;
	return d; 
}

GetAsyncKeyState(VK_UP) & 0x8000这个表达式的值为1的话表示按下了UP键

10.snakeMove( )函数

这是最核心的函数,用来控制蛇的移动

void snakeMove(){
	//刚开局未按方向键的情况 
	if(d == -1)
		while(d == -1) d = getDirection();

	//前进一格
	node* newHead = (node*)malloc(sizeof(node));
	newHead->x = head->x + dx[d];
	newHead->y = head->y + dy[d];
	newHead->next = head;
	head = newHead;
	
	//判断蛇头是否超出边界或者碰到蛇身
	node* p = head;
	if(inSnake(p->x, p->y) || (p->x == 0 || p->x == 59 || p->y == 0 || p->y == 25))
		fail = 1;  //用fail记录蛇是否存活
	
	//没吃到食物的情况
	else if(p->x != food.x || p->y != food.y){
		//在蛇头打印字符'*'
		Pos(p->x, p->y);
		printf("*");
		
		//因为没吃到食物,蛇前进一个的话要把蛇尾的一格覆盖掉
		while(p->next->next) p = p->next;
		Pos(p->next->x, p->next->y);
		printf(" ");
		p->next = NULL;
	} 
	//吃到食物的情况
	else{
		reCreateFood = 1;  //食物被吃掉后需要再生成
		score += add; 
		Pos(p->x, p->y);
		printf("*");
	}
	//这两行的作用除了打印得分,还是为了消除蛇尾闪烁的光标
	//读者可以把这两行注释掉进行对比
	Pos(61, 25);
	printf("您的得分为:%d", score);
}

11.welcome( )函数

创建欢迎界面

void welcome(){
	system("mode con cols=100 lines=30");  //设置控制台的大小为长100,宽30
	system("cls");  //清除控制台屏幕
	
	Pos(38,6);
	printf("welcome come to SnakeGame\n");
	Pos(38,8);
	printf("↑↓←→control direction\n");
	Pos(45,10);
	printf("ESC For Exit\n");
	Pos(42,12);
	printf("Enter For Begin\n");
	getchar();  //读取Enter字符进入游戏界面
	system("cls");  //清除控制台屏幕
}

效果如下:
在这里插入图片描述

12.主函数

终于到最后啦!加油!

int main(){
	welcome();  //欢迎界面 
	border();  //创建边界 
	initSnake();  //蛇的初始化 
	createFood();  //生成食物 
	
	//fail != 0就一直进行 
	while(1){
		if(fail) break;
		//食物被吃掉需要重新生成 
		if(reCreateFood){
			createFood();
			reCreateFood = 0;
		}
		snakeMove();  //蛇的移动 
		Sleep(500);  //程序暂停500毫秒,注释掉运行一下就懂了
		//获取新的方向 
		d = getDirection(); 
	}
	
	//这个也是注释掉对比一下就懂了 
	Pos(0, 24);
	printf("\n");
	
	return 0;
}

13.总代码

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>

void welcome();  //欢迎界面 
void border();  //创建边界 
void Pos(int x, int y);  //改变控制台光标位置 
void getRandomPos(int *x, int *y);  //获取随机坐标
void initSnake();  //蛇的初始化
void createFood();  //生成食物
int inSnake(int x, int y);  //判断(x, y)是否在蛇上,1表示在,0表示不在 
void snakeMove();  //蛇的移动 
int getDirection();  //获取移动方向,0、1、2、3分别代表上、下、左、右 

typedef struct Node{
	int x;
	int y;
	Node* next;
}node;

//全局变量
node* head;  //蛇头
node food;  //食物 
int reCreateFood;  //判断食物是否被吃掉
int fail;  //如果蛇头碰到蛇身或边界则失败 
int score, add = 10;  //定义分数和每个食物的分数 
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};  //(dx[0], dy[0])为(0, 1),即代表“上”
int d = -1;  //表示蛇前进的方向,与上一行对应,0为上,1为下,2为左,3为右
//开局时初始化为-1,表示还未按方向键 

int main(){
	welcome();  //欢迎界面 
	border();  //创建边界 
	initSnake();  //蛇的初始化 
	createFood();  //生成食物 
	
	//fail != 0就一直进行 
	while(1){
		if(fail) break;
		//食物被吃掉需要重新生成 
		if(reCreateFood){
			createFood();
			reCreateFood = 0;
		}
		snakeMove();  //蛇的移动 
		Sleep(500);  //程序暂停500毫秒,注释掉运行一下就懂了
		//获取新的方向 
		d = getDirection(); 
	}
	
	//这个也是注释掉对比一下就懂了 
	Pos(0, 24);
	printf("\n");
	
	return 0;
}

void welcome(){
	system("mode con cols=100 lines=30");
	system("cls");
	
	Pos(38,6);
	printf("welcome come to SnakeGame\n");
	Pos(38,8);
	printf("↑↓←→control direction\n");
	Pos(45,10);
	printf("ESC For Exit\n");
	Pos(42,12);
	printf("Enter For Begin\n");
	getchar();
	system("cls");
}

void border(){  //创建以(0, 0), (59, 25)为顶点的矩形边界
	for(int i = 0; i < 60; i ++ ){
		Pos(i, 0);  //上行 
		printf("▇");
		Pos(i, 25);  //下行 
		printf("▇"); 
	} 
	
	for(int i = 1; i < 25; i ++ ){
		Pos(0, i);  //左列 
		printf("▇");
		Pos(59, i);  //右列 
		printf("▇");
	}
}

void Pos(int x, int y){
	COORD pos;  //位置坐标 
	pos.X = x;
	pos.Y = y;
	
	HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE);  //获取输出句柄 
	SetConsoleCursorPosition(handleOutput, pos);   //定位 
}

void getRandomPos(int *x, int *y){
	srand((unsigned int)time(NULL));
    *x = rand() % 58 + 1;
    *y = rand() % 24 + 1;
}

void initSnake(){ 
	head = (node*)malloc(sizeof(node));
	//保证在边界内
	head->x = rand() % 58 + 1;   
	head->y = rand() % 24 + 1;
	head->next = NULL;
	
	Pos(head->x, head->y);
	printf("*");
}

void createFood(){
	int x, y;
	getRandomPos(&x, &y);
	if(inSnake(x, y)) createFood();  //重新生成
	food.x = x;
	food.y = y;
	Pos(x, y);
	printf("$");
}

int inSnake(int x, int y){
	node* p = head->next;
	while(p){  //从头遍历蛇身 
		if(x == p->x && y == p->y)
			return 1;
		p = p->next;
	}
	return 0;
} 

void snakeMove(){
	//刚开局未按方向键的情况 
	if(d == -1)
		while(d == -1) d = getDirection();

	node* newHead = (node*)malloc(sizeof(node));
	newHead->x = head->x + dx[d];
	newHead->y = head->y + dy[d];
	newHead->next = head;
	head = newHead;
	
	node* p = head;
	if(inSnake(p->x, p->y) || (p->x == 0 || p->x == 59 || p->y == 0 || p->y == 25))
		fail = 1;
	else if(p->x != food.x || p->y != food.y){
		Pos(p->x, p->y);
		printf("*");
		
		while(p->next->next) p = p->next;
		Pos(p->next->x, p->next->y);
		printf(" ");
		p->next = NULL;
	} 
	else{
		reCreateFood = 1;
		score += add;
		Pos(p->x, p->y);
		printf("*");
	}
	Pos(61, 25);
	printf("您的得分为:%d", score);
}

int getDirection(){
	//不能往反方向走 
	if((GetAsyncKeyState(VK_UP) & 0x8000) && d != 1) d = 0;
	else if((GetAsyncKeyState(VK_DOWN) & 0x8000) && d != 0) d = 1;
	else if((GetAsyncKeyState(VK_LEFT) & 0x8000) && d != 3) d = 2;
	else if((GetAsyncKeyState(VK_RIGHT) & 0x8000) && d != 2) d = 3;	
	return d; 
}

14.待改进的地方

  • 改变蛇移动的快慢,相应改变食物的分数
  • 没有把边界大小的参数写成宏
  • 改变方向不灵敏

三.结语

以上就是贪吃蛇的基本内容啦,完结撒花~
下面有一些想说的话:
这篇文章的完成时间是2023.5.12,我现在是一名大一下的计科学生,其实C语言在去年十月份的中旬就看完了翁恺老师的网课,那个时候其实就有能力完成这个贪吃蛇的编写了,但是知道现在才写完,其实是走了很多弯路,耽误了不少时间
最后感谢北哥,在加入北哥的知识星球”编程指北“后才回正了自己的学习方向,也希望大家都能在正确的道路上越走越远~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言实现贪吃蛇(详细版) 的相关文章

  • 从pandas dataframe中随机删除n个某一列是某个值的元素

    从pandas dataframe中随机删除n个某一列是某个值的元素 import pandas as pd 创建示例 DataFrame data A 1 2 3 4 5 6 B 6 7 8 9 10 11 C X Y Z X Y X d
  • 基本排序算法(直接排序,选择排序,冒泡排序)

    一 直接排序 思路 首先需要两个嵌套的for循环 外层for循环控制轮数 内层for循环控制每轮比较的次数 这里来演示一下遍历的过程 第一轮 首先让i指向数组的首部 让j指向i的后一个元素 两者比较 2比1大 所以交换2跟1的位置 然后j后
  • git proxy

    git config global https proxy http 127 0 0 1 7890 git config global https proxy https 127 0 0 1 7890
  • tar命令的详细解释

    tar命令 root linux tar cxtzjvfpPN 文件与目录 参数 c 建立一个压缩文件的参数指令 create 的意思 x 解开一个压缩文件的参数指令 t 查看 tarfile 里面的文件 特别注意 在参数的下达中 c x

随机推荐

  • CSS 水平居中

    1 若元素内容为文字时 元素设置text align center text align属性指定元素文本的水平对齐方式 center 把文本排列到中间 p 这里是文本内容 p 2 父子元素宽度固定 父元素设置text align cente
  • SQL实用功能手册

    SQL实用功能手册 SQL基础复习 SQL结构化查询语言 是一种访问和处理数据库的计算机语言 对数据库操作 对表操作 对数据进行CRUD操作 操作视图 存储过程 索引 环境基础操作 安装mysql 启动mysql 配置环境变量 检查mysq
  • 搞懂java类加载机制和类加载器

    搞懂java类加载机制和类加载器 类加载概述 一个类从被加载到虚拟机内存中开始 到卸载出内存为止 它的整个生命周期将会经历加载 验证 准备 解析 初始化 使用和卸载七个阶段 其中验证 准备 解析三个部分统称为连接 如下图所示 其中加载 验证
  • Android学习——Adapter适配器

    AdapterView 容器控件 其整体效果由每一个子元素内容决定 子元素的形式由Adapter决定 AdapterView的子视图对象 ListView 以垂直滑动列表形式显示一组数据 GridView 以网格形式显示一组数据 Spinn
  • GPUView的使用

    本文翻译自GPUView的开发者Matt的blog https graphics stanford edu mdfisher GPUView html GPUview可以在 https docs microsoft com en us wi
  • 10个经典的C语言面试基础算法及代码

    本文是码农网原创整理 转载请看清文末的转载要求 谢谢合作 算法是一个程序和软件的灵魂 作为一名优秀的程序员 只有对一些基础的算法有着全面的掌握 才会在设计程序和编写代码的过程中显得得心应手 本文是近百个C语言算法系列的第二篇 包括了经典的F
  • C语言(Head First C)-5_1:使用多个源文件:数据类型和使用头文件声明函数

    该系列文章系个人读书笔记及总结性内容 任何组织和个人不得转载进行商业活动 5 1 使用多个源文件 数据类型和头文件 大程序不等于大源文件 只有一个源文件的话 维护耗时且困难 如何把源文件分解为易于管理的小模块 然后合成一个大程序 正是本章的
  • Append-only及其使用

    Append only 维基百科 Append only 是计算机数据存储的一种属性 将新数据附加到存储中 但现有数据是不可变的 许多数据结构和数据库实现了不可变对象 有效地使它们的数据结构只能追加 实现仅追加数据结构有很多好处 例如确保数
  • android 播放raw音频文件格式,Android 使用mediaplayer播放res/raw文件夹中的音乐的实例...

    Android 使用mediaplayer播放res raw文件夹中的音乐的实例 1 在res文件夹中新建一个文件夹重命名为raw 并且将要播放的音乐放到raw文件夹里面 2 修改layout目录下的xml布局文件 添加3个按钮空间和一个文
  • 计算机硬盘启动设置方法,bios设置硬盘启动,详细教您bios设置硬盘启动操作步骤...

    我们在操作电脑的时候 会遇到要重装系统的情况 每当这种时候我们就会想到通过bios设置来进行系统的重装 所以今天小编就来重点给你们说说关于bios设置硬盘启动的操作步骤 重装系统是我们在使用电脑的时候会经常在遇到问题时采用的解决方法 但说到
  • spring boot 获取yaml配置的对象和数组对象

    一 获取对象 例子 yaml中的配置 permission method POST path auth login spring boot 中 在需要使用到该配置的controller或者service或者专门的配置类中 添加method和
  • 磁盘加新盘扩容

    注 如果有磁盘超过2T 如下调整 使用parted来对GPT磁盘操作 进入交互式模式 没有的话yum安装下 parted dev sdb 将MBR磁盘格式化为GPT parted mklabel gpt 到这就可以进行下面的了 先查看下新磁
  • QT UI不能索引控件

    原因 Qt程序使用的UI h文件并不是最新的UI文件 最新的ui h在bulid文件夹里面了 软件依旧使用旧的ui h文件 简单讲就是先要从 ui生成ui h然后再编译 所以界面未更新实际上是因为ui h这个文件没有更新导致的 解决办法 1
  • 论文阅读-(GLIP)Grounded Language-Image Pre-training (目标检测+定位)

    Paper Grounded Language Image Pre training Code https github com microsoft GLIP 简介 定位任务与图像检测任务非常类似 都是去图中找目标物体的位置 目标检测为给出
  • 好一场逗鹅冤:一瓶老干妈撬动BAT

    近日 号称 南山必胜客 的腾讯法务部将低调做酱料的老干妈送上了热搜 腾讯状告老干妈欠广告费 老干妈则称腾讯遭遇诈骗 双方各执一词 引得不明真相的吃瓜群众热闹围观 01 腾讯被骗 老干妈躺赚 6月29日 广东省深圳市南山区人民法院发布一则民事
  • QTcpSocket 发送数据心得

    遇到不会用的函数前 最好还是看看手册QAQ 今天居然吃了这个大亏 先交代一下背景 在做TCP客户端的发送数据功能 要和服务器程序进行TCP IP通信 且根据通信协议要发送数组或者结构体 并且数组的每一个位都是有效数据位 因此不能像大多数人一
  • 跳转小程序:wx-open-launch-weapp 注意事项,不显示按钮问题

    JSSDK参考文件 一 注意查看引入JS的版本 版本 版本 引用1 6 引用1 6 引用1 6 http res wx qq com open js jweixin 1 6 0 js 如果不引用1 6会出现什么情况 开放标签列表不显示 所以
  • 浅谈路由器工作原理

    路由器的作用是实现网络的三层通信 将二层网络互联形成一个三层网络 路由器工作内容 1 封装和解封装 网卡CU 数据帧 2 维护路由表 3 IP转发 也叫网络转发 三层转发 路由器接口特点 路由器接口和计算机网卡接口一样 都能封装和解封装数据
  • 【Python:Pycharm】mmSegmentation语义分割框架教程

    文章目录 一 MMSegmentation介绍 二 MMSegmentation基本框架 1 model设置 2 dataset设置 2 1 Dataset Class文件配置 2 2 Dataset Config文件配置 2 3 Tota
  • C语言实现贪吃蛇(详细版)

    一 需要掌握的知识 C语言基础语法 结构体 指针 链表