c语言实现贪吃蛇小游戏————附全代码!!!

目录

1.Win32 API

1.1控制台应用程序

1.2控制台的名称,控制台窗口大小

 1.3设置控制台光标位置

COORD - 光标坐标

GetStdHandle - 获取句柄

SetConsoleCursorPosition - 设置光标位置

封装一个设置光标的函数

1.4设置控制台光标的属性

CONSOLE_CURSOR_INFO - 光标属性

SetConsoleCursorInfo - 设置光标属性

1.5 GetAsyncKeyState - 获取按键状态

2.贪吃蛇游戏注意事项讲解

 2.1地图的设计

2.1.1setlocale

2.1.2宽字符的打印

 2.1.3地图的坐标

2.2 贪吃蛇的设计

2.3贪吃蛇 与 食物 在地图上的打印

3.贪吃蛇游戏流程设计

4.贪吃蛇全代码:

Snake.h

Snake.c

Snaketest.c


注意事项:

  • 本项目使用的环境是vs2022
  • 该项目下的控制台程序需要改成 :Windows 控制台主机 

1.Win32 API

在此项目中我们需要用到 Win32 API 中的一些函数完成对控制台应用程序的操作,接下来我们将挨个介绍

Win32 API(Windows 32-bit Application Programming Interface)是微软为Windows操作系统提供的一套应用程序编程接口(API)。它允许开发者使用C或C++等编程语言来编写Windows桌面应用程序。Win32 API涵盖了从基本的窗口管理、图形绘制到更高级的网络编程、文件I/O和线程同步等各种功能。

1.1控制台应用程序

控制台应用程序的一些属性:

1.控制台名称,控制的窗口大小

2.控制台光标位置

3.控制台光标的大小属性

除了控制台名称以外的属性,接下来我们都要使用Win32 API 中的函数 来对它们进行修改

1.2控制台的名称,控制台窗口大小

设置控制台窗口的⼤⼩,30行,100列 :
mode con cols= 100 lines= 30

 也可以通过命令设置控制台窗⼝的名字:

title 贪吃蛇

这些能在控制台窗口执行的命令,也可以调⽤C语⾔函数system来执⾏。例如:

#include <stdio.h>
int main()
{
     //设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
     system("mode con cols=100 lines=30");

     //设置cmd窗⼝名称
     system("title 贪吃蛇"); 

     return 0;
}

 1.3设置控制台光标位置

想要精确的定位控制台光标的位置,我们首先要知道控制台是有坐标轴的。

有了坐标轴的概念,我们才能更好的去设置控制

COORD - 光标坐标

COORD是 Windows API 中定义的⼀个结构体,表示⼀个字符在控制台屏幕幕缓冲区上的坐标

COORD类型的声明:

typedef struct _ COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

创建一个COORD类型的结构体类型变量,并赋值:

COORD pos = { 10, 15 };

GetStdHandle - 获取句柄

包含 <windows.h> 头文件中 

 函数原型:HANDLE GetStdHandle(DWORD nStdHandle);

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

参数:

 DWORD nStdHandle:标准设备,此参数的取值可为下列值之一

含义
STD_INPUT_HANDLE标准输入设备,通常为键盘
STD_OUTPUT_HANDLE标准输出设备,通常为屏幕
STD_ERROR_HANDLE标准错误设备,通常为屏幕

返回值: 

HANDLE : 该返回值其实是一个指向HANDLE结构体的指针

经过重命名 --- typedef void *HANDLE 为 HANDLE

也可以把它称为一个句柄,通过句柄我们可以修改控制台的属性

使用: 

int main() {

    //创建一个HANDLE指针变量
	HANDLE hOutput = NULL;

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

	return 0;
}

SetConsoleCursorPosition - 设置光标位置

包含 <windows.h> 头文件中

函数原型:

BOOL WINAPI SetConsoleCursorPosition (
    HANDLE hConsoleOutput,
    COORD pos
);

设置屏幕缓冲区新的光标位置 

参数:

HANDLE hConsoleOutput : HANDLE指针变量(控制台屏幕的句柄)

COORD pos : 指定新光标位置(以字符为单位)的 COORD 结构。

返回值:

如果该函数成功,则返回值为非零值。

如果函数失败,则返回值为零。 

使用:

将光标的位置定位到 x = 10 y = 5 的位置

int main(){
    //创建一个HANDLE指针变量
	HANDLE hOutput = NULL;

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

	//设置COORD结构体变量的 x y 值
	COORD pos = { 10 , 5 };

	//设置新的光标位置
	SetConsoleCursorPosition(hOutput, pos);

	getchar();
}

封装一个设置光标的函数

//设置光标的坐标
void SetPos(short x, short y)
{

	//获取标准输出的句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//设置标准输出上光标的位置为pos
	COORD pos = { x, y };
	SetConsoleCursorPosition(hOutput, pos);
	
}

1.4设置控制台光标的属性

CONSOLE_CURSOR_INFO - 光标属性

这个结构体包含控制台光标的属性
typedef struct _ CONSOLE_CURSOR_INFO {
    DWORD dwSize;
    BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

成员 DWORD dwSize --- 由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。

成员 BOOL bVisible --- 游标的可⻅性。 如果光标可⻅,则此成员为 true,如果光标不可见,则此成员为 false

创建一个COORD类型的结构体类型变量,并赋值:

int main(){

    //创建一个CONSOLE_CURSOR_INFO结构体变量,并且赋值
    CONSOLE_CURSOR_INFO CursorInfo = {50 , false };

    return 0;
}

SetConsoleCursorInfo - 设置光标属性

包含 <windows.h> 头文件中

函数原型:

BOOL WINAPI SetConsoleCursorInfo (
    HANDLE hConsoleOutput,
    const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

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

参数: 

HANDLE hConsoleOutput :HANDLE指针变量(控制台屏幕的句柄)

const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo :指向 CONSOLE_CURSOR_INFO 结构的指针,该结构为控制台屏幕缓冲区的光标提供新的规范。

返回值 :

如果该函数成功,则返回值为非零值。

如果函数失败,则返回值为零。

使用 :

隐藏控制台光标操作

 int main(){   
    //影藏光标操作

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

	CONSOLE_CURSOR_INFO CursorInfo = { 1,false };//创建结构体,并赋值以你想要设置的控制台光标属性

	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标新的属性

    return 0;
}

1.5 GetAsyncKeyState - 获取按键状态

 包含 <windows.h> 头文件中

函数原型:

SHORT GetAsyncKeyState (
    int vKey
);

确定调用函数时键是向上还是向下,以及上次调用 GetAsyncKeyState 后是否按下了该键。

 参数:

int vKey --- 键盘上各个按键的虚拟键码   具体虚拟键码点击此处查看

返回值: 

函数的返回值是一个SHORT类型,表示指定键的状态。如果指定的键被按下,则返回值的最高位(位15)为1,同时如果自上次调用GetAsyncKeyState以来键已被按过,则最低位(位0)也为1。如果键未被按下,则返回值为0。因此,如果返回值是负数,表示该键此前被按下并一直保持按下状态。

使用:

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

写一个宏,用来判断一个键是否被按过
//原理 : 判断返回值的最低为是否为1,是的话返回 1,不是的话返回 0
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

2.贪吃蛇游戏注意事项讲解

我们最终实现的游戏大概长这个样子

 

 2.1地图的设计

在游戏地图上,我们打印墙体使用的是宽字符:□ ,打印蛇使用宽字符 :● ,打印食物使用宽字符:☆
普通的字符是占⼀个字节的,这类宽字符是占用2个字节。( wchar_t --- 宽字符类型
那么我们要怎么打印出宽字符呢?这时就需要用到 setlocale 函数,对编译器进行本地化了

2.1.1setlocale

包含 <locale.h> 头文件中

函数原型:char* setlocale (int category, const char* locale);

对编译器进行本地化设置,编译器才能支持我们宽字符(汉字)的输出

参数 :

int category :

受影响的类项,主要有下面这几个参数

参数受影响的类项
LC_COLLATE
影响字符串⽐较函数 strcoll() strxfrm()
LC_CTYPE
影响字符处理函数的⾏为
LC_MONETARY
影响货币格式
LC_NUMERIC
影响 printf() 的数字格式
LC_TIME
影响时间格式 strftime() wcsftime()
LC_ALL
针对所有类项修改,将以上所有类别设置为给定的语⾔环境

const char* locale : 

C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)

在任意程序开始执行的时候都会隐藏执行调用:

setlocale(LC_ALL, "C");

返回值 :

成功后,指向 C 字符串的指针

如果函数无法设置新的区域设置,则不会修改此设置,并返回空指针

使用:

设置本地化模式,后就⽀持宽字符(汉字)的输出等。

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

2.1.2宽字符的打印

宽字符的定义:

宽字符数据类型为 wchar_t 当我们定义一个宽字符常量的时候,我们要在应号之前加上 :L

例如:

#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'★';
 
 return 0;
}

宽字符的打印:

宽字符的打印需要用到函数 wprintf () ,用法与printf ()类似

宽字符的占位符为:%lc

例外在函数wprintf()格式的前面也要加上 :L  ,才能正确的打印宽字符

例如:

#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'★';
 
 
 wprintf(L"%lc\n", ch1);
 wprintf(L"%lc\n", ch2);
 wprintf(L"%lc\n", ch3);
 wprintf(L"%lc\n", ch4);

 return 0;

}

 2.1.3地图的坐标

例如我们想实现27行 ,58 列的一个贪吃蛇地图,在围绕着地图填充墙体

注意:

一个窄字符在屏幕缓冲区上是一个长方形

一个宽字符在屏幕缓冲区上占两个窄字符

所以当我们围绕贪吃蛇地图填充墙的时候,会变成图上这样

2.2 贪吃蛇的设计

设计一个结构体来管理我们的贪吃蛇

//蛇的身体
typedef struct SnakeNode {
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,* pSnakeNode;

//蛇头的方向
enum DIRECTION {
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//贪吃蛇的状态
enum GAME_STATUSL {
	OK,//正常状态
	KILL_BY_WALL,//撞到墙死了
	KILL_BY_SELF,//撞到自己死了
	END_NORMAL//游戏正常退出
};

//贪吃蛇对象
typedef struct Snake {
	pSnakeNode _pSnake;             //指向蛇头的指针
	pSnakeNode _pFood;             //指向食物节点的指针
	enum DIRECTION _dir;          //蛇头的方向
	enum GAME_STATUSL _status;    //游戏此时的状态
	int _food_weight;           //一个食物的分数
	int _score;                //游戏总分
	int _sleep_time;          //游戏休眠的时间,休眠时间越短,速度越快,休眠时间越长,速度越慢
}Snake,* pSnake;

贪吃蛇的属性我们选着利用结构体来维护它

2.3贪吃蛇 与 食物 在地图上的打印

初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24, 5)处开始出现蛇,连续5个节点。
关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然后打印★。
注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中, 另外⼀般在墙外的现象,坐标不好对齐。食物也是如此

3.贪吃蛇游戏流程设计

4.贪吃蛇全代码:

Snake.h

#pragma once
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>

#define WALL L'□'
#define pos_x 24
#define pos_y 5
#define BODY L'●'
#define FOOD L'☆'

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

//蛇的身体
typedef struct SnakeNode {
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

//蛇头的方向
enum DIRECTION {
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//贪吃蛇的状态
enum GAME_STATUSL {
	OK,//正常状态
	KILL_BY_WALL,//撞到墙死了
	KILL_BY_SELF,//撞到自己死了
	END_NORMAL//游戏正常退出
};

//贪吃蛇对象
typedef struct Snake {
	pSnakeNode _pSnakeHead;             //指向蛇头的指针
	pSnakeNode _pFood;             //指向食物节点的指针
	enum DIRECTION _dir;          //蛇头的方向
	enum GAME_STATUSL _status;    //游戏此时的状态
	int _food_weight;           //一个食物的分数
	int _score;                //游戏总分
	int _sleep_time;          //游戏休眠的时间,休眠时间越短,速度越快,休眠时间越长,速度越慢
}Snake, * pSnake;

//1.游戏开始
void GameStart(pSnake ps);

//定位光标函数
void SetPos(short x,short y);

//打印欢迎界面
WelcomeToGame();

//地图绘制
CreateMap();

//创建一条贪吃蛇,并且部分初始化
void InitSnake(pSnake ps);

//创建食物
void CreatFood(pSnake ps);

//2.游戏运行
void GameRun(pSnake ps);

//暂停
void Pause();

//蛇走一步的过程
void SnakeMove(pSnake ps);

//判断下一个位置是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps);

//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);

//不是食物走一步
void NoFood(pSnakeNode pn, pSnake ps);

//检测蛇是否撞到墙
void KillByWall(pSnake ps);

//检测蛇是否撞到自己
void KillBySelf(pSnake ps);

//3.游戏结束
void GameOvre(pSnake ps);

Snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"
//定位光标函数
void SetPos(short x,short y) {

	//获取句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//设置标准输出上光标的位置为pos
	COORD pos = { x, y };
	SetConsoleCursorPosition(hOutput, pos);

}

//打印欢迎界面
WelcomeToGame() {

	SetPos(35,13);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(36, 17);
	system("pause");

	system("cls");

	SetPos(24, 13);
	wprintf(L"按↑ . ↓ . ← . → 来控制蛇的移动,F3加速 ,F4减速");
	SetPos(35, 14);
	wprintf(L"加速能够得到更高的分数");
	SetPos(36, 17);
	system("pause");

	system("cls");

}

//绘制地图
CreateMap() {

	//上墙
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}

	//下墙
	SetPos(0,26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	
	//左墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0,i);
		wprintf(L"%lc", WALL);
	}

	//右墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

//创建一条贪吃蛇,并且部分初始化
void InitSnake(pSnake ps) {
	//初始化蛇头方向,一个食物分数,总分数,屏幕休眠时间,蛇状态
	ps->_dir = RIGHT;
	ps->_food_weight = 10;
	ps->_score = 0;
	ps->_sleep_time = 200;
	ps->_status = OK;

	//初始化蛇身
	pSnakeNode cur = NULL;

	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));//创建新的蛇身节点
		if (cur == NULL)
		{
			perror("InitSnak()::malloc()");
			return;
		}

		//设置蛇身节点坐标,即让每个节点成为新的蛇头
		cur->x = pos_x + i * 2;  //每次新的节点x轴坐标都比前一个蛇身节点多2格
		cur->y = pos_y;
		cur->next = NULL;

		//使用头插法把蛇身节点都连接起来
		if (ps->_pSnakeHead == NULL)
		{
			ps->_pSnakeHead = cur;   //蛇身一个节点都没有的情况
		}
		else
		{
			cur->next = ps->_pSnakeHead;
			ps->_pSnakeHead = cur;
		}

	}

	//将蛇打印在地图上
	cur = ps->_pSnakeHead;
	while (cur)
	{
		SetPos(cur->x,cur->y);
		wprintf(L"%lc",BODY);
		cur = cur->next;
	}

}

//创建食物
void CreatFood(pSnake ps) {
	int x;
	int y;
	//为食物创建随机的坐标
again:
	do 
	{
		//食物的坐标要在墙体内
		x = rand() % 53 + 2;  //x轴的范围是 2 ~ 54
		y = rand() % 25 + 1;  //y轴的范围是 1 ~ 25
	} while (x % 2 != 0);

	//检查食物坐标是否与蛇身重叠
	pSnakeNode cur = ps->_pSnakeHead;
	while (cur) 
	{
		if ((cur->x == x) && (cur->y == y))
		{
			goto again;//若重叠,则重新为食物创建坐标
		}
		cur = cur->next;
	}

	//创建食物节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreatFood()::malloc()");
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	//记录食物节点到贪吃蛇中
	ps->_pFood = pFood;

	//打印食物
	SetPos(x,y);
	wprintf(L"%lc", FOOD);
	
}

//一.游戏开始
void GameStart(pSnake ps) {
	//1.设置窗口大小,名称
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//2.隐藏光标
	HANDLE hOutput = NULL;
	//获取标准输出的句柄(⽤来标识不同设备的数值)
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo = { 1,false }; //创建结构体,并赋值以你想要设置的控制台光标属性
	SetConsoleCursorInfo(hOutput, &CursorInfo);

	//3.打印欢迎界面
	WelcomeToGame();

	//4.地图绘制
	CreateMap();

	//5.将贪吃蛇属性部分初始化
	InitSnake(ps);

	//6.创建食物
	CreatFood(ps);

	/*getchar();*/
	
}
PrintfHelpInfo() {

	SetPos(62, 10);
	wprintf(L"%ls", L"不能穿墙,不能撞到自己");
	SetPos(62, 11);
	wprintf(L"%ls", L"按↑ . ↓ . ← . → 来控制蛇的移动");
	SetPos(62, 12);
	wprintf(L"%ls", L"F3加速 ,F4减速");
	SetPos(62, 13);
	wprintf(L"%ls", L"按Esc退出游戏,按空格暂停游戏");

	SetPos(62, 16);
	wprintf(L"%ls", L"———玖伍出品———");
}

//暂停
void Pause() {
	while (1) {
		Sleep(200);//为了省性能加上屏幕睡眠时间,否者一直死循环浪费新能
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//判断下一个位置是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps) {
	return ((pn->x == ps->_pFood->x) && (pn->y == ps->_pFood->y));
}

//吃掉食物
//pSnakeNode psn 是计算的下⼀个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode pn, pSnake ps) {

	//头插法,将即将要吃掉的食物节点成为新的头结点
	ps->_pFood->next = ps->_pSnakeHead;//食物节点成为新的头节点
	ps->_pSnakeHead = ps->_pFood;      //改变贪吃蛇的蛇头属性

	//打印蛇
	pSnakeNode cur = ps->_pSnakeHead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%c", BODY);    //第一次循环就将原来的食物节点覆盖了,不用做过多的处理
		cur = cur->next;
	}

	//吃到食物加总分
	ps->_score += ps->_food_weight;

	//释放我们计算的下一个节点
	free(pn);
	pn = NULL;

	//创建新的食物
	CreatFood(ps);
}

//不是食物走一步
void NoFood(pSnakeNode pn, pSnake ps) {

	//走一步的过程:将计算的下一个节点头插,打印新的蛇(除了尾节点),最后在释放尾节点

	//将计算的下一个节点成为新的蛇头
	pn->next = ps->_pSnakeHead;
	ps->_pSnakeHead = pn;

	//打印的蛇头,覆盖食物的图案
	pSnakeNode cur = ps->_pSnakeHead;
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", BODY);

	//找到新蛇倒数第二个节点,我们后面要让他成为新的尾节点,和释放旧的尾节点
	while (cur->next->next != NULL)
	{
		cur = cur->next;
	}
	
	//清除屏幕上残留的旧尾节点图案
	SetPos(cur->next->x, cur->next->y);
	printf("  ");

	//释放尾节点
	free(cur->next);
	cur->next = NULL;

	//将新为节点的next指向NULL
	cur->next = NULL;
}

//检测蛇是否撞到墙
void KillByWall(pSnake ps) {
	if (ps->_pSnakeHead->x == 0 || ps->_pSnakeHead->x == 56 ||
		ps->_pSnakeHead->y == 0 || ps->_pSnakeHead->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

//检测蛇是否撞到自己
void KillBySelf(pSnake ps) {

	pSnakeNode cur = ps->_pSnakeHead->next;
	while (cur)
	{
		//若蛇头与某个蛇身体节点重叠就撞到自己了
		if (cur->x == ps->_pSnakeHead->x && cur->y == ps->_pSnakeHead->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

//蛇走一步的过程
void SnakeMove(pSnake ps) {

	//计算蛇头的下一刻的位置
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc");
	}

	switch (ps->_dir)
	{
	case UP:
		pNextNode->x = ps->_pSnakeHead->x;
		pNextNode->y = ps->_pSnakeHead->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnakeHead->x;
		pNextNode->y = ps->_pSnakeHead->y + 1;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnakeHead->x + 2;
		pNextNode->y = ps->_pSnakeHead->y;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnakeHead->x - 2;
		pNextNode->y = ps->_pSnakeHead->y;
		break;
	}

	//判断下一个位置是不是食物

	if (NextIsFood(pNextNode,ps))//是食物此函数返回1,否者返回0
	{
		//下一个位置是食物,就吃掉
		EatFood(pNextNode, ps);
	}
	else
	{
		//下一个位置不是食物,蛇整体走一步
		NoFood(pNextNode, ps);
	}

	//检测蛇是否撞到墙
	KillByWall(ps);

	//检测蛇是否撞到自己
	KillBySelf(ps);
}
//2.游戏运行
void GameRun(pSnake ps) {

	//1.右侧打印帮助信息
	PrintfHelpInfo();

	//2.贪吃蛇在地图里行走,当蛇的状态!= OK时,结束行走
	do {

		//食物分数与总分数随着游戏的进行会被改变,所以要一直打印,直至游戏结束
		SetPos(64, 7);
		printf("总得分:%d ", ps->_score);
		SetPos(64, 8);
		printf("每个食物得分:%2d分", ps->_food_weight);

		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();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}

		//蛇走一步的过程
		SnakeMove(ps);
		Sleep(ps->_sleep_time);

	}while(ps->_status == OK);//当状态不是OK时结束行走

}

//3.游戏结束
void GameOvre(pSnake ps) {
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("您主动结束了游戏");
		break;
	case KILL_BY_WALL:
		printf("您撞上了墙,结束了游戏");
		break;
	case KILL_BY_SELF:
		printf("您撞上了自己,结束了游戏");
		break;
	}

	//释放链表
	pSnakeNode cur = ps->_pSnakeHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

Snaketest.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"


int main() {

	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	int ch = 0;

	do
	{
		system("cls");
		//游戏开始
		Snake snake = { 0 };
		GameStart(&snake);

		//游戏中
		GameRun(&snake);

		//游戏结束
		GameOvre(&snake);

		SetPos(20, 15);
		printf("在来一局吗?(Y/N)");
		ch = getchar();
		//清理回车
		while (getchar() != '\n');

	} while (ch == 'y' || ch == 'Y');

	SetPos(0, 27);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/604163.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【第13章】spring-mvc之validator

文章目录 前言一、准备1. 引入库2. add.jsp3. show.jsp 二、代码部分1.实体类2. 控制器类3. 效果4. 展示 总结 前言 【第20章】spring-validator 虽然前面已经在spring介绍过&#xff0c;但是为了保证代码可用&#xff0c;还是会从头讲到尾&#xff0c;尽量把关键点列出来讲给…

微服务架构中的挑战及应对方式:Outbox 模式

使用 Outbox 模式保持微服务数据一致性 在一个由许多小型服务组成的系统中保持数据一致性是困难的&#xff0c;因为它们分散在各处。以下是一些常见问题以及如何处理它们的方法&#xff1a;当服务发送消息时&#xff0c;同时更新数据库和发送消息是棘手的问题。 在微服务中发出…

【Qt 开发基础体系】Qt信号与槽机制

文章目录 1.Qt 信号与槽机制原理&#xff08;Signal & Slot&#xff09;2. QObject 类 connect 的介绍3. 信号与槽机制连接方式4. 信号和槽机制优势及其效率&#xff1a;3. 信号与槽机制应用 1.Qt 信号与槽机制原理&#xff08;Signal & Slot&#xff09; &#x1f42…

通过AOP实现项目中业务服务降级功能

最近项目中需要增强系统的可靠性&#xff0c;比如某远程服务宕机或者网络抖动引起服务不可用&#xff0c;需要从本地或者其它地方获取业务数据&#xff0c;保证业务的连续稳定性等等。这里简单记录下业务实现&#xff0c;主要我们项目中调用远程接口失败时&#xff0c;需要从本…

《武林秘籍》——闪侠惠递如何让消费者寄快递更安心!

现如今&#xff0c;网上下单寄快递的便利性让众多人享受到了电商物流飞速发展带来的红利性。今天小编直接介绍一款寄快递特别省钱的利器&#xff0c;就是利用闪侠惠递来寄快递。闪侠惠递寄快递&#xff0c;真正的实现了便宜寄快递发物流的便捷性&#xff0c;开创了低价发快递的…

【汇总】虚拟机网络不通(Xshell无法连接虚拟机)排查方法

搜索关键字关键字关键字&#xff1a;虚拟机虚拟机虚拟机连接失败、虚拟机无法连接、Xshell连接失败、ping baidu.com失败、静态IP设置 Kali、CentOS、远程连接 描述&#xff1a;物理机无法连接虚拟机&#xff1b;虚拟机无法访问百度&#xff0c;虚拟机无法访问baidu.com 虚拟机…

Logstash分析MySQL慢查询日志实践

删除匹配到的行&#xff0c;当前行信息不记录到message中

可视化面板布局适配屏幕-基于 flexible.js + rem 智能大屏适配

可视化面板布局适配屏幕-基于 flexible.js rem 智能大屏适配 VScode 安装cssrem插件引入flexible.js在之后的开发都使用rem为单位&#xff0c;安装cssrem插件就是为了快捷将px转为rem我们的设计稿是1920px&#xff0c;设置最小宽度为1024px&#xff0c;最后&#xff0c;我们可…

JavaScript异步编程——05-回调函数

我们在前面的文章《JavaScript 基础&#xff1a;异步编程/单线程和异步》中讲过&#xff0c;Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时&#xff0c;⼤部分情况都是通过回调函数来进⾏。 &#xff08;如果你还不了解单线程和异步的概念&#xff0c;可以先去回顾上一…

SlowFast报错:ValueError: too many values to unpack (expected 4)

SlowFast报错&#xff1a;ValueError: too many values to unpack (expected 4) 报错细节 File "/home/user/yuanjinmin/SlowFast/tools/visualization.py", line 81, in run_visualizationfor inputs, labels, _, meta in tqdm.tqdm(vis_loader): ValueError: too …

流星烛台如何交易?Anzo Capital昂首资本3步盈利收场

各位投资者通过之前的文章可以准确的辨认出什么是流星烛台了&#xff0c;但是各位投资者一旦遇到流星图案知道怎么交易吗?其实一点都不困难&#xff0c;只要掌握住流星图案的交易真棒&#xff0c;Anzo Capital昂首资本3步就可以盈利收场。 首先&#xff0c;投资者需要确定图…

文件下载出现 IOExpcetion: closed

异常原因截图 : 异常代码位置 : 出现的原因是 使用 try-with-resources, downloadFile 方法执行完毕, 文件流被关闭了...导致前面读取文件字节, 异常提示已经关闭... try (Response response OkHttpUtils.getInstance().client.newCall(new Request.Builder().url(fileUrl)…

Yolov8实现loopy视频识别

1、前言 loopy是一个非常可爱的动漫角色&#xff08;可爱粉色淀粉肠&#xff09;&#xff0c;闲来无事&#xff0c;打算用yolov8训练一个模型对loopy进行识别。 2、准备工作 先在网络上搜寻很多loopy的图片&#xff0c;然后将图片导入Lablel Studio软件进行标注&#xff0c;并…

第1章.STM32单片机入门知识介绍

目录 0. 《STM32单片机自学教程》专栏 1.1 嵌入式系统简介 1.1.1 什么是嵌入式系统 1.1.2 嵌入式系统的特点 1.1.3 嵌入式系统的应用领域 1.2 单片机基本概念 1.3 ARM简介 1.3.1 ARM公司简介 1.3.2 ARM处理器简介 1.4 STM32简介 1.4.1 基于Cortex内核的MCU 1.4.…

springMVC入门学习

目录 1、 什么是springmvc 2、springmvc工作流程 3、 springmvc快速入门&#xff08;XML版本&#xff09; 4、加载自定义目录下的springmvc.xml配置文件 5、 解析器InternalResourceViewResolver 6、 映射器BeanNameUrlHandlerMapping 7、 适配器SimpleControllerHandle…

【算法-程序的灵魂#谭浩强配套】(适合专升本、考研)

无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 完整资料如下&#xff1a; 1.一个程序主要包括以下两方面信息&#xff1a;程…

React:Router-1.BrowserRouter组件式

使用步骤 安装 react-router-dom 依赖 $ npm install react-router-dom6导入 BrowserRouter, Link, Routes, Route 对象 import {BrowserRouter, Link, Routes, Route} from react-router-dom;3.BrowserRouter&#xff1a;history模式路由&#xff1b; HashRouter&#xff1…

Flask gevent启动报错UnicodeDecodeError

文章目录 环境代码报错Track解决思路 环境 acondana 24.1.2python 3.7.13 32bitflask 2.2.3gevent 21.8.0 代码 port 7236 logging.basicConfig(levellogging.INFO, # 控制台打印的日志级别filename./logs/app.log, # 将日志写入log_new.log文件中filemodea, # 模式&…

python笔记:dataclass

1 引子&#xff1a;其他类似实现方法的局限性 假设我们现在需要实现这样的内容&#xff1a; nameChinaarea960population140967 1.1 tuple/list country1_tuple(China,960,140967) country1_tuple[0] #China 缺点&#xff1a;需要记住各个属性是list/tuple第几位的属性&am…

DEV--C++小游戏(吃星星(0.2))

目录 吃星星&#xff08;0.2&#xff09; 简介 分部代码 头文件&#xff08;增&#xff09; 命名空间变量&#xff08;增&#xff09; 副函数&#xff08;新&#xff0c;增&#xff09; 清屏函数 打印地图函数&#xff08;增&#xff09; 移动函数 选择颜色&#xff…