C语言实现贪吃蛇,链表实现,图文详解,含登陆系统

前言:这款贪吃蛇是用链表创建的,涵盖了链表的头插法新结点生成、遍历链表、链表的动态内存分配,且有完整的登录注册账号密码的界面,完整的菜单界面,最高分记录,用户信息储存,菜单跳转等等

这个版本的贪吃蛇核心代码仅大约550行,简单易懂,有图文讲解,有350行代码是登录系统的

实现效果

目录

一.具体需求&实现思路

二.各部分功能实现

1.创建文件储存数据

2.头文件、函数声明、结构体、宏常量

3.光标定位

4.登录系统

5.主函数&菜单

6.游戏开始前获取最高分信息

7.初始化地图

8.初始化蛇

9.创建食物

10.获取键盘信号

11.反应蛇的走向

12.判断蛇是否吃到食物&打印蛇的移动轨迹

13.判断是否吃到自己&判断是否撞墙

14.蛇的加速和减速

15.游戏结束画面&游戏说明

三.完整代码

四.结语


一.具体需求&实现思路

首先是结构体,这个游戏中涵盖了四个对象,分别是玩家、最高分得者、蛇、食物,所以要创建四个结构体。

登录系统中涵盖了登录、注册、找回密码、输入密码掩盖等功能。

游戏开始前,要初始化一些游戏数据、初始化地图、初始化蛇、创建食物。

游戏过程中,要时刻监听(获取)键盘输入信号,获取信号后蛇就可以移动,蛇移动后要判断是 吃到食物、是否撞到墙、是否吃到自己。

游戏结束后,要有结束画面、保存最高分信息。

综合以上考虑,有了以下大概框架。

接着,我们就可以准备下前期工作,声明函数、头文件,创建需要的结构体,定义宏常量。

这里说一下为什么要定义宏常量,宏常量在前面定义完后,函数功能实现就可以用,而且最重要的是,在后面需要对代码的一些参数做修改时,可以直接修改宏常量的数值,不用在代码里到处改,会方便很多

上代码!!!

二.各部分功能实现

1.创建文件储存数据

需要在.c文件的同一个目录下创建两个文件,一个叫users.dat,用于储存用户数据;另一个叫maxscore.dat,用于储存最高分信息。.dat后缀是我随便起的,无所谓,就行了~

 像我这样就可以了,一定要和自己创建的.c文件同个目录下!!!

2.头文件、函数声明、结构体、宏常量


//引用头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>//用来反应错误警告的,后面会讲
#include <windows.h>
#include <time.h>

//定义宏常量
#define U 1//上
#define D 2//下
#define L 3//左
#define R 4//右
#define INIT_VALUE 4//初始蛇身长度
#define MAX_SLEEPTIME 340//蛇最慢的速度
#define LOW_SLEEPTIME 60//蛇最快的速度
#define TIME_SCOPE 20//速度变化范围
#define COL 70//长
#define LINE 36//高
//定义宏常量
#define MAX_ID 11//ID的最大长度
#define MAX_PWD 20//密码的最大长度
#define MAX_NAME 15//姓名的最大长度
#define MAX_SEX 5//性别的最大长度
#define MAX_PHONE 12//电话的最大长度

//声明全局变量
int addition;//吃一个食物加多少分
int score;//得分
int status;
int sleeptime;//游戏间隔(蛇的移动速度)
struct MvpUser mvp;//最高分玩家
Users c;//玩家数据
snake* head;//一直记录蛇头的位置


//结构体创建

//创建用户的结构体
typedef struct Users
{
	char id[MAX_ID];
	char pwd[MAX_PWD];
	char name[MAX_NAME];
	char sex[MAX_SEX];
	char phone[MAX_PHONE];
}Users;

//历史最高得分玩家的结构体
struct MvpUser
{
	char name[MAX_NAME];
	int maxscore;
};


//蛇的结构体
typedef struct Snake
{
	int x;
	int y;
	struct Snake* next;
}snake;

//食物的结构体
struct Food
{
	int x;
	int y;
}food;


//声明函数-登录系统

//用户注册
void Register();

//登录
int Login();

//找回密码
void Reback();

//找到光标的x坐标
int posx();

//找到光标的y坐标
int posy();

//密码的输入,具有掩盖功能
void Getpwd(char* pwd);


//声明函数-游戏系统

//打印菜单
void meun_1();
void meun_2();

//更新光标位置
void gotoxy(int x, int y);

//画地图
void CreatMap();

//画方块
void gotoprint(int x, int y);

//游戏说明
void explation();

//初始化蛇
void InitSnake();

//添加食物
void CreatFood();

//键盘控制-主循环
void KeyboardControl();

//snake加速
void Speedup();

//snake减速
void Speeddown();

//snake的移动
int Snakemoving();

//判断撞墙
int JudgeHitWall();

//判断蛇头是否会吃到自己
int Hititself();

//判断是否吃到东西
void JudgeEatFood(snake* nexthead);

//获取历史最高分
void GetMaxScore();

//保存历史最高得分
void SaveMaxScore();

3.光标定位

我们这里面光标会到处跑来跑去打印东西,所以需要一个移动定位光标的函数

//移动光标
void gotoxy(int x, int y)
{
	//更新光标位置
	COORD pos;
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOutput, pos);
	//隐藏光标
	CONSOLE_CURSOR_INFO cursor;
	cursor.bVisible = FALSE;
	cursor.dwSize = sizeof(cursor);
	SetConsoleCursorInfo(hOutput, &cursor);
}

这几个长长的函数是windows控制台的API函数,具体是什么我也不太懂,有兴趣的大佬可以点点下面的链接了解了解~

COORD

GetStdHandle

SetConsoleCursorPosition

SetConsoleCursorInfo

4.登录系统

登录系统可以看博主之前发的一篇关于登录系统的详细讲解的文章,这里不过多讲解(有详细的注释),直接上代码

登录系统

//注册账号
void Register()
{
	Users a, b;//创建用户结构体临时变量a,b;a是用来接收用户输入的,b是用来从文件中读取的,来和a作比较

	char tmp[20] = "";用着下面判断的

	printf("欢迎来到注册界面!\n");

	FILE* pf = fopen("users.dat", "rb");//"rb"是以二进制只读方式打开文件的意思
	fread(&b, sizeof(Users), 1, pf);//将数据读入b中
	if (!pf)
	{
		printf("%s", strerror(errno));errno可以理解是报的错误对应的编号,strerror可以把这个编号翻译出来,输出在屏幕上
		return;
	}
	Sleep(1000);

	printf("请输入您的账号>>");
	scanf("%s", &a.id);

	while (1)
	{
		if (strcmp(a.id, b.id))//两个用户名不相等
		{
			if (!feof(pf))//没读到文件尾,继续读
		{
				fread(&b, sizeof(Users), 1, pf);
			}
			else//读到文件末尾了,确认没有重复的id
			{
				break;
			}
		}
		else//有两个用户名相等
		{
			printf("此用户名已存在在,请重新输入!\n");
			Sleep(1000);
			fclose(pf);关闭文件
			pf = NULL;pf置空,避免野指针
			return;
		}
	}
	printf("请输入您的姓名>>");
	scanf("%s", a.name);
	printf("请输入您的性别>>");
	scanf("%s", a.sex);
	printf("请输入您的电话号码>>");
	scanf("%s", a.phone);
	printf("请输入您的密码>>");
	Getpwd(a.pwd);输入密码的函数,有掩盖功能,下面会介绍
	printf("\n请再次确认您的密码>>");
	Getpwd(tmp);
	do
	{
		if (!strcmp(a.pwd, tmp))//两次密码相等
		{
			pf = fopen("users.dat", "ab");
			fwrite(&a, sizeof(Users), 1, pf);
			printf("\n账号注册成功,请登录!\n");
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return;
		}
		else
		{
			printf("\n两次密码不匹配!请重新输入>>");
			Getpwd(a.pwd);
			printf("\n请再次确认>>");
			Getpwd(tmp);
		}
	} while (1);
}


//登录
int Login()
{
	Users a, b;同理,a是用来给用户输入的,b是用来是水的(从文件中读取到b中,用b和a匹配比较)

	FILE* pf = fopen("users.dat", "rb");//以只读的方式打开文件
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	printf("欢迎来到登录界面!\n");
	Sleep(1000);

	fread(&b, sizeof(Users), 1, pf);

	printf("请输入账号>>");
	scanf("%s", a.id);

	while (1)
	{
		if (!strcmp(a.id, b.id))//在文件中找到了与用户输入相同的id
		{
			break;
		}
		else
		{
			if (!feof(pf))//没读到文件末尾,继续读取文件中的id到b中
			{
				fread(&b, sizeof(Users), 1, pf);
			}
			else//读到文件末尾了,没有找到与用户输入相同的账号
			{
				printf("此账号不存在!请重新输入!\n");
				Sleep(500);
				fclose(pf);
				pf = NULL;
				return 0;
			}
		}
	}

	do
	{
		printf("请输入密码>>");
		Getpwd(a.pwd);
		if (!strcmp(a.pwd, b.pwd))//输入的密码与文件中的相同
		{
			printf("\n登录成功!欢迎使用!\n");
			memcpy(&c, &b, sizeof(Users));//将用户的完整数据从b拷贝到c
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return 1;//登录成功返回1
		}
		else
		{
			printf("\n密码输入错误,请重新输入\n");
		}
	} while (strcmp(a.pwd, b.pwd));
	return 1;
}

//找回密码
void Reback()
{
	char tmp[20] = "";
	Users a, b;

	FILE* pf = fopen("users.dat", "rb+");
	if (!pf)
	{
		printf("%s", strerror(errno));
		return;
	}

	fread(&b, sizeof(Users), 1, pf);

	printf("请输入您的账号>>");
	Sleep(1000);
	scanf("%s", a.id);

	while (1)//在文件中找到与用户输入相同的id
	{
		if (!strcmp(a.id, b.id))如果读取到了相同的id(在文件中找到了和用户输入一样的)
		{
			break;
		}
		else
		{
			if (!feof(pf))没读到文件尾,继续读
			{
				fread(&b, sizeof(Users), 1, pf);
			}
			else
			{
				printf("您输入的账号不存在!请重新输入!\n");
				Sleep(500);
				fclose(pf);
				pf = NULL;
				break;
			}
		}
	}
	//下面是信息匹配验证
	do
	{
		printf("请输入您的姓名>>");
		scanf("%s", a.name);
		if (!strcmp(a.name, b.name))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.name, b.name));
	do
	{
		printf("请输入您的性别>>");
		scanf("%s", a.sex);
		if (!strcmp(a.sex, b.sex))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.sex, b.sex));
	do
	{
		printf("请输入您的电话号码>>");
		scanf("%s", a.phone);
		if (!strcmp(a.phone, b.phone))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.phone, b.phone));

	printf("验证成功!请修改您的密码!\n");
	printf("请输入您的密码>>");
	Getpwd(a.pwd);
	printf("\n请再次确认您的密码>>");
	Getpwd(tmp);

	if (!pf)
	{
		printf("%s", strerror(errno));
		return;
	}

	do
	{
		if (!strcmp(a.pwd, tmp))//两次密码相等
		{
			fseek(pf, -((int)(sizeof(Users) - MAX_ID)), SEEK_CUR);//将文件流调回到要修改的密码的位置
			fprintf(pf, "%s", a.pwd);//覆盖原来的密码
			printf("\n密码修改成功,请登录!\n");
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return;
		}
		else
		{
			printf("\n两次密码不匹配!请重新输入>>");
			Getpwd(a.pwd);
			printf("\n请再次确认>>");
			Getpwd(tmp);
		}
	} while (1);
}
//获取光标x坐标
int posx()
{
	CONSOLE_SCREEN_BUFFER_INFO ptr;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ptr);
	return (ptr.dwCursorPosition.X);
}
//获取光标y坐标
int posy()
{
	CONSOLE_SCREEN_BUFFER_INFO ptr;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ptr);
	return (ptr.dwCursorPosition.Y);
}
//输入密码
void Getpwd(char* pwd)
{
	int i = 0;
	int x, y;
	while (1)
	{
		pwd[i] = getch();//获取单个密码(字符)
		if (pwd[i] == VK_BACK && i >= 0)//如果输入的是回退键,VK_BACK是键盘的键值,ASCII码值为8
		{
			if (i > 0)//i>0说明有输入东西了,则回退前一格
			{
				i--;
				x = posx() - 1;//定位x并回退一格
				y = posy();//定位y
				gotoxy(x, y);//定位光标
				printf(" ");//将*用空格掩盖
				x = posx() - 1;//再次回退,下次输入时光标才会显示在正确的位置
				y = posy();//定位y
				gotoxy(x, y);
				continue;//然后跳过此次循环
			}
			else if (i == 0)//i==0说明没输入东西,直接跳过此次循环即可
			{
				continue;
			}
		}
		if (i >= 0 && pwd[i] != VK_RETURN && pwd[i] != VK_BACK)//输入东西了
		{
			x = posx();
			y = posy();
			gotoxy(x, y);
			printf("*");
		}
		if (i == 0 && pwd[i] == VK_RETURN)//如果没输入东西直接按回车,直接跳过此次循环,避免程序把回车当密码了
		{
			continue;
		}
		if (pwd[i] == VK_RETURN || i == MAX_PWD - 2)//输入回车了或者到上限了
		{
			i++;
			pwd[i] = '\0';//结尾放'\0'
			break;
		}
		i++;
	}
}

5.主函数&菜单

//主菜单1-登录界面
void meun_1()
{
	gotoxy(15, 8);
	printf("       ************************************");
	gotoxy(20, 11);
	printf("     欢迎来到贪吃蛇Hi~ o(* ̄▽ ̄*)ブ         ");
	gotoxy(14, 14);
	printf("  1.登录       2.注册       3.找回密码     0.退出");
	gotoxy(15, 17);
	printf("       ************************************\n");
}

//主菜单2-游戏菜单界面
void meun_2()
{
	gotoxy(15, 8);
	printf("       ************************************");
	gotoxy(20, 11);
	printf("     欢迎来到贪吃蛇Hi~ o(* ̄▽ ̄*)ブ         ");
	gotoxy(16, 14);
	printf("  1.开始游戏        2.游戏说明        0.返回    ");
	gotoxy(15, 17);
	printf("       ************************************\n");
}

int main()
{
	char input;
	char init;
	char ch;
	system("color 0C");//设置颜色
	system("mode con cols=130 lines=40");//设置控制台长&高
	do
	{
		system("cls");//清屏
		setbuf(stdin, NULL);//清空输入缓冲区
		meun_1();
		scanf("%c", &input);
		switch (input)
		{
		case '1':
			if (Login())//登录
			{
				do
				{
					system("cls");//清屏
					setbuf(stdin, NULL);//清空输入缓冲区
					meun_2();
					scanf("%c", &init);
					switch (init)
					{
					case '1':
						CreatMap();//画地图
						InitSnake();//初始化蛇
						CreatFood();//创建食物
						KeyboardControl();//获取键盘输入信号,游戏主循环
						SaveMaxScore();//保存最高分
						break;
					case '2':
						do
						{
							explation();//游戏玩法
							scanf("%c", &ch);
						} while (ch != '0');
						break;
					case '0':
						printf("返回成功!\n");
						Sleep(1000);
						break;
					default:
						printf("输入错误,请重新输入!\n");
						break;
					}
					system("cls");
				} while (init != '0');
			}
			break;
		case '2':
			Register();//注册
			break;
		case '3':
			Reback();//找回密码
			break;
		case '0':
			printf("退出成功!\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input != '0');
	return 0;
}

这里用do-while循环加上switch语句,可以很好地实现功能的选择和界面的选择。

用字符类型来作为switch的判断依据,这样不会因为输入了字符串或者其它东西而引起死循环,具体情况大家可以去试一试,最后在每一次输入前最好都清空一下输入缓冲区,避免上一次输入的东西没有完全输入进字符,留在输入缓冲区对下一次输入造成影响。

6.游戏开始前获取最高分信息

//获取最高分玩家
void GetMaxScore()
{
	FILE* pf = fopen("maxscore.dat", "rb");//打开数据文件

	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	fread(&mvp, sizeof(struct MvpUser), 1, pf);//获取最高分,将最高分玩家读入mvp中

	fclose(pf);//关闭文件
	pf = NULL;//置空
}

7.初始化地图

//打印方块
void gotoprint(int x, int y)
{
	gotoxy(x, y);
	printf("■");
}

//打印地图
void CreatMap()
{
	//清屏
	system("cls");

	//获取最高分
	GetMaxScore();

	//重新初始化数据
	score = 0;
	addition = 10;
	sleeptime = 200;

	//打印地图
	for (int i = 0; i <= COL; i += 2)//COL是长,这里初始值是70
	{
		gotoprint(i, 0);
		gotoprint(i, LINE);
	}
	for (int i = 1; i < LINE; i++)//LINE是高,这里初始值是36
	{
		gotoprint(0, i);
		gotoprint(COL, i);
	}

	//打印附加栏
	gotoxy(78, 8);
	printf("得分:%2d  ", score);
	gotoxy(92, 8);
	printf("玩家:%s", c.name);
	gotoxy(78, 11);
	printf("历史最高得分:%4d分,用户名:%s", mvp.maxscore,mvp.name);
	gotoxy(78, 14);
	printf("每个食物得分:%d分", addition);
	gotoxy(78, 16);
	printf("不能穿墙,不能咬到自己");
	gotoxy(78, 18);
	printf("用↑ ↓ ← →分别控制蛇的移动");
	gotoxy(78, 20);
	printf("F1 为加速,F2 为减速");
	gotoxy(78, 22);
	printf("space:暂停游戏");
	gotoxy(78, 24);
	printf("ESC :退出游戏");
}

8.初始化蛇

蛇的数据结构我们用到链表,蛇头及头结点,这里蛇头坐标由我们自己初始化坐标,用头插法生成蛇身,就是在蛇头的前面,生成一个新的蛇头,旧的蛇头自然而然就变成了蛇身。

//初始化蛇
void InitSnake()
{
	//头插法生成蛇
    //生成新蛇头
	snake* newhead = (snake*)malloc(sizeof(snake));
	if (!newhead)
	{
		printf("%s\n", strerror(errno));
		return;
	}

	//初始化蛇的坐标
	newhead->x = 26;
	newhead->y = 10;
	newhead->next = NULL;//尾部的下一个为空

	//生成蛇身
	for (int i=1; i <= INIT_VALUE; i++)//初始生成4节
	{
		head = (snake*)malloc(sizeof(snake));
		if (!head)
		{
			printf("%s\n", strerror(errno));
			return;
		}
		head->x = 26 + 2 * i;
		head->y = 10;
		head->next = newhead;
		newhead = head;//重新指向蛇头
	}

    //newhead已经没用了,我们就用来遍历蛇身(遍历链表)
     
	//打印蛇身
	while (newhead != NULL)
	{
		gotoxy(newhead->x, newhead->y);
		printf("■");
		newhead = newhead->next;
	}
	free(newhead);
	newhead = NULL;
}

9.创建食物

用时间戳随机获取食物

//刷新食物
void CreatFood()
{
	int flag = 0;
	while (!flag)
	{
		srand((unsigned int)time(NULL));
		food.y = (rand() % (LINE-2)) + 1;
		food.x = 1;//初始化食物的横坐标为奇数,使其进入下面的循环
		while ((food.x % 2) != 0)//使食物的横坐标为偶数,因为windows控制台的长高比为2:1
		{
			food.x = ( rand() % (COL-4) )+ 2;
		}
		flag = 1;
	    snake* p = head;//工具蛇p上场
		while (1)//遍历蛇身避免食物生成在蛇身
		{
			if (p->next == NULL)//如果已经遍历到蛇尾,跳出循环
			{
				break;
			}
			if (food.x == p->x && food.y == p->y)//如果食物坐标在蛇身上
			{
				flag = 0;
			}
			p = p->next;//p指向下一个蛇身
		}
	}
	gotoxy(food.x, food.y);
	printf("⊙");
}

10.获取键盘信号

void KeyboardControl()
{

	status = R;
	do
	{
		if (GetAsyncKeyState(VK_UP) && status != D)//接收键盘信号,符合条件赋值
		{
			status = U;
		}
		else if (GetAsyncKeyState(VK_DOWN) && status != U)
		{
			status = D;
		}
		else if (GetAsyncKeyState(VK_LEFT) && status != R)
		{
			status = L;
		}
		else if (GetAsyncKeyState(VK_RIGHT) && status != L)
		{
			status = R;
		}
		if (GetAsyncKeyState(VK_SPACE))//空格暂停
		{
			while (1)
			{
				Sleep(500);
				if (GetAsyncKeyState(VK_SPACE))
				{
					break;
				}
			}
		}
		else if (GetAsyncKeyState(VK_ESCAPE))//ESC退出
		{
			gameover();
			break;
		}
		else if (GetAsyncKeyState(VK_F1))//F1加速
		{
			Speedup();
		}
		else if (GetAsyncKeyState(VK_F2))//F2减速
		{
			Speeddown();
		}
		Sleep(sleeptime);

	}while(SnakeMoving());//Snakemoving是蛇的移动,蛇成功动了就返回1
	gotoxy(30, 37);
	system("pause");
}

GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数,如果键盘上按下了这个按键,就返回1。

11.反应蛇的走向

int SnakeMoving()
{
	snake* nexthead=(snake*)malloc(sizeof(snake));//创建新的蛇头结点
	if (!nexthead)//创建失败
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	if (status == U)//如果接收的是↑
	{
		nexthead->x = head->x;//横坐标不变
		nexthead->y = head->y - 1;//纵坐标减1
		nexthead->next = head;//新的头存下旧的头的地址(链接)
		head = nexthead;
        //判断是否吃到食物,蛇的具体显示移动在下面的函数中
		JudgeEatFood(nexthead);//这里传nexthead安全点,因为已经用不到了
	}
	else if (status == D)
	{
		nexthead->x = head->x;
		nexthead->y = head->y + 1;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	else if (status == L)
	{
		nexthead->x = head->x - 2;
		nexthead->y = head->y;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	else if (status == R)
	{
		nexthead->x = head->x + 2;
		nexthead->y = head->y;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	if (JudgeHitWall())//判断是否撞墙
	{
		gotoxy(20,37);
		printf("撞到墙了喔~\n");
		system("pause");
		gameover();
		return 0;
	}
	if (Hititself())//判断是否吃到自己
	{
		gotoxy(20, 37);
		printf("吃到自己了喔~\n");
		system("pause");
		gameover();
		return 0;
	}
	return 1;
}

12.判断蛇是否吃到食物&打印蛇的移动轨迹

如果蛇吃到食物,我们可以通过将食物直接画成蛇头,这样蛇就向前移动也变长一节。

 如果没吃到食物,就在旧的蛇头前画上新的蛇头,然后把蛇尾用两个空格填了(为什么是两个空格?因为windows控制台的长高比2:1)。

 下面是代码实现

//判断是否吃到了食物
void JudgeEatFood(snake*nexthead)
{
	if (nexthead->x == food.x && nexthead->y == food.y)//新的头碰到了食物
	{
		CreatFood();//创造下一个食物

		gotoprint(nexthead->x, nexthead->y);//画蛇头

		score += addition;//得分
		gotoxy(78, 8);
		printf("得分:%d  ", score);
		Speedup();//吃到食物后加速
		
	}
	else//如果没吃到食物
	{
		gotoprint(nexthead->x, nexthead->y);//画新的蛇头
		snake* p = nexthead;//用p来遍历蛇身

		while (p->next->next!=NULL)//将p指到蛇身的倒数第二个,即蛇尾的前一个
		{
			p = p->next;
		}

		gotoxy(p->next->x, p->next->y);//到原来蛇尾的位置
		printf("  ");//将原来蛇尾的位置填上两个空格,由于windows控制台长宽比,注意是两个空格

		free(p->next);//将原来蛇尾的指针free掉
		p->next = NULL;//指空,避免野指针
	}
}

13.判断是否吃到自己&判断是否撞墙

//判断是否吃到自己

int Hititself()
{
	snake* p = head->next;//工具蛇登场
	while (p->next)//遍历蛇身,找有没有蛇头的坐标与蛇身相同的
	{
		if (p->x == head->x && p->y == head->y)
		{
			return 1;
		}
		p = p->next;
	}
	return 0;
}

//判断是否撞墙
int JudgeHitWall()
{
	if (head->x == 0 || head->x == COL || head->y == LINE || head->y == 0)
	{
		return 1;
	}
	return 0;
}

14.蛇的加速和减速

//snake加速
void Speedup()
{
	if (sleeptime > LOW_SLEEPTIME)
	{
		sleeptime -= TIME_SCOPE;
		addition++;//每个食物得分加1
		gotoxy(78, 14);
		printf("每个食物得分:%2d分", addition);
	}
}

//snake减速
void Speeddown()
{
	if (sleeptime < MAX_SLEEPTIME)
	{
		sleeptime += TIME_SCOPE;
		addition--;每个食物得分-1
		gotoxy(78, 14);
		printf("每个食物得分:%2d分", addition);
	}
}

15.游戏结束画面&游戏说明

//游戏结束画面
void gameover()
{
	system("cls");
	gotoxy(15, 10);
	printf("/**********************************************/");
	gotoxy(15, 20);
	printf("/**********************************************/");
	gotoxy(20, 14);
	printf("GOOD   GAME      o(≧口≦)o");
	gotoxy(22, 16);
	printf("Your Score is %d    GAME OVER!", score);
	gotoxy(20, 18);
	printf("还不错哦,     继续努力ヾ( ̄▽ ̄)Bye~Bye~");
}

//游戏说明
void explation()
{
	system("cls");
	gotoxy(30, 8);
	printf("1. 不能穿墙,不能咬到自己");
	gotoxy(30, 11);
	printf("2. 用↑.↓.←.→分别控制蛇的移动");
	gotoxy(30, 14);
	printf("3. F1 为加速,F2 为减速");
	gotoxy(30, 17);
	printf("4. 按空格键暂停游戏,再按空格键继续");
	gotoxy(30, 20);
	printf("5. ESC :退出游戏.space:暂停游戏");
	gotoxy(30, 23);
	printf("6,返回上一级请输入0\n");
}

三.完整代码


//引用头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>//用来反应错误警告的,后面会讲
#include <windows.h>
#include <time.h>

//定义宏常量
#define U 1//上
#define D 2//下
#define L 3//左
#define R 4//右
#define INIT_VALUE 4//初始蛇身长度
#define MAX_SLEEPTIME 340//蛇最慢的速度
#define LOW_SLEEPTIME 60//蛇最快的速度
#define TIME_SCOPE 20//速度变化范围
#define COL 70//长
#define LINE 36//高
//定义宏常量
#define MAX_ID 11//ID的最大长度
#define MAX_PWD 20//密码的最大长度
#define MAX_NAME 15//姓名的最大长度
#define MAX_SEX 5//性别的最大长度
#define MAX_PHONE 12//电话的最大长度

//声明全局变量
int addition;//吃一个食物加多少分
int score;//得分
int status;
int sleeptime;//游戏间隔(蛇的移动速度)
struct MvpUser mvp;//最高分玩家
Users c;//玩家数据
snake* head;//一直记录蛇头的位置


//结构体创建

//创建用户的结构体
typedef struct Users
{
	char id[MAX_ID];
	char pwd[MAX_PWD];
	char name[MAX_NAME];
	char sex[MAX_SEX];
	char phone[MAX_PHONE];
}Users;

//历史最高得分玩家的结构体
struct MvpUser
{
	char name[MAX_NAME];
	int maxscore;
};


//蛇的结构体
typedef struct Snake
{
	int x;
	int y;
	struct Snake* next;
}snake;

//食物的结构体
struct Food
{
	int x;
	int y;
}food;


//声明函数-登录系统

//用户注册
void Register();

//登录
int Login();

//找回密码
void Reback();

//找到光标的x坐标
int posx();

//找到光标的y坐标
int posy();

//密码的输入,具有掩盖功能
void Getpwd(char* pwd);


//声明函数-游戏系统

//打印菜单
void meun_1();
void meun_2();

//更新光标位置
void gotoxy(int x, int y);

//画地图
void CreatMap();

//画方块
void gotoprint(int x, int y);

//游戏说明
void explation();

//初始化蛇
void InitSnake();

//添加食物
void CreatFood();

//键盘控制-主循环
void KeyboardControl();

//snake加速
void Speedup();

//snake减速
void Speeddown();

//snake的移动
int Snakemoving();

//判断撞墙
int JudgeHitWall();

//判断蛇头是否会吃到自己
int Hititself();

//判断是否吃到东西
void JudgeEatFood(snake* nexthead);

//获取历史最高分
void GetMaxScore();

//保存历史最高得分
void SaveMaxScore();

int main()
{
	char input;
	char init;
	char ch;
	system("color 0C");//设置颜色
	system("mode con cols=130 lines=40");//设置控制台长&高
	do
	{
		system("cls");//清屏
		setbuf(stdin, NULL);//清空输入缓冲区
		meun_1();
		scanf("%c", &input);
		switch (input)
		{
		case '1':
			if (Login())//登录
			{
				do
				{
					system("cls");//清屏
					setbuf(stdin, NULL);//清空输入缓冲区
					meun_2();
					scanf("%c", &init);
					switch (init)
					{
					case '1':
						CreatMap();//画地图
						InitSnake();//初始化蛇
						CreatFood();//创建食物
						KeyboardControl();//获取键盘输入信号,游戏主循环
						SaveMaxScore();//保存最高分
						break;
					case '2':
						do
						{
							explation();//游戏玩法
							scanf("%c", &ch);
						} while (ch != '0');
						break;
					case '0':
						printf("返回成功!\n");
						Sleep(1000);
						break;
					default:
						printf("输入错误,请重新输入!\n");
						break;
					}
					system("cls");
				} while (init != '0');
			}
			break;
		case '2':
			Register();//注册
			break;
		case '3':
			Reback();//找回密码
			break;
		case '0':
			printf("退出成功!\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input != '0');
	return 0;
}

//主菜单1-登录界面
void meun_1()
{
	gotoxy(15, 8);
	printf("       ************************************");
	gotoxy(20, 11);
	printf("     欢迎来到贪吃蛇Hi~ o(* ̄▽ ̄*)ブ         ");
	gotoxy(14, 14);
	printf("  1.登录       2.注册       3.找回密码     0.退出");
	gotoxy(15, 17);
	printf("       ************************************\n");
}

//主菜单2-游戏菜单界面
void meun_2()
{
	gotoxy(15, 8);
	printf("       ************************************");
	gotoxy(20, 11);
	printf("     欢迎来到贪吃蛇Hi~ o(* ̄▽ ̄*)ブ         ");
	gotoxy(16, 14);
	printf("  1.开始游戏        2.游戏说明        0.返回    ");
	gotoxy(15, 17);
	printf("       ************************************\n");
}

//获取最高分玩家
void GetMaxScore()
{
	FILE* pf = fopen("maxscore.dat", "rb");//打开数据文件

	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	fread(&mvp, sizeof(struct MvpUser), 1, pf);//获取最高分,将最高分玩家读入mvp中

	fclose(pf);//关闭文件
	pf = NULL;//置空
}

//打印方块
void gotoprint(int x, int y)
{
	gotoxy(x, y);
	printf("■");
}

//打印地图
void CreatMap()
{
	//清屏
	system("cls");

	//获取最高分
	GetMaxScore();

	//重新初始化数据
	score = 0;
	addition = 10;
	sleeptime = 200;

	//打印地图
	for (int i = 0; i <= COL; i += 2)//COL是长,这里初始值是70
	{
		gotoprint(i, 0);
		gotoprint(i, LINE);
	}
	for (int i = 1; i < LINE; i++)//LINE是高,这里初始值是36
	{
		gotoprint(0, i);
		gotoprint(COL, i);
	}

	//打印附加栏
	gotoxy(78, 8);
	printf("得分:%2d  ", score);
	gotoxy(92, 8);
	printf("玩家:%s", c.name);
	gotoxy(78, 11);
	printf("历史最高得分:%4d分,用户名:%s", mvp.maxscore,mvp.name);
	gotoxy(78, 14);
	printf("每个食物得分:%d分", addition);
	gotoxy(78, 16);
	printf("不能穿墙,不能咬到自己");
	gotoxy(78, 18);
	printf("用↑ ↓ ← →分别控制蛇的移动");
	gotoxy(78, 20);
	printf("F1 为加速,F2 为减速");
	gotoxy(78, 22);
	printf("space:暂停游戏");
	gotoxy(78, 24);
	printf("ESC :退出游戏");
}

//初始化蛇
void InitSnake()
{
	//头插法生成蛇
    //生成新蛇头
	snake* newhead = (snake*)malloc(sizeof(snake));
	if (!newhead)
	{
		printf("%s\n", strerror(errno));
		return;
	}

	//初始化蛇的坐标
	newhead->x = 26;
	newhead->y = 10;
	newhead->next = NULL;//尾部的下一个为空

	//生成蛇身
	for (int i=1; i <= INIT_VALUE; i++)//初始生成4节
	{
		head = (snake*)malloc(sizeof(snake));
		if (!head)
		{
			printf("%s\n", strerror(errno));
			return;
		}
		head->x = 26 + 2 * i;
		head->y = 10;
		head->next = newhead;
		newhead = head;//重新指向蛇头
	}

    //newhead已经没用了,我们就用来遍历蛇身(遍历链表)
     
	//打印蛇身
	while (newhead != NULL)
	{
		gotoxy(newhead->x, newhead->y);
		printf("■");
		newhead = newhead->next;
	}
	free(newhead);
	newhead = NULL;
}

//刷新食物
void CreatFood()
{
	int flag = 0;
	while (!flag)
	{
		srand((unsigned int)time(NULL));
		food.y = (rand() % (LINE-2)) + 1;
		food.x = 1;//初始化食物的横坐标为奇数,使其进入下面的循环
		while ((food.x % 2) != 0)//使食物的横坐标为偶数,因为windows控制台的长高比为2:1
		{
			food.x = ( rand() % (COL-4) )+ 2;
		}
		flag = 1;
	    snake* p = head;//工具蛇p上场
		while (1)//遍历蛇身避免食物生成在蛇身
		{
			if (p->next == NULL)//如果已经遍历到蛇尾,跳出循环
			{
				break;
			}
			if (food.x == p->x && food.y == p->y)//如果食物坐标在蛇身上
			{
				flag = 0;
			}
			p = p->next;//p指向下一个蛇身
		}
	}
	gotoxy(food.x, food.y);
	printf("⊙");
}

void KeyboardControl()
{

	status = R;
	do
	{
		if (GetAsyncKeyState(VK_UP) && status != D)//接收键盘信号,符合条件赋值
		{
			status = U;
		}
		else if (GetAsyncKeyState(VK_DOWN) && status != U)
		{
			status = D;
		}
		else if (GetAsyncKeyState(VK_LEFT) && status != R)
		{
			status = L;
		}
		else if (GetAsyncKeyState(VK_RIGHT) && status != L)
		{
			status = R;
		}
		if (GetAsyncKeyState(VK_SPACE))//空格暂停
		{
			while (1)
			{
				Sleep(500);
				if (GetAsyncKeyState(VK_SPACE))
				{
					break;
				}
			}
		}
		else if (GetAsyncKeyState(VK_ESCAPE))//ESC退出
		{
			gameover();
			break;
		}
		else if (GetAsyncKeyState(VK_F1))//F1加速
		{
			Speedup();
		}
		else if (GetAsyncKeyState(VK_F2))//F2减速
		{
			Speeddown();
		}
		Sleep(sleeptime);

	}while(SnakeMoving());//Snakemoving是蛇的移动,蛇成功动了就返回1
	gotoxy(30, 37);
	system("pause");
}

int SnakeMoving()
{
	snake* nexthead=(snake*)malloc(sizeof(snake));//创建新的蛇头结点
	if (!nexthead)//创建失败
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	if (status == U)//如果接收的是↑
	{
		nexthead->x = head->x;//横坐标不变
		nexthead->y = head->y - 1;//纵坐标减1
		nexthead->next = head;//新的头存下旧的头的地址(链接)
		head = nexthead;
        //判断是否吃到食物,蛇的具体显示移动在下面的函数中
		JudgeEatFood(nexthead);//这里传nexthead安全点,因为已经用不到了
	}
	else if (status == D)
	{
		nexthead->x = head->x;
		nexthead->y = head->y + 1;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	else if (status == L)
	{
		nexthead->x = head->x - 2;
		nexthead->y = head->y;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	else if (status == R)
	{
		nexthead->x = head->x + 2;
		nexthead->y = head->y;
		nexthead->next = head;
		head = nexthead;
		JudgeEatFood(nexthead);
	}
	if (JudgeHitWall())//判断是否撞墙
	{
		gotoxy(20,37);
		printf("撞到墙了喔~\n");
		system("pause");
		gameover();
		return 0;
	}
	if (Hititself())//判断是否吃到自己
	{
		gotoxy(20, 37);
		printf("吃到自己了喔~\n");
		system("pause");
		gameover();
		return 0;
	}
	return 1;
}

//判断是否吃到了食物
void JudgeEatFood(snake*nexthead)
{
	if (nexthead->x == food.x && nexthead->y == food.y)//新的头碰到了食物
	{
		CreatFood();//创造下一个食物

		gotoprint(nexthead->x, nexthead->y);//画蛇头

		score += addition;//得分
		gotoxy(78, 8);
		printf("得分:%d  ", score);
		Speedup();//吃到食物后加速
		
	}
	else//如果没吃到食物
	{
		gotoprint(nexthead->x, nexthead->y);//画新的蛇头
		snake* p = nexthead;//用p来遍历蛇身

		while (p->next->next!=NULL)//将p指到蛇身的倒数第二个,即蛇尾的前一个
		{
			p = p->next;
		}

		gotoxy(p->next->x, p->next->y);//到原来蛇尾的位置
		printf("  ");//将原来蛇尾的位置填上两个空格,由于windows控制台长宽比,注意是两个空格

		free(p->next);//将原来蛇尾的指针free掉
		p->next = NULL;//指空,避免野指针
	}
}

//判断是否吃到自己

int Hititself()
{
	snake* p = head->next;//工具蛇登场
	while (p->next)//遍历蛇身,找有没有蛇头的坐标与蛇身相同的
	{
		if (p->x == head->x && p->y == head->y)
		{
			return 1;
		}
		p = p->next;
	}
	return 0;
}

//判断是否撞墙
int JudgeHitWall()
{
	if (head->x == 0 || head->x == COL || head->y == LINE || head->y == 0)
	{
		return 1;
	}
	return 0;
}

//snake加速
void Speedup()
{
	if (sleeptime > LOW_SLEEPTIME)
	{
		sleeptime -= TIME_SCOPE;
		addition++;//每个食物得分加1
		gotoxy(78, 14);
		printf("每个食物得分:%2d分", addition);
	}
}

//snake减速
void Speeddown()
{
	if (sleeptime < MAX_SLEEPTIME)
	{
		sleeptime += TIME_SCOPE;
		addition--;每个食物得分-1
		gotoxy(78, 14);
		printf("每个食物得分:%2d分", addition);
	}
}

//游戏结束画面
void gameover()
{
	system("cls");
	gotoxy(15, 10);
	printf("/**********************************************/");
	gotoxy(15, 20);
	printf("/**********************************************/");
	gotoxy(20, 14);
	printf("GOOD   GAME      o(≧口≦)o");
	gotoxy(22, 16);
	printf("Your Score is %d    GAME OVER!", score);
	gotoxy(20, 18);
	printf("还不错哦,     继续努力ヾ( ̄▽ ̄)Bye~Bye~");
}

//游戏说明
void explation()
{
	system("cls");
	gotoxy(30, 8);
	printf("1. 不能穿墙,不能咬到自己");
	gotoxy(30, 11);
	printf("2. 用↑.↓.←.→分别控制蛇的移动");
	gotoxy(30, 14);
	printf("3. F1 为加速,F2 为减速");
	gotoxy(30, 17);
	printf("4. 按空格键暂停游戏,再按空格键继续");
	gotoxy(30, 20);
	printf("5. ESC :退出游戏.space:暂停游戏");
	gotoxy(30, 23);
	printf("6,返回上一级请输入0\n");
}

//注册账号
void Register()
{
	Users a, b;//创建用户结构体临时变量a,b;a是用来接收用户输入的,b是用来从文件中读取的,来和a作比较

	char tmp[20] = "";用着下面判断的

	printf("欢迎来到注册界面!\n");

	FILE* pf = fopen("users.dat", "rb");//"rb"是以二进制只读方式打开文件的意思
	fread(&b, sizeof(Users), 1, pf);//将数据读入b中
	if (!pf)
	{
		printf("%s", strerror(errno));errno可以理解是报的错误对应的编号,strerror可以把这个编号翻译出来,输出在屏幕上
		return;
	}
	Sleep(1000);

	printf("请输入您的账号>>");
	scanf("%s", &a.id);

	while (1)
	{
		if (strcmp(a.id, b.id))//两个用户名不相等
		{
			if (!feof(pf))//没读到文件尾,继续读
		{
				fread(&b, sizeof(Users), 1, pf);
			}
			else//读到文件末尾了,确认没有重复的id
			{
				break;
			}
		}
		else//有两个用户名相等
		{
			printf("此用户名已存在在,请重新输入!\n");
			Sleep(1000);
			fclose(pf);关闭文件
			pf = NULL;pf置空,避免野指针
			return;
		}
	}
	printf("请输入您的姓名>>");
	scanf("%s", a.name);
	printf("请输入您的性别>>");
	scanf("%s", a.sex);
	printf("请输入您的电话号码>>");
	scanf("%s", a.phone);
	printf("请输入您的密码>>");
	Getpwd(a.pwd);输入密码的函数,有掩盖功能,下面会介绍
	printf("\n请再次确认您的密码>>");
	Getpwd(tmp);
	do
	{
		if (!strcmp(a.pwd, tmp))//两次密码相等
		{
			pf = fopen("users.dat", "ab");
			fwrite(&a, sizeof(Users), 1, pf);
			printf("\n账号注册成功,请登录!\n");
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return;
		}
		else
		{
			printf("\n两次密码不匹配!请重新输入>>");
			Getpwd(a.pwd);
			printf("\n请再次确认>>");
			Getpwd(tmp);
		}
	} while (1);
}


//登录
int Login()
{
	Users a, b;同理,a是用来给用户输入的,b是用来是水的(从文件中读取到b中,用b和a匹配比较)

	FILE* pf = fopen("users.dat", "rb");//以只读的方式打开文件
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	printf("欢迎来到登录界面!\n");
	Sleep(1000);

	fread(&b, sizeof(Users), 1, pf);

	printf("请输入账号>>");
	scanf("%s", a.id);

	while (1)
	{
		if (!strcmp(a.id, b.id))//在文件中找到了与用户输入相同的id
		{
			break;
		}
		else
		{
			if (!feof(pf))//没读到文件末尾,继续读取文件中的id到b中
			{
				fread(&b, sizeof(Users), 1, pf);
			}
			else//读到文件末尾了,没有找到与用户输入相同的账号
			{
				printf("此账号不存在!请重新输入!\n");
				Sleep(500);
				fclose(pf);
				pf = NULL;
				return 0;
			}
		}
	}

	do
	{
		printf("请输入密码>>");
		Getpwd(a.pwd);
		if (!strcmp(a.pwd, b.pwd))//输入的密码与文件中的相同
		{
			printf("\n登录成功!欢迎使用!\n");
			memcpy(&c, &b, sizeof(Users));//将用户的完整数据从b拷贝到c
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return 1;//登录成功返回1
		}
		else
		{
			printf("\n密码输入错误,请重新输入\n");
		}
	} while (strcmp(a.pwd, b.pwd));
	return 1;
}

//找回密码
void Reback()
{
	char tmp[20] = "";
	Users a, b;

	FILE* pf = fopen("users.dat", "rb+");
	if (!pf)
	{
		printf("%s", strerror(errno));
		return;
	}

	fread(&b, sizeof(Users), 1, pf);

	printf("请输入您的账号>>");
	Sleep(1000);
	scanf("%s", a.id);

	while (1)//在文件中找到与用户输入相同的id
	{
		if (!strcmp(a.id, b.id))如果读取到了相同的id(在文件中找到了和用户输入一样的)
		{
			break;
		}
		else
		{
			if (!feof(pf))没读到文件尾,继续读
			{
				fread(&b, sizeof(Users), 1, pf);
			}
			else
			{
				printf("您输入的账号不存在!请重新输入!\n");
				Sleep(500);
				fclose(pf);
				pf = NULL;
				break;
			}
		}
	}
	//下面是信息匹配验证
	do
	{
		printf("请输入您的姓名>>");
		scanf("%s", a.name);
		if (!strcmp(a.name, b.name))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.name, b.name));
	do
	{
		printf("请输入您的性别>>");
		scanf("%s", a.sex);
		if (!strcmp(a.sex, b.sex))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.sex, b.sex));
	do
	{
		printf("请输入您的电话号码>>");
		scanf("%s", a.phone);
		if (!strcmp(a.phone, b.phone))
		{
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (strcmp(a.phone, b.phone));

	printf("验证成功!请修改您的密码!\n");
	printf("请输入您的密码>>");
	Getpwd(a.pwd);
	printf("\n请再次确认您的密码>>");
	Getpwd(tmp);

	if (!pf)
	{
		printf("%s", strerror(errno));
		return;
	}

	do
	{
		if (!strcmp(a.pwd, tmp))//两次密码相等
		{
			fseek(pf, -((int)(sizeof(Users) - MAX_ID)), SEEK_CUR);//将文件流调回到要修改的密码的位置
			fprintf(pf, "%s", a.pwd);//覆盖原来的密码
			printf("\n密码修改成功,请登录!\n");
			Sleep(500);
			fclose(pf);
			pf = NULL;
			return;
		}
		else
		{
			printf("\n两次密码不匹配!请重新输入>>");
			Getpwd(a.pwd);
			printf("\n请再次确认>>");
			Getpwd(tmp);
		}
	} while (1);
}
//获取光标x坐标
int posx()
{
	CONSOLE_SCREEN_BUFFER_INFO ptr;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ptr);
	return (ptr.dwCursorPosition.X);
}
//获取光标y坐标
int posy()
{
	CONSOLE_SCREEN_BUFFER_INFO ptr;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ptr);
	return (ptr.dwCursorPosition.Y);
}
//输入密码
void Getpwd(char* pwd)
{
	int i = 0;
	int x, y;
	while (1)
	{
		pwd[i] = getch();//获取单个密码(字符)
		if (pwd[i] == VK_BACK && i >= 0)//如果输入的是回退键,VK_BACK是键盘的键值,ASCII码值为8
		{
			if (i > 0)//i>0说明有输入东西了,则回退前一格
			{
				i--;
				x = posx() - 1;//定位x并回退一格
				y = posy();//定位y
				gotoxy(x, y);//定位光标
				printf(" ");//将*用空格掩盖
				x = posx() - 1;//再次回退,下次输入时光标才会显示在正确的位置
				y = posy();//定位y
				gotoxy(x, y);
				continue;//然后跳过此次循环
			}
			else if (i == 0)//i==0说明没输入东西,直接跳过此次循环即可
			{
				continue;
			}
		}
		if (i >= 0 && pwd[i] != VK_RETURN && pwd[i] != VK_BACK)//输入东西了
		{
			x = posx();
			y = posy();
			gotoxy(x, y);
			printf("*");
		}
		if (i == 0 && pwd[i] == VK_RETURN)//如果没输入东西直接按回车,直接跳过此次循环,避免程序把回车当密码了
		{
			continue;
		}
		if (pwd[i] == VK_RETURN || i == MAX_PWD - 2)//输入回车了或者到上限了
		{
			i++;
			pwd[i] = '\0';//结尾放'\0'
			break;
		}
		i++;
	}
}

四.结语

✨感谢您的耐心阅读!!!!

✨看完了不妨点赞收藏吧~

✨您的支持就是博主创作的最大动力!!!!

✨再次感谢!!❤️

猜你喜欢

转载自blog.csdn.net/weixin_62244902/article/details/122256733