Tea9源码网 - 免费分享精品源码、教程、软件|全网干货资源收集,免费下载。

C语言编写贪吃蛇小游戏

发布:资源收集工2021-4-11 22:51分类: 技术教程 标签: C语言 编程技巧

编写环境

IDE           :VisualStudio2017社区版
字符集       :使用多字节字符集
辅助工具    :EasyX图形库2018春分版

PS

①账号注册、登录系统、高分榜系统的数据全部分别保存在默认添加创建的Users.dat,scores.dat里,所以要借鉴的朋友需要手动在IDE里添加这两个文件(对应名称可以自己改,但一定要对应到代码里,否则会fp指针调用文件出错);


②关于背景音乐:我添加的背景音乐的资源就不放到帖子上了(名字叫silver city,好听,哈哈哈),所以要借鉴源码的朋友需要自己了解一下“如何添加MP3格式的音乐到代码”的问题,然后对代码第328行进行改写;


1、操作问题:因为我喜欢玩FPS游戏,所以定义的上下左右是wsad(小写,一定要把输入法切换为小写英文状态,否则蛇死亡),所以不习惯的朋友可以重新定义为键盘方向键(需要了解一下键盘虚拟键值,然后改对应的switch语句,代码量应该还是挺大的)。


2、EasyX图形库:(这段是复制我设计报告的23333333)在我决定用EasyX库的图形函数来绘制地图之前,对于这个模块的编写有很多问题:譬如单用gotoxy()函数实现绘制地图的话,一片黑白,惨不忍睹;而且也会成几何倍的增加代码量:尤其是for循环语句和printf语句会多不胜数。从而就会导致不仅开发人员降低对于代码的可读性,而且从头至尾的黑白界面无疑会劝退很多玩家,降低可玩性。在我用EasyX来绘制地图后,测试的过程只能用两个字来形容:真香!EasyX的一个moveto()配合一个for循环代替了多不胜数的gotoxy()函数和for循环。这不仅大大缩减了代码量,而且图形编程的优点“多彩化”也得以体现。在调试过程中我对for循环进行了优化:一个进行边界的上下绘制,另一个负责左右的绘制,实现了不仅减少了代码量而且没有破坏不影响可读性的前提。

代码如下:

#include <stdio.h>                        //C语言标准输入输出头文件
#include <graphics.h>                // easyx图形库头文件
#include <conio.h>                        //获取数据头文件(getch()和kbhit())
#include <windows.h>                //使用其中的Sleep函数控制循环的时间,system("cls")函数来清屏
#include <stdlib.h>                        //调用了srand()随机函数
#include <time.h>                        //使用当前时钟做随机种子
#include <mmsystem.h>                //多媒体设备接口头文件
#pragma comment (lib,"winmm.lib")
 
//首先进行一堆宏定义
 
//定义地图大小
#define frame_height 30 
#define frame_width 30
//定义操作键,小写的wasd控制上左下右
#define UP 'w' 
#define DOWN 's'
#define LEFT 'a'
#define RIGHT 'd'
//控制台的点是单位点,不考虑大小,我们用的easyx绘制像素点为了将单位点在图形界面上放大为一个正方形,系数转换×16可以把一个单位点放大成正方形。16可以是任意数,相应的会放大或缩小所有的像素点。
#define SIZE 16
 
#define N 4
 
//声明fp是指针,用来指向FILE类型的对象。
FILE *fp;
 
int i, j, k, m, n, s[N];
 
char s1[4], s2[4];
//蛇的初始方向
char ch = UP;
//定义一个全局变量,来判断蛇是否成长,成长则速度加快。grow值也相应增加。
int grow = 0;
 
 
 
//食物的坐标
struct Food {
        int x;
        int y;
}food;
 
//蛇的信息:snake[0]是蛇头,len为蛇的长度,speed蛇的速度
struct Snake {
        int x[50];
        int y[50];
        int len;
        int speed;
}snake;
 
//用于储存用户账号密码
typedef struct The_users
{
        char id[11]; //账号
        char pwd[20];//密码
}users;
users a, b;//新建结构体成员变量,用来登陆与注册
 
 
//此处声明变量
 
void init_map(void);
void update_food(void);
void move_snake(void);
int is_alive(void);
void get_speed(void);
void gotoxy(int x, int y);
void redraw_map(void);
void menu();
void registers();
void Login();
//void Create_File();
void score();
int compInc(const void *a, const void *b);
int compDec(const void *a, const void *b);
 
 
//主函数位置
int main()
{
        initgraph(640, 480);        //创建绘图窗口,长640.
        menu();                                        //调用菜单函数
        score();                                //调用得分函数
        return 0;
}
 
 
//再次绘制地图(根据宏定义中的宽和高)
void redraw_map(void)
{
        for (j = 0; j < frame_width; j++)                        //该for循环用于绘制地图的上下边界,根据width宽来从左往右、先上后下依次绘制。
        {
                moveto(j*SIZE, 0);                                                //将画笔移动到(j*SIZE,0)的位置开始进行下面的绘图,另外(j*SIZE)是将单位点放大SIZE(16)倍
                setfillcolor(BLUE);                                                //定义上边界为蓝色
                fillrectangle(j*SIZE, 0, j*SIZE + SIZE, SIZE);        //绘制矩形
                moveto(j*SIZE, (frame_height - 1)*SIZE);//再次移动画笔
                setfillcolor(BLUE);                                                //定义下边界为蓝色
                fillrectangle(j*SIZE, (frame_height - 1)*SIZE, j*SIZE + SIZE, (frame_height - 1)*SIZE + SIZE);//绘制矩形
        }
        for (i = 1; i < frame_height - 1; i++)//次循环绘制地图左右剩余的28个矩形方块,先左后右,从上至下!
        {
                moveto(0, i*SIZE);                                //画笔移动到(0,i*SIZE)
                setfillcolor(BLUE);                                //定义左边界为蓝色
                fillrectangle(0, i*SIZE, 0 + SIZE, i*SIZE + SIZE);
                moveto((frame_width - 1)*SIZE, i*SIZE);//再次移动画笔
                setfillcolor(BLUE);                                        //定义右边界为蓝色
                fillrectangle((frame_width - 1)*SIZE, i*SIZE, (frame_width - 1)*SIZE + SIZE, i*SIZE + SIZE); //绘制矩形
        }
}
 
//然后初始化地图内容
void init_map(void)
{
        //初始化食物
        srand((unsigned int)time(NULL));//用srand函数集结合time.h头文件中的time来生成随机数
        food.x = rand() % (frame_height - 2) + 1;//在1~(frameheight-2)之间随机生成一个食物的横坐标
        food.y = rand() % (frame_width - 2) + 1;//在1~(framewidth -2)之间随机生成一个食物的纵坐标
        moveto(food.y*SIZE, food.x*SIZE);                //将画笔移动到该随机坐标
        setfillcolor(RED);                                                //定义食物为红色
        fillcircle(food.y *SIZE + SIZE / 2, food.x*SIZE + SIZE / 2, SIZE / 2);                //定义食物形状为以(food.y *SIZE + SIZE / 2, food.x*SIZE + SIZE / 2)为圆心,SIZE/2为半径的圆形
 
        //初始化蛇
        snake.x[0] = (frame_height) / 2;//初始化蛇头横坐标位置
        snake.y[0] = (frame_width) / 2;//初始化蛇头纵坐标位置
        moveto(snake.y[0] * SIZE, snake.x[0] * SIZE);//画笔移动到蛇头坐标
        setfillcolor(GREEN);                        //定义蛇的初始颜色为绿色
        fillcircle(snake.y[0] * SIZE + SIZE / 2, snake.x[0] * SIZE + SIZE / 2, SIZE / 2);//画蛇
        snake.len = 3;                                                //初始化蛇的节数为3节
        snake.speed = 200;                                        //初始化蛇的速度
        for (k = 1; k < snake.len; k++)                //用for循环画出蛇剩余的节数
        {
                snake.x[k] = snake.x[k - 1] + 1;
                snake.y[k] = snake.y[k - 1];
                moveto(snake.y[k] * SIZE, snake.x[k] * SIZE);
                setfillcolor(GREEN);
                fillcircle(snake.y[k] * SIZE + SIZE / 2, snake.x[k] * SIZE + SIZE / 2, SIZE / 2);
        }
}
 
//生成食物
void update_food()
{
        if (snake.x[0] == food.x&&snake.y[0] == food.y)//首先保证每次随机的食物不能刚好随机到蛇头所在坐标
        {
                food.x = rand() % (frame_height - 2) + 1;
                food.y = rand() % (frame_width - 2) + 1;
                for (k = 1; k < snake.len; k++)
                {
                        if (snake.x[k] == food.x&&snake.y[k] == food.y)//判断蛇头坐标等于随机食物的坐标 即:吃到食物
                        {
                                food.x = rand() % (frame_height - 2) + 1;//再次随机生成食物
                                food.y = rand() % (frame_width - 2) + 1;
                        }
                }
                moveto(food.y*SIZE, food.x*SIZE);//移动画笔到随机食物的坐标
                setfillcolor(RED);                                //定义食物颜色为红色
                fillcircle(food.y*SIZE + SIZE / 2, food.x*SIZE + SIZE / 2, SIZE / 2);                //定义食物为圆形
                snake.len++;                                        //蛇的长度加一
                grow = 1;                                                //给全局变量grow赋值1
        }
}
 
//蛇的移动
void move_snake()
{
        if (_kbhit())//kbhit判断键盘输入的虚拟键值
                ch = _getch();//将方向键值表现为对应的方向
        if (!grow)//此时grow值若为零则执行if语句,不为零则不执行
        {
                moveto(snake.y[snake.len - 1] * SIZE, snake.x[snake.len - 1] * SIZE);
                setfillcolor(BLACK);
                solidrectangle(snake.y[snake.len - 1] * SIZE, snake.x[snake.len - 1] * SIZE, snake.y[snake.len - 1] * SIZE + SIZE, snake.x[snake.len - 1] * SIZE + SIZE);
        }
        for (k = snake.len - 1; k > 0; k--)
        {
                snake.x[k] = snake.x[k - 1];
                snake.y[k] = snake.y[k - 1];
        }
        switch (ch)
        {
        case UP:  snake.x[0]--; break;
        case DOWN: snake.x[0]++; break;
        case LEFT:  snake.y[0]--; break;
        case RIGHT:  snake.y[0]++; break;
        default:  break;                                        //其他按键直接导致死亡
        }
        moveto(snake.y[0] * SIZE, snake.x[0] * SIZE);
        setfillcolor(GREEN);
        fillcircle(snake.y[0] * SIZE + SIZE / 2, snake.x[0] * SIZE + SIZE / 2, SIZE / 2);
        grow = 0;
}
 
//判断是否存活
int is_alive(void)
{
        if (snake.x[0] == 0 || snake.x[0] == frame_height - 1 || snake.y[0] == frame_width - 1 || snake.y[0] == 0)//判断是否撞墙
                return 0;                //死
        for (k = 1; k < snake.len; k++)
                if (snake.x[k] == snake.x[0] && snake.y[k] == snake.y[0])//判断蛇头是否撞到蛇的身体
                        return 0;        //死
        return 1;                        //活
}
 
//通过节数来提升速度
void get_speed(void)
{
        if (snake.len <= 6)
                snake.speed = 200;
        else if (snake.len <= 10)
                snake.speed = 100;
        else if (snake.len <= 20)
                snake.speed = 50;
        else if (snake.len <= 30)
                snake.speed = 30;
        else snake.speed = 20;
}
 
//移动光标
void gotoxy(int x, int y)
{
        HANDLE hout;
        COORD cor;
        hout = GetStdHandle(STD_OUTPUT_HANDLE);
        cor.X = y;
        cor.Y = x;
        SetConsoleCursorPosition(hout, cor);
}
 
//进入界面
void menu()
{
        InputBox(s1, 4, "输入数字以选择目的:\n1.开始游戏\n2.高分榜\n3.退出游戏\n游戏说明:wasd控制上左下右(小写)", "疯狂的蛇——海制作", NULL, 0, 0, false);
        int x, y[3];
        sscanf_s(s1, "%d", &x);//将用户输入转化为数字
        if (x == 1)
        {
                InputBox(s2, 4, "输入数字以选择目的:\n1.注册账号\n2.登陆游戏", "疯狂的蛇——海制作");
                sscanf_s(s2, "%d", &x);
                if (x == 1) {
                        registers();
                        Login();
                }
                else if (x == 2) {
                        Login();
                }
                init_map(); //初始化地图
                while (1)
                {
                        update_food();
                        get_speed();
                        move_snake();
                        redraw_map();
                        Sleep(snake.speed);
                        if (!(is_alive()))
                                break;
                }
                closegraph();                                                //关闭游戏窗口
                printf("哈哈小辣鸡,游戏结束!\n");
                printf("你的得分:%d", snake.len - 3);//得分=(蛇死亡前节数len)-(蛇的初始节数3)
                _getch();
        }
        else if (x == 2)
        {
                closegraph();                                                //关闭游戏窗口
                printf("                    排行榜\n");
                fopen_s(&fp, "scores.dat", "r");
                for (x = 0; x < N - 1; x++) {
                        fscanf_s(fp, "%d", &y[x]);
                        printf("%d\n", y[x]);
                }
                fclose(fp);
                _getch();
 
        }
        else//输入3或其他除了1、2任意键均退出游戏
        {
                closegraph();
        }
}
 
//注册系统
void registers()
{
        fopen_s(&fp, "Users.dat", "r");
        fscanf_s(fp, "%s%s", b.id, sizeof(b.id), b.pwd, sizeof(b.pwd));
        InputBox(a.id, 11, "请输入你的账号:", "疯狂的蛇——海制作");
        while (1)
        {
                if (strcmp(a.id, b.id) != 0)//如果两串字符串不相等
                {
                        if (!feof(fp))//如果未至文件末尾,它的工作原理是,站在光标所在位置,向后看看还有没有字符。如果有,返回0;如果没有,返回非0。它并不会读取相关信息,只是查看光标后是否还有内容。
                        {
                                fscanf_s(fp, "%s%s", b.id, sizeof(b.id), b.pwd, sizeof(b.pwd));
                        }
                        else break;
                }
                else
                {
                        outtextxy(220, 200, "此用户名已被注册");
                        fclose(fp);
                        _getch();
                        exit(0);
                }
        }
        fclose(fp);
        InputBox(a.pwd, 10, "请输入你的密码:", "疯狂的蛇——海制作");
        fopen_s(&fp, "Users.dat", "a");
        fprintf_s(fp, "%s %s\n", a.id, a.pwd);
        outtextxy(220, 200, "奥利给!恭喜你!账号注册成功!");
        fclose(fp);
}
 
//登陆账号密码,登录时播放bgm嘿嘿sao起来
void Login()
{
        mciSendString("open SC.mp3 alias BIGOcean", 0, 0, 0);
        mciSendString("play BIGOcean repeat", 0, 0, 0);
        fopen_s(&fp, "Users.dat", "r");
        fscanf_s(fp, "%s%s", b.id, sizeof(b.id), b.pwd, sizeof(b.pwd));
        InputBox(a.id, 11, "请输入账号", "疯狂的蛇——海制作");
        while (1)
        {
                if (strcmp(a.id, b.id) == 0) break;//如果找到了这个用户名
                else
                {
                        if (!feof(fp))//如果文件未读完
                                fscanf_s(fp, "%s%s", b.id, sizeof(b.id), b.pwd, sizeof(b.pwd));
                        else
                        {
                                outtextxy(220, 200, "此用户名不存在!");
                                fclose(fp);
                                _getch();
                                exit(0);
                        }
                }
        }
        InputBox(a.pwd, 20, "请输入密码", "疯狂的蛇——海制作");
        if (strcmp(a.pwd, b.pwd) == 0)//如果密码匹配
        {
                fclose(fp);
                outtextxy(250, 200, "登陆成功!奥利给!");
                initgraph(640, 480);
        }
        else
        {
                outtextxy(220, 200, "STFU!密码不正确");
                _getch();
                exit(0);
        }
}
 
//分数系统
void score()
{
        fopen_s(&fp, "scores.dat", "r");
        for (n = 0; n < N - 1; n++)
        {
                fscanf_s(fp, "%d", &m);
                s[n] = m;
        }
        s[N - 1] = snake.len - 3;
        qsort(s, N, sizeof(s[0]), compDec);
        fclose(fp);
        fopen_s(&fp, "scores.dat", "w");
        for (n = 0; n < N - 1; n++)
        {
                fprintf_s(fp, "%d\n", s[n]);
        }
        fclose(fp);
}
 
//分数的排序
//升序排序
int compInc(const void *a, const void *b)
{
        return *(int *)a - *(int *)b;
}
//降序排序
int compDec(const void *a, const void *b) {
        return *(int *)b - *(int *)a;
}
温馨提示如有转载或引用以上内容之必要,敬请将本文链接作为出处标注,谢谢合作!

已有 3183 人阅读