https://blog.csdn.net/qq_40953281/article/details/79315254
题目描述
设计一个类似贪吃蛇的游戏,并用C语言编写程序实现,该程序的功能参照贪吃蛇游戏,主要功能包括:
(1)蛇移动区域的设定,移动控制键的设置。
(2)食物的出现位置的随机性。
(3)蛇移动路径的自然性,蛇每遇到(吃)一个食物,蛇身会长长,直至布满整个移动区域。
(4)蛇头冲出设定的移动区域和蛇身体即为“失败”。
2.题目要求
(1)按照分析、设计、编码、调试和测试过程完成应用程序;
(2)学习并使用流程图等工具,并在撰写报告中使用;
(3)边框、食物、蛇头、蛇身的构成、蛇头的初始位置、蛇身的长度等信息可以自行安排。
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define up 'w'
#define down 's'
#define left 'a'
#define right 'd'
#define stop 'p'
char name[21]= {"user"}; //系统默认名称为user
typedef struct
{
char num[21];
char Name[21];
char garde[21];
} guys;//排行榜内容
guys noob[11];//排行榜中前十名的数据和当前玩家的数据
guys nownoob;//当前用户的信息
typedef struct
{
int num;
char Name[21];
int garde;
} midguy;//中间储存
typedef struct Snakes
{
int x;
int y;
struct Snakes *next;
} snake;//蛇坐标
snake *head,*tail;
struct Food
{
int x;
int y;
} food;//食物坐标
int score = 0;//分数
char click = 1;//是否按下键
int speed;//控制蛇的速度
void gotoxy(int x, int y)
{
COORD pos;
HANDLE handle;
pos.X = x;
pos.Y = y;
handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle, pos);
}//控制光标位置
void print(int x, int y)
{
gotoxy(x, y);
printf("■");
}//打印数据
void clearprint(int x, int y)
{
gotoxy(x, y);
printf(" ");
}//使用覆盖的方式删除
void creatfood()
{
srand((unsigned)time(NULL));//设置随机数
loop:
food.y = rand() % (25) + 1;
food.x = rand() % (54 - 1) + 2;
if (food.x % 2 != 0)
{
food.x++;
}
snake *bodys = head;
while (1) //遍历排除蛇身重复
{
if (bodys->next == NULL)
break;
if (food.x == bodys->x&&food.y == bodys->y)
{
goto loop;
}//如果食物生成在蛇的身上就重新生成一个食物。
bodys = bodys->next;
}
gotoxy(food.x, food.y);
printf("★");
}
int welcome()
{
printf("**************************************************************************************************************\n");
printf("**************************************************************************************************************\n");
printf(" 贪吃蛇\n");
printf("**************************************************************************************************************\n");
printf("**************************************************************************************************************\n");
printf("**************************************************************************************************************\n");
printf("**************************************************************************************************************\n");
printf(" 作者:计机国际张伟\n");
printf(" 学号: 201825110118\n");
printf("**************************************************************************************************************\n");
printf("**************************************************************************************************************\n");
printf("************************************************ 操作选项 *********************************************\n");
printf("************************************************ 1.输入用户名 *********************************************\n");
printf("************************************************ 2.排行榜 *********************************************\n");
printf("************************************************ 3.开始游戏 *********************************************\n");
printf("************************************************ 4.游戏说明 *********************************************\n");
printf("************************************************ 5.退出游戏 *********************************************\n");
printf("**************************************************************************************************************\n");
return 1 ;
}
int hitchat()//碰撞检查
{
if (head->x == 0|| head->x == 56 || head->y == 0 || head->y == 26)
{
welcome();
return 0;
}//如果碰到墙壁就返回;
snake *p = head->next;//从身体第二节开始遍历,判断 是否撞到自己
while (1)
{
if (p->next == NULL)
break;//没撞到
if (head->x == p->x&&head->y == p->y)
{
welcome();
return 0;//撞到
}//撞到自己
p = p->next;
}
return 1;
}
void changebody(int a,int b)//让蛇身体往头的方向移动一步
{
snake *p = head->next;
int temp1, temp2,_temp1,_temp2;
temp1 = p->x;
temp2 = p->y;
while (1)//遍历将身体往前挪一格
{
if (p->next->next == NULL)
break;
_temp1 = p->next->x;
_temp2 = p->next->y;
p->next->x = temp1;
p->next->y = temp2;
temp1 = _temp1;
temp2 = _temp2;
p = p->next;
}
p = head->next;
//if (p->next!= NULL)
{
p->x = a;
p->y = b;
}
}
void movebody()
{
int coutnumber = 0;
int a = head->x, b = head->y;
snake *p = head;
//通过先清空后打印实现动画效果
while (1)
{
if (p->next == NULL)
break;
clearprint(p->x, p->y);
coutnumber++;//用于计数,然后调整速度
p = p->next;
}
switch (click)
{
case up:
head->y -= 1;//改变头的位置
changebody(a,b);//改变身体的位置
break;
case down:
head->y += 1;
changebody(a,b);
break;
case left:
head->x -= 2;
changebody(a,b);
break;
case right:
head->x += 2;
changebody(a,b);
break;
case stop:
p=head;
while (1)//前面清空了蛇,这里进行打印
{
if (p->next == NULL)
break;
print(p->x, p->y);
p = p->next;
}
getch();//暂停以后按任意键开始;
break;
}
p = head;
while (1)//前面清空了蛇,这里进行打印
{
if (p->next == NULL)
break;
print(p->x, p->y);
p = p->next;
}
p = head;
gotoxy(0, 28);//控制光标不跟着蛇乱跑
if (coutnumber <= 10)
speed = 150;
else if (coutnumber > 10 && coutnumber <= 20)
speed = 100;
else if (coutnumber > 20 && coutnumber <= 40)
speed = 50;
else
speed = 10;
Sleep(speed);//控制蛇的速度
}
void eatfood()
{
if (head->x == food.x&&head->y == food.y)//如果吃到了食物
{
creatfood();
snake *newnod = (snake*)malloc(sizeof(snake));//蛇增加一节
snake *p;
p = head;
while (1)//将节点连上
{
if (p->next->next == NULL)//p的下一个节点是尾结点
break;
p = p->next;
}
p->next = newnod;
newnod->next = tail;//插入节点重合
score += 10;
gotoxy(77, 15);
printf("%d", score);//刷新分数
}
}
/*int finish()
{
system("cls");
char a='0';
printf(" 您真的要退出吗?\n");
printf(" 1.退出 2.我再想想\n");
scanf("%c",&a);
scanf("%c",&a);
while(a!='\n')
{
if(a=='1')
return 1;
else if(a=='2')
return 0;
else
{
system("cls");
printf(" 请规范输入\n");
printf(" 1.退出 2.我再想想\n");
}
scanf("%c",&a);
}
return 0;
}*/
int introduce()
{
char a='0';
system("cls");
printf(" 游戏说明\n");
printf("1.本游戏由作者独立完成,请勿盗版,搬运,如有bug,请联系qq2814248442.\n");
printf("2.操作方法:本游戏由WSAD,分别控制上下左右,按p暂停。(注意将输入法设置为英文,以免操作失误).\n");
printf("3.游戏规则:蛇可以在范围内不断运动,但不能碰到墙壁和自己,否则游戏结束;在游戏进行时,会不断生成食物,\n 蛇每吃到一个食物加一分,并且身体边长,直到填满范围,获得游戏的胜利.\n");
printf("4.该游戏可以使用个人用户名登录,最后成绩将被记录,并且成绩排名前十的玩家的名字将会出现在排行榜上,其中\n 每个玩家最好的成绩会覆盖掉以前的成绩,且排行榜上,每个玩家的名字只会出现一次,系统默认名字为user .\n");
printf(" 退出游戏说明,返回主菜单,请按1.\n");
while(1)
{
scanf("%c",&a);
scanf("%c",&a);
if(a=='1')
{
system("cls");
return 1;
}
else
{
printf(" 请规范输入\n");
printf(" 退出游戏说明,返回主菜单,请按1\n");
}
}
return 0;
}
int username()
{
char a='0';
while(1)
{
system("cls");
printf("请输入您的英文用户名(不超过20个字母,可以包含数字).");
scanf("%s",name);
printf("请检查您的用户名是否为%s\n",name);
printf("1.准确无误 2.输入错误\n");
while(1)
{
scanf("%c",&a);
scanf("%c",&a);//如果输入的是英文,将会陷入死循环,可以进行判断,如果输入的不是数字,就直接再次输入
if(a=='1')
{
system("cls");
return 1;
}
else if(a=='2')
{
break;
}
else
{
printf("请输入准确的选项\n");
}
}
}
return 0 ;
}
int lookrank()//查看排行榜
{
char a,temp;
FILE *fp;
fp=fopen("case.txt","r+");
if((temp=fgetc(fp))==EOF)
{
while(1)
{
system("cls");
printf("文件是空的\n");
printf("按1返回主页面\n");
scanf("%c",&a);
if(a=='1')
{
return 0;
}
}
}
else
{
system("cls");
printf("--------------------------------------------------------------------------------\n");
printf(" 排行榜 \n");
printf("--------------------------------------------------------------------------------\n");
}
a='0';
for(int i=0; i<10; i++)
{
printf("%c",temp);
while((temp=fgetc(fp))!=' ')
printf("%c",temp);
printf(" ");
while((temp=fgetc(fp))!=' ')
printf("%c",temp);
printf(" ");
while((temp=fgetc(fp))!='\n')
printf("%c",temp);
printf(" ");
printf("\n");
printf("--------------------------------------------------------------------------------\n");
}
lays:
printf("显示完成,按1返回主页面\n");
scanf("%c",&a);
scanf("%c",&a);
if(a=='1')
{
fclose(fp);
return 0;
}
else
{
printf("请规范输入:\n");
goto lays;
}
return 0;
}
int outputrank()//导出数据,便于修改。
{
FILE *fp;
fp=fopen("case.txt","r+");
for(int i=0; i<10; i++)
fscanf(fp,"%s %s %s",noob[i].num,noob[i].Name,noob[i].garde);
fclose(fp);
return 1;
}
void strmcpy(char*a,char*b)//复制数组将后者复制到前者
{
int j=0,i;
for(i=0; a[i]!='\0'; i++)
{
b[j++]=a[i];
}
b[j++]='\0';
}
void copynod(guys &b,guys &a)//复制节点,在冒泡排序中使用方便,将后者复制给前者
{
strmcpy(a.garde,b.garde);
strmcpy(a.Name,b.Name);
strmcpy(a.num,b.num);
}
int inputnownoob()//有待改进
{
//itoa(score,nownoob.garde,10);
snprintf(nownoob.garde,sizeof(nownoob.garde),"%d",score);//将分数转换为字符串
strmcpy(name,nownoob.Name);
printf("%s %s",name,nownoob.garde);
return 1;
}
//itoa(i, s, 10);
//snprintf(s, sizeof(s), "%d", i);
int updeaterank()//未检查************************************************************
{
//printf("s");
outputrank();
inputnownoob();//问题会不会是在导出的时候数据丢失X
//system("cls");
//lookrank();
getch();
guys tempnod;
int temp1,temp2;
int flag=0;
FILE *fp;
fp=fopen("case.txt","w+");
if(fp==NULL)
return 0;
//下面可以用冒泡排序进行排序,选出前十,可以先比较当前玩家,和排行榜中第10名,如果小于第10名,就可以直接不插入了
//将当前玩家的信息储存入noob[10]里面,再冒泡比较score,记得先将grade转换为数字,用atoi函数
copynod(noob[10],nownoob);
//printf("%s %s",noob[10].Name,noob[10].garde);
temp1=atoi(noob[9].garde);
temp2=atoi(noob[10].garde);
if(temp1>temp2)
flag=1;
for(int i=0;i<10&&flag==0;i++)
{
for(int j=0; j<10-i; j++)
{
temp1=atoi(noob[j].garde);
temp2=atoi(noob[j+1].garde);
if(temp1x = 14;
head->y = 9;
//gotoprint(head->x, head->y);
tail = (snake*)malloc(sizeof(snake));
snake *p = (snake*)malloc(sizeof(snake));
snake *q = (snake*)malloc(sizeof(snake));
p->x = 14;
p->y = 10;
q->x = 14;
q->y = 11;
head->next = p;
p->next = q;
q->next = tail;
//gotoprint(p->x, p->y);
//gotoprint(q->x, q->y);
tail->next = NULL;
tail->x = 10;
tail->y = 10;//将尾巴随意设置一个数,反正会刷新
}
int playgame()
{
welcome();
printarea();
creatfood();
if (checkinput() == 0)
return 0;
return 1;
}
int creategame()//可以利用函数返回值判断函数是否正常进行
{
while(1)
{
welcome();
char a;
scanf("%c",&a);
switch(a)
{
case '1' :
username();
break;
case '2' :
lookrank();//排行榜函数
break;
case '3' :
playgame();//开始游戏函数
break;
case '4' :
introduce();
break;
case '5' :
return 0;//提示函数
default:
system("cls");
}
}
return 0;
}
int main()
{
system("color 0A");
creategame();
/*char a[20],b[20];
scanf("%s",a);
strmcpy(a,b);
{
printf("%s",b);
}//测试函数是否有误*/
/*FILE *fp;
fp=fopen("case.txt","w+");
for(int i=0; i<10; i++)
{
itoa(i+1,noob[i].num,10);
fprintf(fp, "%s %s %s\n", noob[i].num, noob[i].Name,noob[i].garde);
}
fclose(fp);
lookrank();//测试fprintf*/
/*int num;
char name[5];
FILE *fp;
if ((fp = fopen("t.txt", "w+")) == NULL)
printf("cannot open file!\n");
scanf("%d %s",&num,name);
fprintf(fp, "%s", name);
fclose(fp);
if ((fp = fopen("tt.txt", "w+")) == NULL)
printf("cannot open file!\n");
fwrite(&num, sizeof(int), 1, fp);
fwrite(name, sizeof(char), 5, fp);
fclose(fp);//测试fprintf*/
/*
FILE *fp;
fp=fopen("case.txt","r+");
for(int i=0; i<10; i++)
fscanf(fp,"%s %s %s",noob[i].num,noob[i].Name,noob[i].garde);
fclose(fp);
for(int i=0; i<10; i++)
printf("%s %s %s\n",noob[i].num,noob[i].Name,noob[i].garde);*/
/*guys apple,ap;
scanf("%s %s %s",apple.garde,apple.Name,apple.num);
copynod(ap,apple);
printf("%s %s %s",ap.garde,ap.Name,ap.num);
printf("%s %s %s",apple.garde,apple.Name,apple.num);//测试copynod*/
return 0;
}
1.界面设计
界面一般是由主菜单组成,然后主菜单包含开始游戏,游戏介绍,退出游戏,排行榜,输入用户名等。最为简单的方法就是print出来,然后调用system函数改变颜色。
2.贪吃蛇游戏设计
(1)打印问题
最开始我是打算直接print出蛇和游戏界面,蛇每移动一次就调用system函数进行清屏,然后再打印一次,但过于麻烦。然后想着能不能就改变指定位置的内容,于是上网学习中发现可以通过句柄使用SetConsoleCursorPosition函数进行定向输入,又清屏转变为覆盖。注意边框打印使用了中文字符,而中文字符宽度是英文字符宽度的两倍,所以横向打印时一次跳两格。
(2)贪吃蛇的构成
由于贪吃蛇每吃一个食物就会变长,所以用了链表,而链表元素包括了横纵坐标,和指向下一节身体的指针。
(3)贪吃蛇移动问题
贪吃蛇在没有按键的情况下是直接向原本方向前进的,每一个结点的改变都是一样的,这个没有什么难度,但是如果左转或者右转以后,身体就会改变方向,在拐点之前的结点是一种改变方式,在拐点之后的结点又是另外一种改变方式,这个就有些麻烦了。所以,我打算在贪吃蛇设立一个头结点,不存储信息,首元结点开始存储信息,移动的时候,可以从前往后将当前结点的x,y坐标存入temp中,然后再将下一结点的x,y坐标存入temp_中,进行覆盖,最后再回过头来,将新的x,y坐标替换掉首元结点的原始x,y。新的x,y坐标是在原来的首元结点x,y的基础进行改变的。如果按了↑,y-1;如果按了↓,y+1;如果按了←,x-1;如果按了→,x+1;打印的时候,先将原本的蛇全部打印“ ”以清除,然后再重新打印。
(4)贪吃蛇食物生成问题
贪吃蛇食物生成可以用随机数生成,首先由于贪吃蛇边框设立在了x(0-25),y(0-54)之间,所以随机数要设两个,分别随机x和y,然后范围在x,y的取值范围之间就可以了。然后遍历一次蛇,判断食物是否生成在了蛇身体上,如果生成在了蛇身体上就重新生成。
(5)碰撞检测问题
如果贪吃蛇碰到了自己的身体,或者是边界的话游戏就结束了,所以在每次移动身体都需要进行碰撞检测,可以在移动前判断,也可以在移动后判断,因为电脑运算速度快,人眼看不出差别,于是本程序中采用了比较方便的移动后检测,将首元结点的x,y坐标和围墙还有自己的身体进行比较,判断是否有一样的。若碰撞了,则进入显示分数页面,按任意键返回主页面。
(6)贪吃蛇变长问题
先检查贪吃蛇的首元结点的x,y和食物的x,y是否相等,相等的话就进行边长操作,否则就不变。贪吃蛇吃了食物应该要再生成一个食物而且蛇的身体要边长,所以遍历一次在尾结点之前加上一个结点,再将尾结点接上去。此外,还设立了一个score用来存储分数,每吃一个分数加10。当分数达到100和200的时候都会进行一次提速(改变sleep函数的参数实现)。
(7)贪吃蛇反应问题
由于电脑计算速度极快,如果不控制速度的话,一瞬间蛇就会撞死,于是我又在网上学习中发现有一个函数可以使用,就是sleep函数,与sleep函数一起出现的还有wait函数,但是sleep是指定休眠时间到了时间后继续往下执行,而wait是等待,需要被触发。而贪吃蛇向前移动身体的时候,我们是不需要按的,所以这里选择了sleep函数。
3.排行榜设计
本游戏设立了排行榜,将包括了排名前十的用户名和用户分数,及编号。排行榜最开始默认用户名为0,分数为0,采用了txt文件进行存储。因此设立了包含分数,排名,用户名的结构体guys,为了导入导出方便,暂时全部都用字符串进行存储。在游戏中采用了nownoob这个guys结构体存储当前的用户信息。在游戏结束后,将score利用snprintf函数转变为char类型存入nownoob中(最开始是使用itoa函数的,但过了一段时间就用不了了,网上给出的解释是itoa是最早的形式,被废弃了,在msdn中查不到了,还有的地方说不安全,于是我就替换成snprintf了)。
接着利用长度为11的结构体数组noob,存储导出的排行榜数据,将newnoob数据作为noob[10]存入,然后自己写了strmcpy(复制char类型函数)和copynod(复制节点函数)然后利用冒泡排序,通过比较每个节点的score(先转换为数字)大小,进行排序,然后将排序结果的前10导入文件中存储起来。
用于复制的函数
进行比较大小的函数
这篇博客是本人第一篇博客,如有哪里实现不了或者有什么不小心侵权抄袭部分,请与本人联系,请大家多多包涵