贪吃蛇---C语言---详解

这篇具有很好参考价值的文章主要介绍了贪吃蛇---C语言---详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

C语言已经学了不短的时间的,这期间已经开始C++和Python的学习,想给我的C语言收个尾,想起了小时候见过别人的老人机上的贪吃蛇游戏,自己父母的手机又没有这个游戏,当时成为了我的一大遗憾,这两天发现C语言实现这个项目似乎并不难,于是查了一些WindowsAPI的控制台函数,实现了这一游戏。如果你觉得你的C语言基础语法学的差不多了,又想实现贪吃蛇这样一个小游戏,那么就跟我一起来实现它吧。下面是最终成品的样子:

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

本贪吃蛇是用控制台实现,其中¥是贪吃蛇的食物,⚪是贪吃蛇,■是墙体。

Win32 API

在开始我们的代码之前,像讲一下关于Win32 API的相关知识,Windows这个多作业系统除了协调应用程序的执行,分配内存,管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗,绘制图形,使用周边设备等目的,由于这些函数的服务对象是应用程序(Application),所以便称之为Application Programming Interface,简称API函数。Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。

控制台程序

平常我们运行起来的黑框其实是就是控制台程序

我们可以用cmd命令来控制控制台窗口的长宽:比如设置窗口大小,30行,100列

mode con cols=100 lines=30

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

同时也可以通过命令修改窗口的名字:

title 贪吃蛇

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

这里注意一下,在改名字之后加一个getchar()保证程序处在运行状态,这样才能正确观察到要改后的名字。

这些能在控制台窗口执行的命令,像我上方图片中的代码一样,可以用C语言函数system来执行。

代码放在下面:

#include<stdlib.h>
int main()
{
	system("mode con cols=100 lines=30");//设置窗口大小
	system("title 贪吃蛇");//改窗口标题
	getchar();
	return 0;
}

这里注意一下system的头文件是

#include <stdlib.h>

控制台上的坐标COORD

COORD是Windows API中定义的一个结构体,表示一个字符在控制台屏幕上的坐标,下面是关于对COORD的定义:

typedef struct _COORD{
    SHORT x;
    SHORT y;
}COORD, *PCOORD;

其中x轴和y轴如图

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

同时可以给上方结构体(坐标)赋值:

COORD pos = {10,15};

GetStdHandle

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

HANDLE GetStdHandle(DWORD nStdHandle);//函数的参数为标准设备

句柄是什么?

句柄相当于一个操作工具,你可以通过操作某设备的句柄去获得和修改某标准设备的信息

实例(获得句柄)

HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性信息

BOOL WINAPI GetConsoleInfo(
    HANDLE hConsoleOutput,
    PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

实例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
//通过传CursorInfo的地址并通过函数将当前光标信息传给CursorInfo

CONSOLE_CURSOR_INFO

在上一份代码中CursorInfo,里面存的是光标信息,类型是CONSOLE_CURSOR_INFO,我们可以来看看这个类型是如何定义的

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;//这个变量是表示光标所占一个格的百分比
 BOOL bVisible;//这个变量是决定光标是否可见
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
  • dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的水平线条。
  • bVisible,游标的可见性。如果光标可见,则此成员为TRUE

我们在运行打印贪吃蛇的过程中将光标设置为不可见,就不会影响到整个游戏的美观

CursorInfo.bVisible = false; //隐藏控制台光标 

SetConsoleCursorInfo

上方的GetConsoleCursorInfo是通过函数获取光标信息,这次的函数是通过函数实在改变控制台光标信息,下面是本函数声明

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);//设置控制台光标状态 

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);
//通过以上代码可以将光标设置到10 5 位置上

看到这里,我们是否可以考虑封装一个函数,可以专门通过传入坐标来控制光标位置,于是封装了一个这样的函数Setpos

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

GetAsyncKeyState

这个函数用于获取按键情况,原型如下:

SHORT GetAsyncKeyState(
 int vKey
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位short数据中,最高位是1,说明按键的状态是按下,如果是0,说明最高位的状态是抬起;如果最低为被设置为1则说明,该按键被按过,否则为0。

如果我们要判断一个按键是否被按过,可以检测GetAsyncKeyState的返回值最低为的值是否为1,可据此写出一个宏

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)

这样就可以通过向KEY_PRESS传入键值直接监测按键是否被按过了。

下面是关于不同键值介绍的链接

Virtual-Key Codes (Winuser.h) - Win32 apps | Microsoft Learn

不过目前我们知道:

  1. VK_UP 向上箭头键 
  2. VK_DOWN 向下箭头键
  3. VK_LEFT 向左箭头键
  4. VK_RIGHT 向右箭头键
  5. VK_ESCAPE ESC按键
  6. VK_F3 F3按键
  7. VK_F4 F4按键

这些VK_XXX已经是头文件中用宏定义好的常量,直接用就行,不需要知道具体的值

就足够用了

贪吃蛇地图设计与分析

地图

如果想用控制台窗口打印地图,就需要了解一下控制台窗口坐标的知识

如下图所示,横向是X轴,从左向右增长,纵向是Y轴,从上到下依次增长

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

在地图上,我们打印墙体用宽字符■,打印蛇用宽字符●,打印食物我这里用的是宽字符¥(因为我个人比较喜欢)如果你在字符表里如果有别的喜欢的字符,也当然可以灵活的根据个人爱好改变

刚刚我介绍的时候介绍的字符是宽字符,意思是占两个字节的字符,普通的字符占一个字节

可以看看占两个字节字符和占一个字节字符的区别:

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

 由观察可以发现,一个占两字节的字符在控制台打印的时候也是占两个一字节字符所占的位置的

这里还需要引入一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。 C语言最初假定字符都是但自己的。但是这些假定并不是在世界的任何地方都适用。

下面引用一段介绍:

C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语 国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符 号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符 号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体 系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪 怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希 伯来语编码中却代表了字⺟Gimel,在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。

后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入宽字符的类型wchar_t和宽字符的输入,输出函数,加入<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

刚才打印方框的过程提到了本地化,如果不进行本地化,■将无法被程序编译识别,最终只会打印问号,所以接下来我们讲讲如何运用<locale.h>以及其函数对编译环境进行本地化。

<locale.h>本地化

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样的行为的部分。

标准中,依赖地区的部分有以下几项:

  • 数字的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表示形式

类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的⼀个宏, 指定⼀个类项:

  • LC_COLLATE
  •  LC_CTYPE 
  • LC_MONETARY
  • LC_NUMERIC
  • LC_TIME
  •  LC_ALL---针对所有类项修改

关于每个类项的详细说明,可参考

setlocale 函数

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

setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。

setlocale的第一个参数可以是前面说明的类项中的一个,那么只会影响一个类项,如果第一个参数是LC_ALL,那么就直接影响所有类项。

C标准给第二个参数仅定义了2种可能的取值:"C"和""。

在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL,"C");

当地区设为"C"时,库函数按正常方式执行。

如果想在程序运行时改变地区,就只能显示调用setlocale函数。用""作为第二个参数,调用setlocale函数就可以切换到本地模式,这种模式会适应本地环境。

当切换到我们本地模式后,就可以支持一些宽字符(如汉字)的占位输出了。

setlocale(LC_ALL, " ");//切换到本地环境

宽字符的打印

#include <stdio.h>
#include<locale.h>
int main() {
    setlocale(LC_ALL, "");
    wchar_t ch1 = L'●'; 
    wchar_t ch2 = L'你';
    wchar_t ch3 = L'好';
    wchar_t ch4 = L'¥';
    printf("%c%c\n", 'a', 'b');
    wprintf(L"%c\n", ch1);
    wprintf(L"%c\n", ch2);
    wprintf(L"%c\n", ch3);
    wprintf(L"%c\n", ch4);
    return 0;
}

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

这里比对的更加清晰一些,⼀个普通字符占⼀个字符的位置 但是打印⼀个汉字字符,占⽤2个字符的位置,那么我们如果 要在贪吃蛇中使⽤宽字符,就得处理好地图上坐标的计算。

关于普通字符和宽字符的处理展示大概是这个样子:

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

我们可以假设实现一个地图,27行,58列,围绕周围画出地图:

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

蛇和食物

初始化的时候,假设蛇长为5,蛇的每个节点宽字符●。这里要注意的是,蛇的每个节点和食物出现的X轴位置都要保证是二的倍数,不然会出现蛇和食物无法对齐或者蛇一半卡在墙体中的情况。

代码环节

数据的结构设计

上面说了这么多,到现在终于可以讲代码了,在学了这些控制台操作和地图分析之后,相信其实聪明的你已经基本能大概想出来如何去实现贪吃蛇的逻辑了,在开始代码之前,来介绍一下我们对我们对贪吃蛇数据的维护和设计

这里讲一下定义的每个节点的结构体:

typedef struct SnakeNode
{
	int x;//节点横坐标
	int y;//节点纵坐标
	struct SnakeNode* next;//指向下一个节点的指针
}SnakeNode, * pSnakeNode;//重命名结构体类型

如果要管理整条蛇,还需要我们封装一个Snake来维护整条蛇🐍:

typedef struct Snake
{
	pSnakeNode pSnake;//指向蛇头节点的指针
	pSnakeNode pFood;//指向食物的食物指针
	int Score;//当前分数
	int FoodWeight;//食物比重
	int SleepTime;//休眠时间
	enum GAME_STATUES status;//游戏当前状态
	enum DIRECTION dir;//蛇当前方向
}Snake, * pSnake;

在维护整条蛇的结构体类型中,定义了两个枚举类型,分别用来表示

游戏当前的状态:

enum GAME_STATUES {
	OK = 1,//游戏正常运行
	ESC,  //点击ESC主动退出
	KILL_BY_WALL,//撞到墙游戏结束
	KILL_BY_SELF //咬到自己游戏结束
};

蛇当前的前进方向:

enum DIRECTION {
	UP = 1,//上
	DOWN, //下
	LEFT, //左
	RIGHT //右
};

贪吃蛇项目流程设计 

这里介绍整个游戏过程中的运行逻辑,我们基本也是这个顺序展开代码

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

 游戏主函数:运行逻辑

#include"greedy_snake.h"
int main()
{
	srand((unsigned int)time(NULL));//随机初始化种子,相关内容可以参考我之前的扫雷博客
	int ch;
	do {
		Snake snake = { 0 };//创建一个维护整个贪吃蛇的数据类型
		snake.pSnake = NULL;
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(20, 15);
		printf("想要再来一局吗?Y/N:");
		ch = getchar();
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 26);
	return 0;
}

GameStart-游戏开始的数据初始化和维护

void GameStart(pSnake ps)
{
	//下面五行使光标不可见
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO rem;
	GetConsoleCursorInfo(houtput, &rem);
	rem.bVisible = 0;
	SetConsoleCursorInfo(houtput, &rem);
	setlocale(LC_ALL, "");//设置为本地类项
	//初始化界面
	system("mode con cols=100 lines=30");//设置窗口大小
	system("title 贪吃蛇");//改窗口标题
    //下面是打印欢迎和介绍信息
	SetPos(32, 10);
	printf("欢迎来到贪吃蛇小游戏!\n");
	SetPos(33, 15);
	system("pause");
	system("cls");
	SetPos(29, 9);
	printf("游戏介绍:");
	SetPos(33, 11);
	printf("通过↑ ← ↓ →控制蛇的移动");
	SetPos(33, 13);
	printf("可以通过F3加速,F4减速");
	SetPos(33, 15);
	printf("更高的速度下可以获得更高的分数");
	SetPos(33, 17);
	printf("可以使用空格暂停");
	SetPos(33, 19);
	system("pause");//这个命令可以使游戏暂停,按任意键继续
	//绘制地图
	CreateMap();
	//初始化创建蛇,传ps
	InitSnake(ps);
	//初始化创建食物,传ps
	CreateFood(ps);
}

SetPos-设置光标位置

void SetPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}

CreateMap-绘制地图

void CreateMap()
{
	system("cls");
	SetPos(0, 0);
    //这里的WALL在头文件中用宏定义:#define WALL L'■'
	//上
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//左和右
	for (int i = 1; i <= 25; i++) {
		SetPos(0, i);
		wprintf(L"%lc", WALL);
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
    //打印右侧边框的提示介绍信息
	SetPos(62, 15);
	printf("通过↑←↓→控制蛇的移动");
	SetPos(62, 16);
	printf("可以通过F3加速,F4减速");
	SetPos(62, 17);
	printf("更高的速度下可以获得更高的分数");
	SetPos(62, 18);
	printf("可以使用空格暂停");
}

CreateFood-初始化创建食物

void CreateFood(pSnake ps)
{
	int xx = 0;
	int yy = 0;
    //生成的地址不能在地图外,不能在蛇身上
	do
	{
		xx = rand() % 53 + 2;
		yy = rand() % 25 + 1;
		if (xx % 2 == 0) {
			pSnakeNode pcur = ps->pSnake;
			while (pcur) {
				if (xx == pcur->x && yy == pcur->y)
					goto again;
				pcur = pcur->next;
			}
			break;
		}
	again:;//循环直到生成正确的地址
	} while (1);
	pSnakeNode PFood = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (PFood == NULL) {
		perror("malloc food fail:");
		exit(1);
	}
	PFood->x = xx;
	PFood->y = yy;
	ps->pFood = PFood;
	SetPos(xx, yy);
    //食物在宏中定义为:#define FOOD L'¥'
	wprintf(L"%lc", FOOD);
}

GameRun-游戏运行维护函数

void GameRun(pSnake ps)
{
	do {
		//打印游戏帮助信息
		SetPos(62, 10);
		printf("总分:%d\n", ps->Score);
		SetPos(62, 11);
		printf("食物分值:%2d\n", 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 = ESC;
			break;
		}
		else if (KEY_PRESS(VK_F3)) {//F3设置加速
			if (ps->SleepTime >= 80) {
				ps->SleepTime -= 30;
				ps->FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4)) {//F4设置减速
			if (ps->FoodWeight > 2) {
				ps->SleepTime += 30;
				ps->FoodWeight -= 2;
			}
		}
		else if (KEY_PRESS(VK_SPACE))//空格设置暂停
		{
			while (1) {
				Sleep(100);
				if (KEY_PRESS(VK_SPACE)) {
					break;
				}
			}
		}
		//睡一下
		Sleep(ps->SleepTime);
		//根据按键控制蛇的运动和吃食物,并打印
		SnakeMove(ps);
	} while (ps->status == OK);
}

SnakeMove-蛇移动

void SnakeMove(pSnake ps)
{
    //根据在GameRun中获得的方向设置生成蛇的下一个节点
	pSnakeNode pNext = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (pNext == NULL) {
		perror("malloc pNext fail:");
		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 (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) {//如果吃上食物
		EatFood(ps, pNext);
	}
	else {//如果没吃上食物
		NotEatFood(ps, pNext);
		KillByWall(ps);//判断是否撞墙
		KillBySelf(ps);//判断是否咬到自己
	}
}
EatFood-吃到食物后蛇增长
void EatFood(pSnake ps,pSnakeNode pNext)
{
    //将新节点赋给蛇
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//打印蛇
	pSnakeNode pcur = ps->pSnake;
	while (pcur) {
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	ps->Score += ps->FoodWeight;
    //释放并创建新食物
	free(ps->pFood);
	CreateFood(ps);
}
NotEatFood-没有吃到食物向后移动
void NotEatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//打印蛇
	pSnakeNode pcur = ps->pSnake;
	while (pcur->next->next) {
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");//将最后一个节点置空
	free(pcur->next);
	pcur->next = NULL;
	SetPos(pcur->x, pcur->y);
    //蛇的身体在头文件中用宏定义为:#define BODY L'●'
	wprintf(L"%lc", BODY);
}
KillByWall-撞墙判定
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;//如果撞墙改变游戏状态
	}
}
KillBySelf-咬到自己判定
void KillBySelf(pSnake ps)
{
	pSnakeNode pcur = ps->pSnake->next;
	while (pcur) {
		if (pcur->x == ps->pSnake->x && pcur->y == ps->pSnake->y)
		{
			ps->status = KILL_BY_SELF;//如果要到自己改变游戏状态
			return;
		}
		pcur = pcur->next;
	}
}

GameEnd-游戏善后,释放蛇

void GameEnd(pSnake ps)
{
    //打印结束信息
	SetPos(20, 11);
	switch (ps->status)
	{
	case ESC:
		printf("正常退出游戏\n");
		SetPos(20, 13);
		printf("你的得分是%d", ps->Score);
		break;
	case KILL_BY_WALL:
		printf("撞墙了,游戏结束!\n");
		SetPos(23, 13);
		printf("你的得分是%d", ps->Score);
		break;
	case KILL_BY_SELF:
		printf("咬到自己了,游戏结束!\n");
		SetPos(23, 13);
		printf("你的得分是%d", ps->Score);
		break;
	}
    //释放蛇
	pSnakeNode pcur = ps->pSnake;
	pSnakeNode del = ps->pSnake;
	while (pcur) {
		del = pcur;
		pcur = pcur->next;
		free(del);
	}
	ps->pSnake = NULL;
	SetPos(0, 26);
	free(ps->pFood);
	ps = NULL;
}

代码汇总

写了这么多,大概就介绍完了所有函数,现在将它们放到三个文件中,相应创建文件CV一下应该就能在你们的VS跑了

头文件-greedy_snake.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<windows.h>
#include<locale.h>
#include<time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'■' 
#define BODY L'●' 
#define FOOD L'¥' 
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1?1:0)

enum GAME_STATUES {
	OK = 1,
	ESC,
	KILL_BY_WALL,
	KILL_BY_SELF
};

enum DIRECTION {
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

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 GAME_STATUES status;//游戏当前状态
	enum DIRECTION dir;//蛇当前方向
}Snake, * pSnake;

//游戏开始的维护
void GameStart(pSnake ps);

//绘制地图
void CreateMap();

//初始化蛇
void InitSnake(pSnake ps);

//初始化食物
void CreateFood(pSnake ps);

//设置光标位置
void SetPos(int x, int y);

//游戏运行维护函数
void GameRun(pSnake ps);

//游戏结束善后
void GameEnd(pSnake ps);

//蛇移动
void SnakeMove(pSnake ps);

源文件-greedy_snake.c

#include"greedy_snake.h"

//设置光标位置
void SetPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}
void CreateMap()
{
	system("cls");
	SetPos(0, 0);
	//上
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//左
	for (int i = 1; i <= 25; i++) {
		SetPos(0, i);
		wprintf(L"%lc", WALL);
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	SetPos(62, 15);
	printf("通过↑←↓→控制蛇的移动");
	SetPos(62, 16);
	printf("可以通过F3加速,F4减速");
	SetPos(62, 17);
	printf("更高的速度下可以获得更高的分数");
	SetPos(62, 18);
	printf("可以使用空格暂停");
}

//初始化蛇
void InitSnake(pSnake ps)
{
	//创建五个蛇身节点
	pSnakeNode pcur = NULL;
	for (int i = 0; i < 5; i++) {
		pcur = (SnakeNode*)malloc(sizeof(SnakeNode));
		if (pcur == NULL) {
			perror("malloc 节点 fail:");
			exit(1);
		}
		pcur->x = POS_X + 2 * i;
		pcur->y = POS_Y;
		pcur->next = NULL;
		if (ps->pSnake == NULL) {
			ps->pSnake = pcur;
		}
		else {
			pcur->next = ps->pSnake;
			ps->pSnake = pcur;
		}
	}
	//打印蛇身
	pcur = ps->pSnake;
	while (pcur) {
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	//贪吃蛇信息初始化
	ps->dir = RIGHT;
	ps->FoodWeight = 10;
	ps->pFood = NULL;
	ps->Score = 0;
	ps->SleepTime = 200;
	ps->status = OK;
}

void CreateFood(pSnake ps)
{
	int xx = 0;
	int yy = 0;
	do
	{
		xx = rand() % 53 + 2;
		yy = rand() % 25 + 1;
		if (xx % 2 == 0) {
			pSnakeNode pcur = ps->pSnake;
			while (pcur) {
				if (xx == pcur->x && yy == pcur->y)
					goto again;
				pcur = pcur->next;
			}
			break;
		}
	again:;
	} while (1);
	pSnakeNode PFood = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (PFood == NULL) {
		perror("malloc food fail:");
		exit(1);
	}
	PFood->x = xx;
	PFood->y = yy;
	ps->pFood = PFood;
	SetPos(xx, yy);
	wprintf(L"%lc", FOOD);
}

void GameStart(pSnake ps)
{
	//下面五行使光标不可见
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO rem;
	GetConsoleCursorInfo(houtput, &rem);
	rem.bVisible = 0;
	SetConsoleCursorInfo(houtput, &rem);
	setlocale(LC_ALL, "");//设置为本地类项
	//初始化界面
	system("mode con cols=100 lines=30");//设置窗口大小
	system("title 贪吃蛇");//改窗口标题
	SetPos(32, 10);
	printf("欢迎来到贪吃蛇小游戏!\n");
	SetPos(33, 15);
	system("pause");
	system("cls");
	SetPos(29, 9);
	printf("游戏介绍:");
	SetPos(33, 11);
	printf("通过↑ ← ↓ →控制蛇的移动");
	SetPos(33, 13);
	printf("可以通过F3加速,F4减速");
	SetPos(33, 15);
	printf("更高的速度下可以获得更高的分数");
	SetPos(33, 17);
	printf("可以使用空格暂停");
	SetPos(33, 19);
	system("pause");
	//绘制地图
	CreateMap();
	//初始化创建蛇,传ps
	InitSnake(ps);
	//初始化创建食物,传ps
	CreateFood(ps);
}

void EatFood(pSnake ps,pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//打印蛇
	pSnakeNode pcur = ps->pSnake;
	while (pcur) {
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	ps->Score += ps->FoodWeight;
	free(ps->pFood);
	CreateFood(ps);
}

void NotEatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//打印蛇
	pSnakeNode pcur = ps->pSnake;
	while (pcur->next->next) {
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
	SetPos(pcur->x, pcur->y);
	wprintf(L"%lc", BODY);
}

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 pcur = ps->pSnake->next;
	while (pcur) {
		if (pcur->x == ps->pSnake->x && pcur->y == ps->pSnake->y)
		{
			ps->status = KILL_BY_SELF;
			return;
		}
		pcur = pcur->next;
	}
}

void SnakeMove(pSnake ps)
{
	pSnakeNode pNext = (SnakeNode*)malloc(sizeof(SnakeNode));
	if (pNext == NULL) {
		perror("malloc pNext fail:");
		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 (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) {
		EatFood(ps, pNext);
	}
	else {
		NotEatFood(ps, pNext);
		KillByWall(ps);
		KillBySelf(ps);
	}
}

void GameRun(pSnake ps)
{
	do {
		//打印游戏帮助信息
		SetPos(62, 10);
		printf("总分:%d\n", ps->Score);
		SetPos(62, 11);
		printf("食物分值:%2d\n", 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 = ESC;
			break;
		}
		else if (KEY_PRESS(VK_F3)) {
			if (ps->SleepTime >= 80) {
				ps->SleepTime -= 30;
				ps->FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4)) {
			if (ps->FoodWeight > 2) {
				ps->SleepTime += 30;
				ps->FoodWeight -= 2;
			}
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			while (1) {
				Sleep(100);
				if (KEY_PRESS(VK_SPACE)) {
					break;
				}
			}
		}
		//睡一下
		Sleep(ps->SleepTime);
		//根据按键控制蛇的运动和吃食物,并打印
		SnakeMove(ps);
	} while (ps->status == OK);
}

void GameEnd(pSnake ps)
{
	SetPos(20, 11);
	switch (ps->status)
	{
	case ESC:
		printf("正常退出游戏\n");
		SetPos(20, 13);
		printf("你的得分是%d", ps->Score);
		break;
	case KILL_BY_WALL:
		printf("撞墙了,游戏结束!\n");
		SetPos(23, 13);
		printf("你的得分是%d", ps->Score);
		break;
	case KILL_BY_SELF:
		printf("咬到自己了,游戏结束!\n");
		SetPos(23, 13);
		printf("你的得分是%d", ps->Score);
		break;
	}
	pSnakeNode pcur = ps->pSnake;
	pSnakeNode del = ps->pSnake;
	while (pcur) {
		del = pcur;
		pcur = pcur->next;
		free(del);
	}
	ps->pSnake = NULL;
	SetPos(0, 26);
	free(ps->pFood);
	ps = NULL;
}

运行文件-snake_run.c

#include"greedy_snake.h"
int main()
{
	srand((unsigned int)time(NULL));
	int ch;
	do {
		Snake snake = { 0 };
		snake.pSnake = NULL;
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(20, 15);
		printf("想要再来一局吗?Y/N:");
		ch = getchar();
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 26);
	return 0;
}

 运行截图

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

 贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

 贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

 贪吃蛇---C语言---详解,我的小项目,c语言,开发语言

结尾

到这里,本篇博客的内容基本上就结束了,写博客不易,如果感觉对你有帮助的话,还请留个赞留个关注再走啊。博主的C语言语法学习之路到现在也算是真正结束,统计下来C语言将近学了三四遍了,在后面的时间里,我准备好好开始过数据结构的内容,这些时日是没有特别多的时间去写题攻算法了,给自己报了一堆比赛还需要去准备,还是要先把C++和Python在假期赶快速成一下,数据结构系统仔细的过上一遍,给未来打好基础。后期我还会继续产出有意思的内容,请大家多多关注我吧!

在这里记录一下,今天是2024.1.31,大一的寒假♥文章来源地址https://www.toymoban.com/news/detail-827412.html

到了这里,关于贪吃蛇---C语言---详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言项目实战——贪吃蛇游戏,附源码

    目录          一、在Visual Studio2010上创建C项目 二、对于新建项目的测试 三、贪吃蛇游戏        1.游戏准备         2.游戏界面大小及背景颜色确定         3. 画出食物          4.画蛇且使蛇移动          5.通过按键控制蛇的移动          6.蛇吃食物的问题  

    2024年01月20日
    浏览(47)
  • C语言实战项目-贪吃蛇小游戏

    1.定义蛇对象、食物对象 2.初始化蛇、初始化食物 3.控制流程: ( 1)蛇头和墙壁的碰撞 (2)蛇头和蛇身体碰撞 (3)蛇头和食物碰撞 (4)移动速度增大 4.游戏效果: ( 1)蛇身增长 (2)食物消失 -- 新食物产生 (3)分数累加 (4)移动速度增大 (5)蛇的移动 (6)显示分

    2024年02月19日
    浏览(48)
  • C语言实现的贪吃蛇(无EasyX,详解)

    或许厌倦了枯燥的做题,那就学学贪吃蛇,激发你的学习乐趣吧~ 你将进一步加深对结构体,单链表,函数,循环等基础的理解。 希望对你有所帮助~ 纯c语言实现的贪吃蛇小游戏 提前说明 必须是要 .cpp 后缀文件才可以使用, .c 文件不支持。 因为这里的头文件中包含了c++的内

    2024年01月21日
    浏览(46)
  • C语言小项目——小游戏贪吃蛇的实现

    我们可以使用 mode命令来设置控制台的大小,使用title命令来设置控制台标题。 在C语言中调用库函数 system来使用这些作用于控制台的命令 。使用代码如下: 采用该命令后控制台界面大概如下:   控制台坐标系以控制台左上角为原点从上往下y轴坐标增大,从左往右x轴坐标增

    2024年04月27日
    浏览(39)
  • C语言结课实战项目_贪吃蛇小游戏

    ✨✨所属专栏:C语言✨✨ ✨✨作者主页:嶔某✨✨ 游戏源代码链接: function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com) • 贪吃蛇地图绘制 • 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、

    2024年04月26日
    浏览(44)
  • 贪吃蛇项目(基于C语言和数据结构中的链表)

    首先先建立3个文件。 Snake.h 函数的声明 Snake.c 函数的定义 Test.c     贪吃蛇的测试    我们分析这整个项目 首先在我们实现游戏开始的部分之前,我们要先创建贪吃蛇的节点,再由此创建整个贪吃蛇所包含的一些信息: 我们枚举一下这个贪吃蛇中所有的一些状态: 然后我们

    2024年02月20日
    浏览(58)
  • 小程序 检测是否添加至我的小程序

    添加小程序到我的小程序,给用户发卷,增加用户的添加,提高小程序的使用率 wx.checkIsAddedToMyMiniProgram() 功能描述 基础库 2.29.1 开始支持,低版本需做兼容处理。 检查小程序是否被添加至 「我的小程序」 属性 类型 默认值 必填 说明 success function 否 接口调用成功的回调函数

    2024年02月07日
    浏览(34)
  • Python 游戏开发 如何写一款贪吃蛇游戏详解 - 曲速引擎(Warp Drive )

    贪吃蛇游戏是一款经典的电子游戏,其核心玩法简单但富有挑战性。玩家控制一条不断成长的蛇,在一个封闭的空间内移动。游戏的目标是尽可能长时间地生存下去,同时吃掉出现在屏幕上的食物来增加得分和蛇的长度。以下是贪吃蛇游戏的主要玩法和逻辑: 玩家通过键盘上

    2024年03月26日
    浏览(90)
  • 为什么我的小程序审核不通过?常见原因及解决方法

    作为程序员、小程序的开发者,工作中比开发小程序还要让人头疼的事,也就只有就是让小程序通过审核了!每隔三五天,总会看到有同行在社区吐槽“吐槽下微信小程序审核机制”、“微信小程序审核不通过 放弃了,细数坑坑”。。。 认证费问题 在讲小程序审核问题之前

    2024年02月10日
    浏览(55)
  • Taro+NutUi 开发不同平台的小程序系列 -第一章节/项目多平台运行测试

    写在前面 今天开始我们写 Taro+NutUi 的文章,第一篇先从创建一个项目开始,后续我们持续增加功能,发布到不同的平台开发工具上看一下具体的渲染效果怎么样,之前 uniapp 也是支持各个平台发布的,甚至可以直接发布到 app 上,我前面的文章也写过,今天我们使用 Taro 将这

    2024年02月04日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包