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

news2025/1/12 1:48:24

目录

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.coloradmin.cn/o/1654509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cargo - 构建 rust项目、管理依赖包

文章目录 关于 Cargo构建项目创建工程编译运行buildclean 管理依赖添加依赖updatecheck计时 manual rust 安装可参考&#xff1a;https://blog.csdn.net/lovechris00/article/details/124808034 关于 Cargo Cargo 官方文档 &#xff1a; https://doc.rust-lang.org/cargo/crat…

泛型编程四:容器

文章目录 前言一、序列容器verctor 总结 前言 STL有六大部件&#xff0c;容器、算法、仿函数、迭代器、适配器和分配器。除了算法是函数模板&#xff0c;其他都是类模板。容器可以分为序列容器和关联容器。常见的序列容器有vector、array、deque、list、forward-list&#xff…

;【排列【

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

【Kolmogorov-Arnold网络 替代多层感知机MLPs】KAN: Kolmogorov-Arnold Networks

KAN: Kolmogorov-Arnold Networks 论文地址 代码地址 知乎上的讨论&#xff08;看一下评论区更正&#xff09; Abstract Inspired by the Kolmogorov-Arnold representation theorem, we propose Kolmogorov-Arnold Networks (KANs) as promising alternatives to Multi-Layer…

1.使用uniapp搭建微信小程序项目并引入前端组件资源

文章目录 1. 项目配置1.1. 新建vue3项目1.2. 关联云空间1.3. 运行到微信开发者工具 2. 前端组件2.1. uniCloud的内置组件和扩展组件2.2. uView3.02.3. 在uniapp项目引入uview3 1. 项目配置 1.1. 新建vue3项目 由于我们要使用vue3而不是vue2&#xff0c;所以要选好版本&#x…

移动端底层事件(如左滑返回事件)在同一个路由下不同页面需要不同的处理要怎样才能做到统一处理?

目录 一、问题 二、解决方法 三、总结 tiips:如嫌繁琐&#xff0c;直接移步总结即可&#xff01; 一、问题 1.测试提了个bug:进入了一个模块A里面的子页面 a1,左滑后按照用户预期应该是返回到模块A,结果回到了app首页。 二、解决方法 1.一开始&#xff1a;啊&#xff0c;…

分布式与一致性协议之ZAB协议(七)

ZAB协议 ZAB协议:如何处理读写请求 你应该有这样的体会&#xff0c;如果你想了解一个网络服务&#xff0c;执行的第一个功能肯定是写操作&#xff0c;然后才会执行读操作。比如&#xff0c;你要了解ZooKeeper&#xff0c;那么肯定会在zkClient.sh命令行中执行写操作(比如crea…

Dynamic Extraction of Subdialogues for Dialogue Emotion Recognition

对话情感识别的子对话动态提取 摘要1. 介绍2 相关工作2.1 对话上下文建模2.2 常识知识 3 方法3.1 问题定义3.2 模型概述3.3 特征提取模块3.4 依赖性建模3.5 交互式子对话提取模块3.6 重要性增强的多头自注意力模块3.7 子对话框主题提取模块3.8. 分类模块 四、实验4.1 数据集4.1…

JAVA基础之jsp标准标签

jsp动作标签实现实例化一个实体类 <jsp:useBean id"标识符" class"java类名" scope"作用范围"> 传统的java方式实例化一个实体类 Users user new Users(); <%%> id: 对象名 * class:类 创建对象时,完全限定名(包名…

vue3使用el-autocomplete请求远程数据

服务器端 RestController RequestMapping("/teacher") public class TeacherController {Resourceprivate TeacherService teacherService;GetMapping({"/v1/getTop10TeacherByName/","/v1/getTop10TeacherByName/{name}"})public ResultBean&l…

巡检机器人有哪些功能和作用?

在科技如此发达的时代&#xff0c;巡检机器人犹如一位不知疲倦的守护者&#xff0c;悄然走进了我们的生活。它们具备着令人惊叹的功能和作用&#xff0c;成为了保障安全、提高效率的重要力量。那么&#xff0c;巡检机器人功能和作用&#xff1f;下面我们来说说旗晟机器人的几款…

爬虫:爬取豆瓣电影

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 上篇我们将到如何利用xpath的规则&#xff0c;那么这一次&#xff0c;我们将通过案例来告诉读者如何使用Xpath来定位到我们需要的数据&#xff0c;就算你不懂H5代码是怎么个嵌套或者十分复…

Leetcode—387. 字符串中的第一个唯一字符【简单】

2024每日刷题&#xff08;127&#xff09; Leetcode—387. 字符串中的第一个唯一字符 实现代码 class Solution { public:int firstUniqChar(string s) {int count[26] {0};for(char c: s) {count[c - a];}for(int i 0; i < s.length(); i) {if(count[s[i] - a] 1) {re…

LSTM神经网络 vs Transformer在量化中的应用

LSTM,全称Long Short-Term Memory,是一种特殊的递归神经网络。它通过巧妙的"门"结构,可以有效地捕捉时间序列数据中的长期依赖关系。这一特点,使得LSTM在处理股价这种具有时间序列特性的数据时,展现出了非凡的潜力。 这种特殊的递归神经网络 与一般的前馈神经网络不…

BACnet转MQTT网关智联楼宇json格式自定义

智能建筑的BACnet协议作为楼宇自动化领域的通用语言&#xff0c;正逐步迈向更广阔的物联网世界。随着云计算和大数据技术的飞速发展&#xff0c;如何将BACnet设备无缝融入云端生态系统&#xff0c;成为众多楼宇管理者关注的焦点。本文将以一个实际案例&#xff0c;揭示BACnet网…

Baidu Comate智能编码助手:提升软件生产力的高效工具使用教程

目录 一、前言 二、Comate助手概览 三、核心功能详解 智能推荐与自动补全 生成单元测试 代码注释生成 四、使用场景与优势 五、总结与展望 一、前言 随着信息技术的飞速发展&#xff0c;编程已经成为许多行业不可或缺的一部分。然而&#xff0c;编程过程中的繁琐和重复…

路由策略与路由控制

1.路由控制工具 匹配工具1&#xff1a;访问控制列表 &#xff08;1&#xff09;通配符 当进行IP地址匹配的时候&#xff0c;后面会跟着32位掩码位&#xff0c;这32位称为通配符。 通配符&#xff0c;也是点分十进制格式&#xff0c;换算成二进制后&#xff0c;“0”表示“匹配…

谷歌月球模型

收费产品&#xff0c;白嫖党勿扰 收费金额500元 1 概述 前些时间&#xff0c;有个客户&#xff0c;想fight TAIWAN&#xff0c;于是乎&#xff0c;我把谷歌地球整个台湾的模型都下载下来了&#xff0c;大约300GB。今天&#xff0c;又有个客户&#xff0c;提出一个过分要求&…

Linux网络编程:TCP编程实现

目录 1、前言 2、函数介绍 2.1 socket函数 与 通信域 2.2 bind函数 与 通信结构体 2.2.1 domain通信地址族 与 通信结构体 2.2.2 IPv4地址族结构体 2.2.3 通用地址族结构体 2.2.4 示例&#xff1a;为套接字fd绑定通信结构体addr 2.3 listen函数 与 accept函数 …

KMP + Compose 跨平台 Android IOS 实战入门

KMP&#xff08;Kotlin Multiplatform&#xff09;是一种面向移动端开发的跨平台框架&#xff0c;使用 Kotlin 语言编写&#xff0c;可实现在 Android 和 iOS 平台上共享代码和逻辑。通过 KMP 框架&#xff0c;我们可以编写一次代码&#xff0c;然后在不同的平台上进行部署和运…