BIGOcean 发表于 2020-2-19 13:43

C语言编写贪吃蛇

本帖最后由 BIGOcean 于 2020-2-19 13:47 编辑

背景          :大一上半年,C程序设计老师布置了一个程序设计作业,于是诞生了这篇代码。
IDE         :VisualStudio2017社区版
字符集       :使用多字节字符集
辅助工具    :EasyX图形库2018春分版
PS:
   1、因为当时想得高分就以为搞点花里胡哨的就不错(哈哈最后混了个优,乐死我了),所以我编写了账号注册、登录系统、调用了多媒体接口以登录游戏时播放背景音乐、高分榜系统以及附带的升序降序排行函数:

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

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

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

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

      4、应程序设计老师要求,基本上能达到每个功能、每个函数甚至每一句都有一条注释。。。(想想都要吐了)
      5、第一次发帖还不太懂怎么排版只好先发出来看看效果了(总觉得漏了点啥东西),需要一些道德绑架:因为我算是初学,所以请大神包容浊眼之错,感谢噻!一嘻一嘻一嘻

https://static.52pojie.cn/static/image/hrline/4.gif

放代码吧:
#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;

char s1, s2;
//蛇的初始方向
char ch = UP;
//定义一个全局变量,来判断蛇是否成长,成长则速度加快。grow值也相应增加。
int grow = 0;



//食物的坐标
struct Food {
      int x;
      int y;
}food;

//蛇的信息:snake是蛇头,len为蛇的长度,speed蛇的速度
struct Snake {
      int x;
      int y;
      int len;
      int speed;
}snake;

//用于储存用户账号密码
typedef struct The_users
{
      char id; //账号
      char pwd;//密码
}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 = (frame_height) / 2;//初始化蛇头横坐标位置
      snake.y = (frame_width) / 2;//初始化蛇头纵坐标位置
      moveto(snake.y * SIZE, snake.x * SIZE);//画笔移动到蛇头坐标
      setfillcolor(GREEN);                        //定义蛇的初始颜色为绿色
      fillcircle(snake.y * SIZE + SIZE / 2, snake.x * SIZE + SIZE / 2, SIZE / 2);//画蛇
      snake.len = 3;                                                //初始化蛇的节数为3节
      snake.speed = 200;                                        //初始化蛇的速度
      for (k = 1; k < snake.len; k++)                //用for循环画出蛇剩余的节数
      {
                snake.x = snake.x + 1;
                snake.y = snake.y;
                moveto(snake.y * SIZE, snake.x * SIZE);
                setfillcolor(GREEN);
                fillcircle(snake.y * SIZE + SIZE / 2, snake.x * SIZE + SIZE / 2, SIZE / 2);
      }
}

//生成食物
void update_food()
{
      if (snake.x == food.x&&snake.y == 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 == food.x&&snake.y == 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 * SIZE, snake.x * SIZE);
                setfillcolor(BLACK);
                solidrectangle(snake.y * SIZE, snake.x * SIZE, snake.y * SIZE + SIZE, snake.x * SIZE + SIZE);
      }
      for (k = snake.len - 1; k > 0; k--)
      {
                snake.x = snake.x;
                snake.y = snake.y;
      }
      switch (ch)
      {
      case UP:snake.x--; break;
      case DOWN: snake.x++; break;
      case LEFT:snake.y--; break;
      case RIGHT:snake.y++; break;
      default:break;                                        //其他按键直接导致死亡
      }
      moveto(snake.y * SIZE, snake.x * SIZE);
      setfillcolor(GREEN);
      fillcircle(snake.y * SIZE + SIZE / 2, snake.x * SIZE + SIZE / 2, SIZE / 2);
      grow = 0;
}

//判断是否存活
int is_alive(void)
{
      if (snake.x == 0 || snake.x == frame_height - 1 || snake.y == frame_width - 1 || snake.y == 0)//判断是否撞墙
                return 0;                //死
      for (k = 1; k < snake.len; k++)
                if (snake.x == snake.x && snake.y == snake.y)//判断蛇头是否撞到蛇的身体
                        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;
      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);
                        printf("%d\n", y);
                }
                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 = m;
      }
      s = snake.len - 3;
      qsort(s, N, sizeof(s), compDec);
      fclose(fp);
      fopen_s(&fp, "scores.dat", "w");
      for (n = 0; n < N - 1; n++)
      {
                fprintf_s(fp, "%d\n", s);
      }
      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;
}

BIGOcean 发表于 2020-2-19 20:03

zhujianeng 发表于 2020-2-19 16:39
运行不了呀,我是新手,怎么搞。

你的错误代码没有截完整,所以我猜测是你没有添加两个用于存储账号密码以及分数的文件,或是你没有添加音乐且没有注释掉我添加音乐那部分的代码,两者都可导致“找不到xx”的错误

BIGOcean 发表于 2020-5-28 11:01

52leitong2016 发表于 2020-5-27 20:33
vc2010找不到#include               // easyx图形库头文件这个???怎么解决 ???

需要到easyx官网下载对应vs版本的easyx插件

还是小白azk 发表于 2020-2-19 16:34

请问这是c还是c++

zhujianeng 发表于 2020-2-19 16:39

运行不了呀,我是新手,怎么搞。

SuperKingCat 发表于 2020-2-19 19:31

感觉还不错,拿去试试

BIGOcean 发表于 2020-2-19 20:00

还是小白azk 发表于 2020-2-19 16:34
请问这是c还是c++

是c语言:lol

qianmo2001 发表于 2020-2-19 20:24

b不错不错

BIGOcean 发表于 2020-2-19 21:52

qianmo2001 发表于 2020-2-19 20:24
b不错不错

哈哈谢谢支持:lol

BIGOcean 发表于 2020-2-19 22:00

SuperKingCat 发表于 2020-2-19 19:31
感觉还不错,拿去试试

嗯嗯,有问题可以评论,我会第一时间回复:lol

BIGOcean 发表于 2020-2-19 22:00

cfc1998 发表于 2020-2-19 15:35
666666666

哈哈用的时候有问题可以评论,我会第一时间回复:lol
页: [1] 2 3
查看完整版本: C语言编写贪吃蛇