贪吃蛇的C语言实现的傻瓜教程(包含详细解释)

贪吃蛇的C语言实现(包含详细解释)

  • 参考文章
  • 关于贪吃蛇C语言的要求介绍
    • 程序预览
    • 完整代码(包含部分测试代码已注释)
    • 设计思路与部分优化思路
    • 最后总结

参考文章

https://blog.csdn.net/qq_40953281/article/details/79315254

关于贪吃蛇C语言的要求介绍

题目描述
设计一个类似贪吃蛇的游戏,并用C语言编写程序实现,该程序的功能参照贪吃蛇游戏,主要功能包括:
(1)蛇移动区域的设定,移动控制键的设置。
(2)食物的出现位置的随机性。
(3)蛇移动路径的自然性,蛇每遇到(吃)一个食物,蛇身会长长,直至布满整个移动区域。
(4)蛇头冲出设定的移动区域和蛇身体即为“失败”。
2.题目要求
(1)按照分析、设计、编码、调试和测试过程完成应用程序;
(2)学习并使用流程图等工具,并在撰写报告中使用;
(3)边框、食物、蛇头、蛇身的构成、蛇头的初始位置、蛇身的长度等信息可以自行安排。

程序预览

贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第1张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第2张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第3张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第4张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第5张图片

完整代码(包含部分测试代码已注释)

#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函数进行定向输入,又清屏转变为覆盖。注意边框打印使用了中文字符,而中文字符宽度是英文字符宽度的两倍,所以横向打印时一次跳两格。

贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第6张图片

(2)贪吃蛇的构成
由于贪吃蛇每吃一个食物就会变长,所以用了链表,而链表元素包括了横纵坐标,和指向下一节身体的指针。

(3)贪吃蛇移动问题
贪吃蛇在没有按键的情况下是直接向原本方向前进的,每一个结点的改变都是一样的,这个没有什么难度,但是如果左转或者右转以后,身体就会改变方向,在拐点之前的结点是一种改变方式,在拐点之后的结点又是另外一种改变方式,这个就有些麻烦了。所以,我打算在贪吃蛇设立一个头结点,不存储信息,首元结点开始存储信息,移动的时候,可以从前往后将当前结点的x,y坐标存入temp中,然后再将下一结点的x,y坐标存入temp_中,进行覆盖,最后再回过头来,将新的x,y坐标替换掉首元结点的原始x,y。新的x,y坐标是在原来的首元结点x,y的基础进行改变的。如果按了↑,y-1;如果按了↓,y+1;如果按了←,x-1;如果按了→,x+1;打印的时候,先将原本的蛇全部打印“ ”以清除,然后再重新打印。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第7张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第8张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第9张图片
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第10张图片
(4)贪吃蛇食物生成问题
贪吃蛇食物生成可以用随机数生成,首先由于贪吃蛇边框设立在了x(0-25),y(0-54)之间,所以随机数要设两个,分别随机x和y,然后范围在x,y的取值范围之间就可以了。然后遍历一次蛇,判断食物是否生成在了蛇身体上,如果生成在了蛇身体上就重新生成。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第11张图片
(5)碰撞检测问题
如果贪吃蛇碰到了自己的身体,或者是边界的话游戏就结束了,所以在每次移动身体都需要进行碰撞检测,可以在移动前判断,也可以在移动后判断,因为电脑运算速度快,人眼看不出差别,于是本程序中采用了比较方便的移动后检测,将首元结点的x,y坐标和围墙还有自己的身体进行比较,判断是否有一样的。若碰撞了,则进入显示分数页面,按任意键返回主页面。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第12张图片
(6)贪吃蛇变长问题
先检查贪吃蛇的首元结点的x,y和食物的x,y是否相等,相等的话就进行边长操作,否则就不变。贪吃蛇吃了食物应该要再生成一个食物而且蛇的身体要边长,所以遍历一次在尾结点之前加上一个结点,再将尾结点接上去。此外,还设立了一个score用来存储分数,每吃一个分数加10。当分数达到100和200的时候都会进行一次提速(改变sleep函数的参数实现)。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第13张图片
(7)贪吃蛇反应问题
由于电脑计算速度极快,如果不控制速度的话,一瞬间蛇就会撞死,于是我又在网上学习中发现有一个函数可以使用,就是sleep函数,与sleep函数一起出现的还有wait函数,但是sleep是指定休眠时间到了时间后继续往下执行,而wait是等待,需要被触发。而贪吃蛇向前移动身体的时候,我们是不需要按的,所以这里选择了sleep函数。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第14张图片
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导入文件中存储起来。
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第15张图片
用于复制的函数
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第16张图片
进行比较大小的函数
贪吃蛇的C语言实现的傻瓜教程(包含详细解释)_第17张图片

最后总结

这篇博客是本人第一篇博客,如有哪里实现不了或者有什么不小心侵权抄袭部分,请与本人联系,请大家多多包涵

你可能感兴趣的:(贪吃蛇的C语言实现的傻瓜教程(包含详细解释))