前言:这款贪吃蛇是用链表创建的,涵盖了链表的头插法新结点生成、遍历链表、链表的动态内存分配,且有完整的登录注册账号密码的界面,完整的菜单界面,最高分记录,用户信息储存,菜单跳转等等
这个版本的贪吃蛇核心代码仅大约550行,简单易懂,有图文讲解,有350行代码是登录系统的
实现效果
目录
一.具体需求&实现思路
二.各部分功能实现
1.创建文件储存数据
2.头文件、函数声明、结构体、宏常量
3.光标定位
4.登录系统
5.主函数&菜单
6.游戏开始前获取最高分信息
7.初始化地图
8.初始化蛇
9.创建食物
10.获取键盘信号
11.反应蛇的走向
12.判断蛇是否吃到食物&打印蛇的移动轨迹
13.判断是否吃到自己&判断是否撞墙
14.蛇的加速和减速
15.游戏结束画面&游戏说明
三.完整代码
四.结语
首先是结构体,这个游戏中涵盖了四个对象,分别是玩家、最高分得者、蛇、食物,所以要创建四个结构体。
登录系统中涵盖了登录、注册、找回密码、输入密码掩盖等功能。
游戏开始前,要初始化一些游戏数据、初始化地图、初始化蛇、创建食物。
游戏过程中,要时刻监听(获取)键盘输入信号,获取信号后蛇就可以移动,蛇移动后要判断是 吃到食物、是否撞到墙、是否吃到自己。
游戏结束后,要有结束画面、保存最高分信息。
综合以上考虑,有了以下大概框架。
接着,我们就可以准备下前期工作,声明函数、头文件,创建需要的结构体,定义宏常量。
这里说一下为什么要定义宏常量,宏常量在前面定义完后,函数功能实现就可以用,而且最重要的是,在后面需要对代码的一些参数做修改时,可以直接修改宏常量的数值,不用在代码里到处改,会方便很多。
上代码!!!
需要在.c文件的同一个目录下创建两个文件,一个叫users.dat,用于储存用户数据;另一个叫maxscore.dat,用于储存最高分信息。.dat后缀是我随便起的,无所谓,帅就行了~
像我这样就可以了,一定要和自己创建的.c文件同个目录下!!!
//引用头文件
#include
#include
#include
#include //用来反应错误警告的,后面会讲
#include
#include
//定义宏常量
#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();
我们这里面光标会到处跑来跑去打印东西,所以需要一个移动定位光标的函数
//移动光标
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
登录系统可以看博主之前发的一篇关于登录系统的详细讲解的文章,这里不过多讲解(有详细的注释),直接上代码
登录系统
//注册账号
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++;
}
}
//主菜单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的判断依据,这样不会因为输入了字符串或者其它东西而引起死循环,具体情况大家可以去试一试,最后在每一次输入前最好都清空一下输入缓冲区,避免上一次输入的东西没有完全输入进字符,留在输入缓冲区对下一次输入造成影响。
//获取最高分玩家
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");
}
GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数,如果键盘上按下了这个按键,就返回1。
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;
}
如果蛇吃到食物,我们可以通过将食物直接画成蛇头,这样蛇就向前移动也变长一节。
如果没吃到食物,就在旧的蛇头前画上新的蛇头,然后把蛇尾用两个空格填了(为什么是两个空格?因为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;//指空,避免野指针
}
}
//判断是否吃到自己
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");
}
//引用头文件
#include
#include
#include
#include //用来反应错误警告的,后面会讲
#include
#include
//定义宏常量
#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++;
}
}
✨感谢您的耐心阅读!!!!
✨看完了不妨点赞收藏吧~
✨您的支持就是博主创作的最大动力!!!!
✨再次感谢!!❤️