贪吃蛇代码实现与剖析(C语言)

news2024/11/30 6:52:42

贪吃蛇代码实现与剖析[C语言]

  • 1.温馨提示
  • 2.最终实现版本的样子
    • 1.游戏开始-欢迎界面
    • 2.游戏运行界面
    • 3.游戏结束界面
    • 4.选择是否继续玩
      • 1.选择继续
    • 2.选择退出游戏
  • 3.完整代码
  • 一.Win32相关API的介绍
    • 1.控制台程序
      • 1.什么是控制台程序
      • 2.命令提示符中设置控制台窗口的大小
      • 3.控制台行和列的注意事项
      • 4.VS2019中设置控制台窗口的大小
      • 5.设置控制台名称
    • 2.控制台屏幕坐标
    • 3.GetStdHandle
    • 4.设置控制台光标状态
      • 1.GetConsoleCursorInfo
      • 2.CONSOLE_CURSOR_INFO
      • 3.SetConsoleCursorInfo
      • 4.SetConsoleCursorPosition
    • 5.SetPos函数的实现
    • 6.GetAsyncKeyState
    • 7.打印宽字符的实现
  • 二.贪吃蛇的游戏流程分析
    • 1.游戏窗口的实现
      • 1.界面的初始化
      • 2.欢迎界面的打印
      • 3.窗口布局(地图坐标)
    • 2.蛇身结构体的创建与初始化
      • 1.蛇身节点的结构体
      • 2.食物节点的结构体
      • 3.蛇身结构体的创建
      • 4.蛇身的初始化
      • 5.食物的初始化
    • 3.GameStart部分的完整代码
      • 1.重点说明一下main函数
      • 2.完整代码
      • 3.最终实现情况:
    • 4.游戏运行
      • 1.GameRun函数的整体框架
      • 2.帮助信息的打印
      • 3.获取按键情况
      • 4.蛇身的移动
        • 1.整体框架
        • 2.EatFood和NoFood函数
        • 3.IfKillByWall和IfKillBySelf函数
    • 5.GameRun部分的完整代码
      • 1.完整代码
      • 2.最终实现情况
    • 6.游戏结束后的处理
      • 1.代码实现
      • 2.Y/N 是否再来一局
  • 三.总结

首先说明:
1.
这个贪吃蛇代码只有在Windows中执行才会起效果
我用的是Windows系统中的VS2019编译器
2.
我们先给出贪吃蛇的完整代码,是为了让大家提起接下来往后仔细看完这篇博客的热情
3.
这个贪吃蛇代码的前置知识:
1.C语言:函数,结构体,枚举,指针,动态内存管理(free,malloc…),宏
2.数据结构:链表

1.温馨提示

想要执行这个代码,在VS2019中需要调整一下控制台的属性
我们先在VS2019中随意跑一段简单的hello world调出控制台来进行属性的调整

默认情况下:我们的控制台是这个样子的
在这里插入图片描述
我们需要修改一下这个控制台的属性
在这里插入图片描述
在这里插入图片描述
然后就会出现这个
在这里插入图片描述
只有这样,我们才可以更好的实现这个窗口
否则,同样的代码在这个控制台窗口下就会出现这种样子
在这里插入图片描述
而我们修改了之后的样子是这样的
在这里插入图片描述
所以我们才要去修改这个控制台窗口的属性

2.最终实现版本的样子

1.游戏开始-欢迎界面

在这里插入图片描述
在这里插入图片描述

2.游戏运行界面

在这里插入图片描述

3.游戏结束界面

在这里插入图片描述

4.选择是否继续玩

1.选择继续

在这里插入图片描述
输入Y/y并按下回车即可继续玩
在这里插入图片描述
然后回到游戏最开始
在这里插入图片描述

2.选择退出游戏

在这里插入图片描述
在这里插入图片描述

3.完整代码

大家可以先在自己的VS中执行一下玩一玩
1.Snake.h

#pragma once
#include <stdio.h>
#include <Windows.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

#define INIT_X 24
#define INIT_Y 6

typedef struct SnakeNode
{
	struct SnakeNode* next;
	int x;
	int y;
}SNode;

enum Direction
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

enum GameState
{
	OK,
	EXIT_NORMAL,
	KILL_BY_WALL,
	KILL_BY_SELF
};

typedef struct Snake
{
	SNode* _pSnake;//蛇头节点
	SNode* _pFood;//食物
	enum Direction _dir;//蛇移动的方向
	enum GameState _state;//当前游戏状态
	int _score;//当前得分
	int _foodWeight;//每个食物的分数
	int _sleepTime;//蛇的休息时间,影响加速和减速和暂停

}Snake;


void SetPos(short x, short y);
void GameStart(Snake* ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(Snake* ps);
void CreateFood(Snake* ps);

void GameRun(Snake* ps);
void PrintHelpInfo();
void SnakeMove(Snake* ps);
//判断是否撞墙
void IfKillByWall(Snake* ps,int x, int y);
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y);

void EatFood(SNode* pNextNode, Snake* ps);
void NoFood(SNode* pNextNode, Snake* ps);
//暂停函数
void pause();

void GameEnd(Snake* ps);

2.Snake.c

#include "Snake.h"
//设置控制台光标位置的函数
void SetPos(short x, short y)
{
	COORD pos = { x,y };
	HANDLE handle = NULL;
	//获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中
	//调用SetConsoleCursorPosition函数将光标位置设置到指定的位置
	SetConsoleCursorPosition(handle, pos);
}

//system("mode con cols=120 lines=35");

void WelcomeToGame()
{
	SetPos(45, 12);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(45, 18);
	system("pause");
	system("cls");
	SetPos(45, 12);
	printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速");
	SetPos(45, 13);
	printf("加速能够得到更高的分数");
	SetPos(45, 18);
	system("pause");
	system("cls");
}

void CreateMap()
{
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25

		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;
		}

		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;
			}
			cur = cur->next;
		}
		//没有跟蛇身重合
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

void GameStart(Snake* ps)
{
	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

void PrintHelpInfo()
{
	SetPos(65, 17);
	printf("不能穿墙,不能咬到自己");
	SetPos(65, 18);
	printf("用↑.↓.←.→ 分别控制蛇的移动");
	SetPos(65, 19);
	printf("F1为加速,F2为减速");
	SetPos(65, 20);
	printf("Esc: 退出游戏  space:暂停游戏");

	SetPos(65, 22);
	printf("编写者:wzs");
}

void EatFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x,cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//释放食物节点
	free(ps->_pFood);
	ps->_pFood = NULL;
	//加分
	ps->_score += ps->_foodWeight;
	//创建新食物
	CreateFood(ps);
}

void NoFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//释放最后一个节点
	SNode* cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

//判断是否撞墙
void IfKillByWall(Snake* ps,int x,int y)
{
	if (x == 0 || x == 56 || y == 0 || y == 26)
	{
		ps->_state = KILL_BY_WALL;
	}
}
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y)
{
	SNode* cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}
//暂停函数
void pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, 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_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}

void GameEnd(Snake* ps)
{
	SetPos(65, 24);
	switch (ps->_state)
	{
	case EXIT_NORMAL:
		printf("玩家选择退出,游戏结束");
		break;
	case KILL_BY_WALL:
		printf("很遗憾,撞墙了,游戏结束");
		break;
	case KILL_BY_SELF:
		printf("很遗憾,咬到自己了,游戏结束");
		break;
	default:
		break;
	}
	//释放蛇身的节点
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

3.test.c文件

#include "Snake.h"

//初始化光标信息等
void Init()
{
	HANDLE handle = NULL;
	//获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;//CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息
	GetConsoleCursorInfo(handle, &CursorInfo);//检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(handle, &CursorInfo);//设置指定控制台屏幕缓冲区的光标的大小和可见性
}

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	char input = 0;
	do
	{
		Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
		srand((unsigned int)time(NULL));
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(65, 26);
		printf("要在玩一局吗?(Y/N)");
		input = getchar();
		getchar();//清理'\n'
		system("cls");//清屏
		SetPos(45,12);
		if (input == 'n' || input == 'N')
		{
			printf("欢迎再次在玩");
		}
		else if (input == 'Y' || input == 'y')
		{
			printf("游戏即将开始,祝您玩的开心");
			SetPos(45, 14);
			system("pause");
			system("cls");//清屏
		}
	} while (input == 'y' || input == 'Y');
	SetPos(32, 0);
	return 0;
}

一.Win32相关API的介绍

1.首先我们先介绍一下:什么是API?
在这里插入图片描述
也就是说我们Window系统给我们提供了很多函数,让我们可以通过调用这些函数去完成一些我们目前想要完成的任务
而这些函数服务的对象是应用程序

因此这些函数被称为API

1.控制台程序

1.什么是控制台程序

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

就是这个Microsoft Visual Studio 调试控制台
在这里插入图片描述
在我们的Windows系统中,就有一个叫做命令提示符的工具
这个也是控制台程序
我们可以在Window系统中搜索cmd
在这里插入图片描述
然后打开,这个命令提示符就是控制台
在这里插入图片描述
我们要介绍的是:

2.命令提示符中设置控制台窗口的大小

我们可以设置控制台窗口的长宽:
比方说我现在想要让这个命令提示符的行数和列数设置为:
10行,50列

mode con cols=50 lines=10

在这里插入图片描述
我们输入这个命令,按下回车(就像是在Linux系统中输入命令行相同)
在这里插入图片描述
然后发现这个命令提示符变得特别小了
因此这就证明了我们是可以手动设置控制台大小的

3.控制台行和列的注意事项

然后我想让他变成一个正方形呢?
我们输入:让它行和列都变成30吧

mode con cols=30 lines=30

在这里插入图片描述
在这里插入图片描述
为什么不是一个正方形呢?
明明我输的是行30,列30啊
为什么会这样呢?
因为控制台中行的长度的基本单位和列的长度的基本单位不同

其实:
我们可以简单理解为:

控制台中每一行的长度==每一列的长度*2

那么我们想要构建一个正方形就可这样做了:

mode con cols=60 lines=30

在这里插入图片描述
这就是一个正方形了
在这里插入图片描述

4.VS2019中设置控制台窗口的大小

比方说我们想要一个15行,30列的一个正方形控制台窗口

mode con cols=30 lines=15

只需要包含Windows.h头文件
并且使用system函数

system("mode con cols=30 lines=15");

在这里插入图片描述

5.设置控制台名称

我们这个个界面还有一个贪吃蛇的名称
这个怎么设置呢?
跟刚才一样
只需要在VS代码里面加上

system("title 贪吃蛇");

在这里插入图片描述
不过我这里一开始的时候是无法修改这个控制台的名称
等到我写完贪吃蛇代码之后
控制台的名称就自然而然好了

所以大家如果在这一步无法修改名称的话,请先继续往后看

2.控制台屏幕坐标

COORD是Windows API中定义的一个结构体
它表示一个字符在控制台屏幕上的坐标
这个坐标系是这样的
在这里插入图片描述

typedef struct _COORD
{
  SHORT X;//X轴上的坐标
  SHORT Y;//Y轴上的坐标
}COORD,*PCOORD;

如果我们想要给这个控制台坐标赋值的话:
比方说我们给它的坐标赋值为:x轴:20,y轴:10
COORD pos = {20,10};
那么pos就是这个控制台上的对应位置的点

我们现在已经清楚了这个控制的坐标系的规则
但是还有一个问题:
在这里插入图片描述
这个控制台上的光标去哪了?
其实这个光标被我们隐藏了
那么我们该怎么样去隐藏这个光标呢?
别急,我们先来介绍一个函数:GetStdHandle

3.GetStdHandle

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

这是微软官方给的API的使用手册,大家可以看一下
Windows API索引
这个GetStdHandle函数的手册网址:GetStdHandle函数的手册网址
在这里插入图片描述

4.设置控制台光标状态

1.GetConsoleCursorInfo

在这里插入图片描述
在这里插入图片描述

2.CONSOLE_CURSOR_INFO

在这里插入图片描述

3.SetConsoleCursorInfo

在这里插入图片描述
也就是说我们想要隐藏光标,需要这样:

#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
int main()
{
	system("mode con cols=60 lines=20");
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//让handle具有能够操作控制台标准输出设备的能力/权限
	CONSOLE_CURSOR_INFO CursorInfo;//这个结构体就是定义光标信息的结构体
	GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标的操作
	SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
	return 0;
}

不要忘了在C语言中使用bool类型的话需要包含stdbool.h头文件
在这里插入图片描述
这样我们就成功隐藏光标了
在这里插入图片描述
可是你这个控制台还能够在任意位置打印数据啊,
这肯定是通过设置光标位置做到的,那么如何才能设置光标位置呢?

4.SetConsoleCursorPosition

在这里插入图片描述
实例:

 COORD pos = {30, 10};
 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
 //设置标准输出上光标的位置为pos
 SetConsoleCursorPosition(handle, pos);

在这里插入图片描述
成功在指定位置打印了hello world

5.SetPos函数的实现

那么既然我们需要很多次调整光标位置以便能够在任意位置写入数据
那么我们不妨设计一个函数SetPos来实现调整光标位置的操作呢?
于是我们就可以写出这样的函数

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

6.GetAsyncKeyState

下面的问题来了:
我们想要玩这个游戏,就一定要能够接收我们的输入
我们既然是在电脑上玩,那就需要使用键盘去玩
那么就一定需要编译器能够在游戏运行的时候获取按键情况

因此微软WIN32API中就给了这么一个函数GetAsyncKeyState
作用是:获取按键情况

SHORT GetAsyncKeyState(
 int vKey
);

在这里插入图片描述
因此我们就可以让这个返回值跟1进行按位与

如果得出来的值是1:那么就代表这个值的最低位是1,也就是说这个键被按过

如果得出来的值是0:那么就代表这个值的最低位是0,也就是说这个键没有被按过
因此我们可以写出如下的宏

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

7.打印宽字符的实现

我们现在能够在屏幕上的任意位置打印数据,还能够隐藏光标,还能检测哪些键是否被按过
那不就可以了吗?
我们还漏了一点:
在这里插入图片描述
这个黑色原点:也就是蛇身
这个黑色五角星,也就是食物
这个白色方块:也就是墙体
这个是怎么打印出来的呢?
键盘上也没有啊

我们可以通过
搜狗输入法->输入方式->符号大全->里面就有
在这里插入图片描述
然后那不就行了吗?
是不行的
因为这三个字符属于宽字符(一个宽字符占2个字节,一个普通字符占1个字节),在VS的默认情况下我们是无法单独打印这些字符的
在这里插入图片描述
那么如何才能打印呢?
在这里插入图片描述
在这里插入图片描述
我们的准备工作终于做完了
下面就可以开始我们贪吃蛇游戏的具体实现了

二.贪吃蛇的游戏流程分析

这是我们贪吃蛇的整个游戏流程的分析
在这里插入图片描述

1.游戏窗口的实现

1.界面的初始化

根据我们刚才API部分的学习,我们已经写出了Init函数
可以用来隐藏屏幕光标
在这里插入图片描述
然后我们可以把打印宽字符,设置窗口大小,窗口名称的代码在main函数中去写
我们在这里将控制台的大小设置为宽:35行,列:120列
所以我们就可以在main函数当中这样去写

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	return 0;
}

这样就完成了
在这里插入图片描述

2.欢迎界面的打印

我们在前面已经实现了SetPos函数
在这里插入图片描述
然后我们就可以通过Setpos去定位光标,然后打印欢迎信息
在这里插入图片描述

3.窗口布局(地图坐标)

这里要实现的是CreateMap函数
我们在这里实现的是一个27行,58列的棋盘
所以我们就需要去通过SetPos定位光标,然后打印这个墙
在这里插入图片描述
其次,为了便于打印,我们宏定义了墙,蛇身,食物的字符

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

在这里插入图片描述
注意:这里最后打印下面的墙是因为测试时当我们打印完成之后
程序运行结束就会打印:

C:\Users\23119\Desktop\C++_learn_code\cpp_learn_code\Snake_review\Debug\Snake_review.exe (进程 6000)已退出,代码为 0。
按任意键关闭此窗口. . .

如果我们最后打印的不是下面的墙
那样就会出现这种情况:
因为打印完下面的墙之后又打印了左边和右边的墙
导致程序结束时下面的墙被这句话覆盖了
在这里插入图片描述
其实我们打印的过程是这样的
在这里插入图片描述
我们调试看一下过程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.蛇身结构体的创建与初始化

1.蛇身节点的结构体

在这里插入图片描述

2.食物节点的结构体

在这里插入图片描述

3.蛇身结构体的创建

在这里插入图片描述
因此我们就可以定义出下面的结构体
在这里插入图片描述

4.蛇身的初始化

定义好蛇身节点,食物节点和蛇的结构体之后
下面我们要初始化这条蛇
怎么初始化呢?
在这里插入图片描述
在这里插入图片描述
因此我们就可以写出这样的代码

这两个宏定义是Snake.h文件中的
#define INIT_X 24
#define INIT_Y 6
void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后面两块是打印蛇身和初始化其他属性的注意事项
在这里插入图片描述

5.食物的初始化

在这里插入图片描述
注意:rand()%53生成的随机数的范围是:0~52

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25
		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;//这里是continue while(1){...}这个循环,这次循环不再执行下面的语句,直接跳转到下一次while(1){...}
		}
		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合,重新通过rand函数设置x和y
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;//这里是break出while(cur){...}这个循环
			}
			cur = cur->next;
		}
		//没有跟蛇身重合,就可以创建食物节点了
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

3.GameStart部分的完整代码

1.重点说明一下main函数

在这里插入图片描述

2.完整代码

1.Snake.h

#pragma once
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <stdlib.h>
#include <time.h>
#define INIT_X 24
#define INIT_Y 6
typedef struct SnakeNode
{
	struct SnakeNode* next;
	int x;
	int y;
}SNode;

enum Direction
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

enum GameState
{
	OK,
	EXIT_NORMAL,
	KILL_BY_WALL,
	KILL_BY_SELF
};

typedef struct Snake
{
	SNode* _pSnake;//蛇头节点
	SNode* _pFood;//食物
	enum Direction _dir;//蛇移动的方向
	enum GameState _state;//当前游戏状态
	int _score;//当前得分
	int _foodWeight;//每个食物的分数
	int _sleepTime;//蛇的休息时间,影响加速和减速和暂停
}Snake;

void Init();
void SetPos(short x, short y);

void GameStart(Snake* ps);

void WelcomeToGame();
void CreateMap();

void InitSnake(Snake* ps);
void CreateFood(Snake* ps);

2.Snake.c

#include "Snake.h"
void Init()
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//让handle具有能够操作控制台标准输出设备的能力/权限
	CONSOLE_CURSOR_INFO CursorInfo;//这个结构体就是定义光标信息的结构体
	GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标的操作
	SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}

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

void WelcomeToGame()
{
	SetPos(45, 12);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(45, 18);
	system("pause");
	system("cls");
	SetPos(45, 12);
	printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速");
	SetPos(45, 13);
	printf("加速能够得到更高的分数");
	SetPos(45, 18);
	system("pause");
	system("cls");
}

void CreateMap()
{
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25

		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;
		}

		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;
			}
			cur = cur->next;
		}
		//没有跟蛇身重合
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

void GameStart(Snake* ps)
{
	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

3.test.c

#include "Snake.h"
//初始化光标信息等
int main()
{
    setlocale(LC_ALL, "");
    system("mode con cols=120 lines=35");
    system("title 贪吃蛇");
    Init();
    Snake snake = { 0 };//将snake结构体变量的内容全都初始化为0
    //(这里主要是为了初始化p_Snake头节点的指针,为了防止头插法创建蛇身链表时出现野指针的非法访问问题)
    //p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
    srand((unsigned int)time(NULL));//设置随机数种子,防止每一次运行rand生成的随机数都是一样的
    GameStart(&snake);
    SetPos(0, 30);//这里我们要定位一下光标,
    //防止最后打印的那条包含:"返回值为0"的语句因为光标最后处于打印食物位置的下一行
    //而导致覆盖我们的墙体
    return 0;
}

3.最终实现情况:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.游戏运行

我们的蛇身结构体和食物都已经初始化好了,游戏的开始工作结束
下面开始实现游戏运行的代码了

1.GameRun函数的整体框架

在这个GameRun函数中我们要实现的整体框架是:
在这里插入图片描述
因此我们可以写出这样的代码框架在这里插入图片描述

2.帮助信息的打印

经过了前面打印欢迎界面之后,这个帮助信息的打印对我们来说就轻而易举了
在这里插入图片描述

3.获取按键情况

我们之前在API中提到过这个获取按键情况的宏
在这里插入图片描述

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

如果我们按了这个键,这个宏对应于这个键的值就是1
如果我们没有按这个键,这个宏对应于这个键的值就是0

那么怎么使用这个宏呢?

只需要将键盘上每个键的虚拟键值传递给这个宏,
就可以通过这个宏的返回值来判断是否按下了这个键

这是微软官方提供的虚拟键代码手册,我已经查阅好了相关的按键
大家感兴趣的话,也可以去看一下这个手册
虚拟键代码手册
因此我们就可以写出这样的代码
在这里插入图片描述

//调整方向
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();
}
//Esc退出
else if (KEY_PRESS(VK_ESCAPE))
{
	ps->_state = EXIT_NORMAL;
	break;
}
//加速
else if (KEY_PRESS(VK_F1))
{
	//防止一直加速导致sleepTime<0出现bug
	if (ps->_sleepTime >= 50)
	{
		ps->_sleepTime -= 30;
		ps->_foodWeight += 2;//加速时食物的分值会增加
	}
}
//减速
else if (KEY_PRESS(VK_F2))
{
	//防止一直减速导致程序运行太慢出现卡顿影响用户体验
	if (ps->_sleepTime < 350)
	{
		ps->_sleepTime += 30;
		ps->_foodWeight -= 2;
		//防止太慢时食物得分减为负数
		if (ps->_sleepTime == 350)
		{
			ps->_foodWeight = 1;
		}
	}
}

Sleep是C语言的库函数,可以让程序休息对应的时间
单位是ms
这里的pause是暂停函数:
在这里插入图片描述
所以我们就可以完善一下我们的GameRun函数了

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, 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_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}
//暂停函数
void pause()
{
        while (1)
        {
            Sleep(200);
            if (KEY_PRESS(VK_SPACE))
            {
                break;
            }
        }
}

4.蛇身的移动

1.整体框架

在这里插入图片描述

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//2.创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//3.判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}

下面我们就要实现一下下面的这4个函数,那么SnakeMove函数就成功完成了

2.EatFood和NoFood函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以我们就可以写出这样的代码
在这里插入图片描述

3.IfKillByWall和IfKillBySelf函数

这两个函数的返回值类型可以是void
因为我们可以直接在这两个函数当中修改游戏当前状态
也就是ps->_state
因此我们可以这样写:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.GameRun部分的完整代码

这里只写了这一部分的完整代码
需要再加上GameStart部分的完整代码才可以正常运行

1.完整代码

Snake.h

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
void pause();
void GameRun(Snake* ps);
void PrintHelpInfo();

void EatFood(SNode* pNextNode, Snake* ps);
void NoFood(SNode* pNextNode, Snake* ps);
void IfKillByWall(Snake* ps, int x, int y);
void IfKillBySelf(Snake* ps, int x, int y);
void SnakeMove(Snake* ps);

Snake.c

//暂停函数
void pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, 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_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}

void PrintHelpInfo()
{
	SetPos(65, 17);
	printf("不能穿墙,不能咬到自己");
	SetPos(65, 18);
	printf("用↑.↓.←.→ 分别控制蛇的移动");
	SetPos(65, 19);
	printf("F1为加速,F2为减速");
	SetPos(65, 20);
	printf("Esc: 退出游戏  space:暂停游戏");

	SetPos(65, 22);
	printf("编写者:wzs");
}

void EatFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//释放食物节点
	free(ps->_pFood);
	ps->_pFood = NULL;
	//加分
	ps->_score += ps->_foodWeight;
	//创建新食物
	CreateFood(ps);
}

void NoFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//释放最后一个节点
	SNode* cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

//判断是否撞墙
void IfKillByWall(Snake* ps, int x, int y)
{
	if (x == 0 || x == 56 || y == 0 || y == 26)
	{
		ps->_state = KILL_BY_WALL;
	}
}
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y)
{
	SNode* cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//2.创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//3.判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}

test.c

int main()
{
    setlocale(LC_ALL, "");
    system("mode con cols=120 lines=35");
    system("title 贪吃蛇");
    Init();
    Snake snake = { 0 };//将snake结构体变量的内容全都初始化为0
    //(这里主要是为了初始化p_Snake头节点的指针,为了防止头插法创建蛇身链表时出现野指针的非法访问问题)
    //p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
    srand((unsigned int)time(NULL));//设置随机数种子,防止每一次运行rand生成的随机数都是一样的
    GameStart(&snake);
    GameRun(&snake);
    SetPos(0, 30);//这里我们要定位一下光标,
    //防止最后打印的那条包含:"返回值为0"的语句因为光标最后处于打印食物位置的下一行
    //而导致覆盖我们的墙体
    return 0;
}

2.最终实现情况

在这里插入图片描述
咬到自己:
在这里插入图片描述
撞墙:
在这里插入图片描述

6.游戏结束后的处理

在这里插入图片描述

1.代码实现

在这里插入图片描述

2.Y/N 是否再来一局

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	char input = 0;
	do
	{
		Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
		srand((unsigned int)time(NULL));
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(65, 26);
		printf("要在玩一局吗?(Y/N)");
		input = getchar();
		getchar();//清理'\n'
		system("cls");//清屏
		SetPos(45,12);
		if (input == 'n' || input == 'N')
		{
			printf("欢迎再次在玩");
		}
		else if (input == 'Y' || input == 'y')
		{
			printf("游戏即将开始,祝您玩的开心");
			SetPos(45, 14);
			system("pause");
			system("cls");//清屏
		}
	} while (input == 'y' || input == 'Y');
	SetPos(32, 0);
	return 0;
}

三.总结

上面就是我们贪吃蛇代码的整体分析和梳理
其实我们的整体思路就是这个图片所展现的
我们只需要先把大概的框架全部完成
具体的函数先声明出来
然后我们剩下的任务就只有去把那些函数一一实现即可
在这里插入图片描述

以上就是贪吃蛇代码实现与剖析(C语言)的全部内容,希望能对大家有所帮助!

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

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

相关文章

【Linux】Nignx及负载均衡动静分离

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《微信小程序开发实战》。&#x1f3af;&#x1f3a…

数据标准是什么?如何建立企业的数据标准?

2023年10月25日国家数据局正式揭牌&#xff0c;由国家发展和改革委员会管理。国家数据局的主要职责是负责协调推进数据基础制度建设&#xff0c;统筹数据资源整合共享和开发利用&#xff0c;统筹推进数字中国、数字经济、数字社会规划和建设等。国家越来越重视数据资源的价值&a…

Explaining and harnessing adversarial examples

Explaining and harnessing adversarial examples----《解释和利用对抗样本》 背景&#xff1a; 早期的研究工作认为神经网络容易受到对抗样本误导是由于其非线性特征和过拟合。 创新点&#xff1a; 该论文作者认为神经网络易受对抗性扰动影响的主要原因是它的线性本质&#xf…

第五章 I/O管理 十、磁盘调度算法(FCFS、SSTF、SCAN、C-SCAN、C-LOOK)

目录 一、概括 二、一次磁盘读/写操作需要的时间 1、寻找时间&#xff08;寻道时间)&#xff1a; 2、延迟时间&#xff1a; 3、传输时间&#xff08;读磁盘的时间&#xff09; 三、先来先服务算法&#xff08;FCFS&#xff09; 1、定义&#xff1a; 2、例子&#xff1…

Qwt QwtPolarPlot类使用

1.概述 QwtPolarPlot是Qwt库中用于绘制极坐标图的类。它继承自QwtPolarItemDict和QFrame类&#xff0c;并且可以作为QwtPlot控件的一部分使用。 以下是类的继承关系图&#xff1a; 2.常用方法 设置标签&#xff1a; void setTitle (const QString &)void setTitle (con…

降低毕业论文写作压力的终极指南

亲爱的同学们&#xff0c;时光荏苒&#xff0c;转眼间你们即将踏入毕业生的行列。毕业论文作为本科和研究生阶段的重要任务&#xff0c;不仅是对所学知识的综合运用&#xff0c;更是一次对自己学术能力和专业素养的全面考验。然而&#xff0c;论文写作常常伴随着压力和焦虑&…

YOLOv5:修改backbone为MobileOne

YOLOv5&#xff1a;修改backbone为MobileOne 前言前提条件相关介绍MobileOneYOLOv5修改backbone为MobileOne修改common.py修改yolo.py修改yolov5.yaml配置 参考 前言 记录在YOLOv5修改backbone操作&#xff0c;方便自己查阅。由于本人水平有限&#xff0c;难免出现错漏&#xf…

前端基础之JavaScript

JavaScript是一种能够在网页上添加交互效果的脚本语言&#xff0c;也被称为客户端语言。它可以在网页中操作HTML元素、改变CSS样式&#xff0c;以及处理用户的交互事件等。 以下是JavaScript的常见基础知识点&#xff1a; 变量和数据类型&#xff1a;JavaScript中的变量可以存…

YOLOv7优化:独家创新(Partial_C_Detect)检测头结构创新,实现涨点 | 检测头新颖创新系列

💡💡💡本文独家改进:独家创新(Partial_C_Detect)检测头结构创新,适合科研创新度十足,强烈推荐 SC_C_Detect | 亲测在多个数据集能够实现大幅涨点 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOLO…

【实现多个接口的使用】

文章目录 前言实现多个接口接口间的继承接口使用实例给对象数组排序创建一个比较器 总结 前言 实现多个接口 Java中不支持多继承&#xff0c;但是一个类可以实现多个接口 下面是自己反复理了很久才敲出来的&#xff0c;涉及到之前学的很多知识点 如果哪看不懂&#xff0c;真…

基于向量数据库的文档检索实战

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在过去的六个月里&#xff0c;我一直在 A 系列初创公司 Voxel51 工作&#xff0c;该公司是开源计算机视觉工具包 FiftyOne 的创建者。 作为一名机器学习工程师和开发人员布道者&#xff0c;我的工作是倾听我们的开源社区的…

LabVIEW开发双目立体系统猪重估算

LabVIEW开发双目立体系统猪重估算 动物的活重是各种研究中的重要参考&#xff0c;例如动物生长&#xff0c;饲料转化率&#xff0c;健康状况和疾病发生。生长中的动物的体重为保持它们处于适当的营养和环境水平提供了一个有价值的参数或指标。动物的利润通常与收入和成本之间的…

Sci Immunol丨Tim-3 适配器蛋白 Bat3 是耐受性树突状细胞

今天和大家分享一篇发表于2022年3月的文章&#xff0c;题目为“Tim-3 adapter protein Bat3 acts as an endogenous regulator of tolerogenic dendritic cell function”&#xff0c;发表在《Sci Immunol》杂志上。文章主要研究了Tim-3和其适配蛋白Bat3在调节免疫应答中的作用…

2023年【R1快开门式压力容器操作】最新解析及R1快开门式压力容器操作复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R1快开门式压力容器操作最新解析是安全生产模拟考试一点通生成的&#xff0c;R1快开门式压力容器操作证模拟考试题库是根据R1快开门式压力容器操作最新版教材汇编出R1快开门式压力容器操作仿真模拟考试。2023年【R1快…

linux 安装 elasticsearch 全教程

一、去 elasticsearch官网找到Linux版本的下载链接 地址https://www.elastic.co/cn/downloads/elasticsearch 二、在linux 中用wget下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.10.4-linux-x86_64.tar.gz三、下载成功后解压文件 tar -x…

Unity中Shader自定义cginc文件

文章目录 前言我们在使用如下场景中的小球来进行自己的 GI Shader测试一、先找到Unity自带的 cginc 库二、仿照 Unity 的 cginc 文件&#xff0c;写一个我们自己的 cginc 文件1、我们新建一个文件&#xff0c;在资源管理器中把 文件后缀名修改成 .cginc2、我们打开Unity自己的c…

【Linux】安装使用Nginx负载均衡,并且部署前端项目

目录 一、Nginx概述 1. 什么 2. 背景 3. 作用 二、Nginx负载均衡 1. 讲述 2. 使用 1. 下载 2. 安装 3. 负载均衡 三、前端部署 1. 准备 2. 部署 一、Nginx概述 1. 什么 Nginx是一个高性能的开源Web服务器和反向代理服务器。它具有轻量级、高并发、低内存消耗的…

让你笑到不行的笑话短视频接口,快来试试!

11在当今这个快节奏的社会中&#xff0c;笑话成为了许多人调节情绪的有效方法。如今&#xff0c;短视频平台已经成为了最受欢迎的娱乐方式之一&#xff0c;因此&#xff0c;将笑话和短视频结合起来&#xff0c;成为了一种很有趣的方式来带给我们欢乐。今天我们要介绍的是挖数据…

【电路笔记】-正弦波形

正弦波 文章目录 正弦波1、概述2、波形产生3、总结 在 19 世纪末的 10 年间&#xff0c;许多技术成就使得交流电的使用得以扩展&#xff0c;并克服了直流电向公众供电的局限性。 1882 年&#xff0c;法国发明了变压器&#xff0c;它简化了交流电的分配&#xff0c;正如我们将在…

【免费活动】11月4日敏捷武林上海站 | Scrum.org CEO 亲临现场

活动介绍 过去的几年里&#xff0c;外界的风云变幻为我们的生活增添了一些不一样的色彩。在VUCA世界的浪潮里&#xff0c;每一个人都成为自己生活里的冒险家。面对每一次的变化&#xff0c;勇于探索未知&#xff0c;迎接挑战&#xff0c;努力追逐更好的自己。 七月&#xff0…