深圳幻海软件技术有限公司 欢迎您!

C语言实现的贪吃蛇(无EasyX,详解)

2023-06-26

💦前言或许厌倦了枯燥的做题,那就学学贪吃蛇,激发你的学习乐趣吧~你将进一步加深对结构体,单链表,函数,循环等基础的理解。希望对你有所帮助~纯C实现的贪吃蛇🐍💦前言🍎代码效果--视频🍑学习新函数,让你的代码变得"高大上"~🍑任意位置输出💡重点注意事项💡使用说明🍑颜色的设置💡使用说明

💦前言

或许厌倦了枯燥的做题,那就学学贪吃蛇,激发你的学习乐趣吧~
你将进一步加深对结构体,单链表,函数,循环等基础的理解。
希望对你有所帮助~

纯C实现的贪吃蛇🐍

  • 💦前言
  • 🍎代码效果--视频
  • 🍑学习新函数,让你的代码变得"高大上"~
    • 🍑任意位置输出
      • 💡重点注意事项
      • 💡使用说明
    • 🍑颜色的设置
    • 💡使用说明
      • 💡颜色
    • 🍑获取字符的新函数
  • 贪吃蛇🐍
    • 🌴游戏思路
    • 🌱蛇的创建
      • 🌲先来一个结构体
      • 🌴创建蛇头
      • 🌱创建蛇身
      • 🌲蛇身初始化
      • 🌴展示蛇身
    • 🍔食物、障碍物的创建
    • 🚲蛇的移动 一
      • 🍳步长设置
      • 🛴移动
        • 📜改动蛇头的创建与移动搭配
    • 🍿吃食物
      • 🧁添加身体
        • 💧对蛇的方向做出的改动
        • 💧对`AllocSnakeBody`的修改
    • 🛫蛇的移动 二
    • 🍓开始考虑整体
    • 🍎又局部---按键设计
      • 🍉按键的细节事项
        • 🍉细节一
        • 🍉细节二
        • 🍉细节三
          • 🔥不匹配的情况有哪些?
        • 🍉细节四
        • 🍉细节五
        • 🍉细节六
  • 🌱判断
    • 🌱判断什么
      • 🌱判断赢
      • 🌱判断吃到食物
      • 🌱判断撞墙
      • 🌱判断撞到自身/障碍物
      • 💦结束设置
    • 💥又一细节
  • 💤在说一些要注意的东西
    • 💦多设置宏
    • 💦声明
    • 💖获取自动的时间设置以及清屏操作
  • 🎑完整代码
    • ✨snake.h
    • ✨Creat.cpp
    • ✨main.cpp
    • ✨Paint.cpp
    • ✨SnakeGame.cpp

🍎代码效果–视频

纯c语言实现的贪吃蛇小游戏

🍑学习新函数,让你的代码变得"高大上"~

提前说明必须是要.cpp后缀文件才可以使用,.c文件不支持。
因为这里的头文件中包含了c++的内容。

🍑任意位置输出

暂且学会如何使用,深入的理解对于现在的我来说还差了些。所以我只会讲如何使用。

#include<windows.h>
#include<iostream>
  • 1
  • 2

以下代码粘贴使用即可

坐标说明

💡重点注意事项

要注意中文字符、图形,在控制台显示的时候x和y的比例使2:1! 比如:打印 长是2,宽是1。

void Pos(int x, int y)
{
COORD pos;
HANDLE hOutput;
pos.X = x;//水平方向
pos.Y = y;//垂直方向
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取缓冲区中的数据,地址赋值给句柄
SetConsoleCursorPosition(hOutput, pos);
/*定位光标位置的函数,坐标为GetStdHandle()返回标准的输出的句柄,
也就是获得输出屏幕缓冲区的句柄,并赋值给对象 pos*/

/*隐藏光标操作 */
CONSOLE_CURSOR_INFO cursor;
cursor.bVisible = FALSE;
cursor.dwSize = sizeof(cursor);
SetConsoleCursorInfo(hOutput, &cursor);
}
int main()
{

Pos(2,2);
printf("hello world");
return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

💡使用说明

先给定你想要输出的位置,在去打印。


更多知识百度一下哦

🍑颜色的设置

void test()
{
HANDLE consolehwnd;//创建句柄
consolehwnd = GetStdHandle(STD_OUTPUT_HANDLE);//实例化句柄,从缓冲区中获取数据
SetConsoleTextAttribute(consolehwnd, FOREGROUND_INTENSITY | FOREGROUND_RED/*字体颜色*/);//设置字体颜色
printf("hello");
SetConsoleTextAttribute(consolehwnd, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
printf("world!\n");
getchar();
SetConsoleTextAttribute(consolehwnd, BACKGROUND_INTENSITY | BACKGROUND_BLUE/*背景颜色*/);

printf("It is really beautiful!\n");
}
int main()
{
test();
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

💡使用说明

运行效果


💡颜色

颜色那部分可以使用16进制或者10进制的数代替。
看下面的图片选取你想要的颜色

16进制表示的时候,前面一个数字代表字体背景的颜色,后面一个数字代表字体的颜色。比如0x0a意思是0:字体的背景颜色是黑色,a:字体的颜色是绿色。


百度一下获取更多知识哦

🍑获取字符的新函数

头文件

#include<conio.h>
  • 1

getch()不需要按回车,直接获取你在键盘上输入的字符。
kbhit()功能及返回值: 检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。

如果直接使用报错的话,请添加下面的一句话

#pragma warning(disable: 4996) 作用:屏蔽4996报错

贪吃蛇🐍

在三子棋和五子棋的博客中有介绍到头文件,在关键字 一中详细介绍了多文件,以及头文件。

所以这里我就不在赘述了。
多文件的使用可以使函数归类,看起来更舒服一点。

🌴游戏思路

个人思路仅供参考

自己写的时候先把游戏框架打出来,在具体到内部的实现。

一开始的游戏效果–>游戏界面的设置–>蛇的创建–>食物的创建–>障碍物的创建–>游戏实现。

我的游戏思路:①局部–②整体–③局部。

①先将细小的“动作”(移动,吃食物)写出来–②程序运行过程,函数位置+按键操作–③按键操作,判断(输赢比较),运行调试修修改改。

一开始的游戏效果和游戏界面的设置我就不讲了,大家各有所爱,凭空想向即可。

还有一句要说的,多分装成函数。
根据功能分装,找错的时候,会很方便

🌱蛇的创建

我知道的两种形式,一种是二维数组,一种是单链表方式。
这里介绍的是单链表的方式。

不怎么会的可以看下这篇博客单链表的基本操作

Snake* sHead;//全局变量
Snake* sEnd;//全局变量
void CreatSnake()
{
sHead = AllocSnakeHead();
sEnd = sHead;
int i = 3;
while (i--)
{
InitSnake(&sEnd);
SnakeLenth++;
}
ShowSnake(sHead);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

🌲先来一个结构体

typedef struct Snake 
{
int x; //x,y 蛇的位置
int y;
const char* head;
const char* body;
struct Snake* next; //链接蛇身(单链表的操作)
}Snake;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

那些图形符号的类型是 const char*[3],类型需要匹配,所以需要加const

🌴创建蛇头

这个地方有点讲究,你设计的巧妙的话你后面会省事很多。暂且先不讲,时机未到。

这里的蛇头就是作头节点

还需要提到的就是坐标要设置成偶数,控制台长和宽比例是2:1

不然你会出现这种情况,对应的食物的坐标也是如此。

Snake* AllocSnakeHead()
{
Snake* p = (Snake*)malloc(sizeof(Snake));//蛇头
p->x = rand() % Diff_x + Dif_x;
p->y = rand() % Diff_y + Dif_y;
while (p->x % 2)//x的坐标要为偶数
{
p->x = rand() % Diff_x + Dif_x;
}
p->head = "⊙";
p->next = NULL;
return p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

rand() % Diff_x + Dif_xrand()应该不陌生吧,创建随机数用的,需要一个随机种子srand((unsigned int )time(NULL)),Diff_x , Dif_x这两个是在头文件中,通过宏定义来表示一个具体的数字

🌱创建蛇身

蛇身的创建,其实就是链表的创建。
你可采用头插入,也可以采用尾插入。

尾插入创建的时候,需要记录尾指针的指向(利于对蛇身初始化,后续操作中用尾指针会更方便,但我自己写的时候忘记了)因此需要传地址,所以需要二级指针去接收。

要和蛇头连上,一开始尾指针要指向蛇头

void InitSnake(Snake** sEnd)
{
Snake* p = (Snake*)AllocSnakeBody((*sEnd)->x, (*sEnd)->y);
(*sEnd)->next = p;//sHead->next = p
*sEnd = p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

🌲蛇身初始化

Snake* AllocSnakeBody(int x, int y)
{
Snake* p = (Snake*)malloc(sizeof(Snake));
p->x = x - 2;
p->y = y;
p->body = "●";
p->next = NULL;
return p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

🌴展示蛇身

本质上就是打印链表。

void ShowSnake(Snake* sHead)
{
Snake* p = sHead;
int flag = 1;
while (p)
{
gotoxy(p->x, p->y);//在哪个位置输出
if (flag)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x03);//颜色设置
printf("%s", p->head);
flag = 0;
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);
printf("%s", p->body);
}
p = p->next;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

🍔食物、障碍物的创建

创建的思路和蛇创建的过程一样,这里就不在赘述了。

🚲蛇的移动 一

先不考虑按键的问题。

🍳步长设置

步长是多少?还记得说比例吗?2:1,在设置的时候,x方向以2为单位,y方向以1为单位去设置。

蛇头是在偶数位置,每次在x方向上移动步长是偶数的,食物位置在设置的时候也是设成偶数的,那吃食物的时候,就不会出现吃到半个的情况了。

🛴移动

蛇在动的时候,只要蛇头动。

创建新蛇头给上一个新位置的坐标,不就完成了蛇头动吗?
蛇身+旧蛇头的坐标不动,但是长度又不能改变因此最后一个蛇的身体,需要被释放掉。

蛇头只能存在一个,那旧蛇头转化为身体就可以喽

刚刚的疑惑是时候解开了。
刚刚不是写了蛇头的创建吗?可不可以再次使用呢?

当然可以,移动的时候涉及到坐标,那我们是不是需要改动一下函数参数呢?必须要。

📜改动蛇头的创建与移动搭配

conut是全局变量,标记第一次使用和第二次以上使用。

//原有的调用需要改动
int conut = 0;//全局变量
Snake* AllocSnakeHead(int x, int y)
{
Snake* p = (Snake*)malloc(sizeof(Snake));//蛇头

//第一次
if (!conut)
{
p->x = rand() % Diff_x + Dif_x;
p->y = rand() % Diff_y + Dif_y;
while (p->x % 2)//x的坐标要为偶数
{
p->x = rand() % Diff_x + Dif_x;
}
conut = 1;
}
else//移动后的坐标
{
p->x = x;
p->y = y;
}
p->head = "⊙";
p->next = NULL;
return p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

删除最后一个蛇尾,本质是释放的过程。

但是原有的位置上还会残留之前的“印记”(蛇尾),还需要一个清理的工作,即将释放的尾巴处清空也就是打印空格

这里Move的形参x,y接收的是具体按某个键移动后的位置(坐标)
DeleBody的的形参x,y是被释放的蛇尾坐标

//清理尾巴
 void DeleBody(int x, int y)
 {
 gotoxy(x, y);
 printf("  ");
 }
//移动过程的实现
 void Move(int x,int y)
 {
 Snake* p = AllocSnakeHead(x, y);
 //新的头
 Snake* t = sHead;
 Snake* t1 = t->next;
 //记录旧的头

 t->body = "●";
 //原来的头变为身体

 p->next = t;
 //链接
 sHead = p;
 //新的头赋值给sHead

 //去掉最后一个节点
 while (t1->next)
 {
 t = t->next;
 t1 = t1->next;
 }
 DeleBody(t1->x, t1->y);
 t->next = NULL;
 free(t1);
 p = NULL;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

🍿吃食物

吃完食物,蛇需要增长。 吃到食物你也可以打印一些东西,比如分数、一些文字之类的。

吃完食物我们不能不管了,还需要创建食物,你也可以增加点难度,添加障碍物的设置。

蛇的移动是靠打印显示出来的,这里的“吃”,本质上是覆盖,蛇头覆盖食物。

添加身体这部分,我写的比较复杂。

优化思路:尾插法。在设计尾指针时,用的是全局变量,所以尾指针创建完之后是指向尾巴的(即指向最后一个节点)。(使用尾指针可以把我这遍历最后一个节点的过程省略掉)

如果采用头插入的话,有些不方便。改动的东西有点多。每个蛇身的坐标都要改动,太复杂。

void AddBody()
{
//也可以使用尾插入,会更简单。
Snake* p = sHead;
//找到最后的一个节点
while (p->next)
{
p = p->next;
}
Snake* t = AllocSnakeBody(p->x, p->y);
p->next = t;
}
void EatFoodSet()
{
//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。
AddBody(); //移动身体
CreatFood();//产生新的食物
play.score++;
switch (play.score)
{
case 2:
case 4:
case 6:
case 8:
case 10:
case 12:
case 14:
CreatObstacle();
default:
break;
}
gotoxy(68, 13);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x05);
printf("得分:  %d ( ̄︶ ̄) ", play.score);
SnakeLenth++;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

🧁添加身体

Add()函数中添加身体真能样写吗?我把AllocSnakeBody()函数放在这里,仔细考虑一下

Snake* AllocSnakeBody(int x, int y)
{
Snake* p = (Snake*)malloc(sizeof(Snake));
p->x = x - 2;
p->y = y;
p->body = "●";
p->next = NULL;
return p;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里的是在刚刚创建的时候,这样做的。如果蛇运动起来那这样还是欠妥,为什么这么说呢看图。

如果食物在红色圆圈那,吃到食物后向x-2的地方去添加,那么墙会被新的蛇身覆盖掉因此不能这么简单的设置。
怎么考虑更全面呢?
我是这样考虑的,既然每面墙都要考虑到,我们不妨划分四个区域。
需要注意的是,添加身体的时候,是在蛇尾部分。

所以下面图中,红色圈圈表示蛇尾那箭头是什么呢?我们还要把蛇运动的方向考虑进来,在边界上,蛇朝不同的方向运动添加的情况也是不同的。
箭头即表示蛇运动的方向

当蛇尾在左上角那个圈的时候,如果蛇是向右移动,那么在蛇尾添加新身体的坐标是x不变,y+1,如果蛇是向下移动,那么,蛇尾添加新身体的坐标是y不变,x+2
其他情况类似。

在创建地图的时候就把中间值可以用宏来代替。
蛇的方向,要在哪体现呢,AllocSnakeBody() 如何改动才更好呢?

💧对蛇的方向做出的改动

typedef struct Snake 
{
int x; //x,y 蛇的位置
int y;
const char* head;
const char* body;
struct Snake* next; //链接蛇身(单链表的操作)
int direction;
}Snake;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
void CreatSnake()
{
sHead = AllocSnakeHead(0, 0);
sEnd = sHead;
int i = 3;
while (i--)
{
InitSnake(&sEnd);
SnakeLenth++;
}
//蛇的随机方向
sHead->direction = rand() % 4 + 1;
while (sHead->direction == LEFT)
{
sHead->direction = rand() % 4 + 1;
}
ShowSnake(sHead);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

💧对AllocSnakeBody的修改

通过参数z来选择执行哪种添加,传(1/0)

Snake* AllocSnakeBody(int x, int y,int z)
{
Snake* p = (Snake*)malloc(sizeof(Snake));
if (z)
{
p->x = x - 2;
p->y = y;
}
else
{
Add(p ,x, y);
}
p->body = "●";
p->next = NULL;
return p;
}
void Add(Snake* p,int x, int y)
{
if (x < Mid_x && y > Mid_y)
{
if (sHead->direction == UP)
{
p->x = x + 2;
p->y = y;
}
if (sHead->direction == RIGHT)
{
p->x = x;
p->y = y - 1;
}

}
else if (x < Mid_x && y < Mid_y)
{
if (sHead->direction == DOWN)
{
p->x = x + 2 ;
p->y = y;
}
if (sHead->direction == RIGHT)
{
p->x = x;
p->y = y + 1;
}
}
else if (x > Mid_x && y < Mid_y)
{
if (sHead->direction == DOWN)
{
p->x = x - 2;
p->y = y;
}
if (sHead->direction == LEFT)
{
p->x = x;
p->y = y + 1;
}
}
else if (x < Mid_x && y < Mid_y)
{
if (sHead->direction == UP)
{
p->x = x - 2;
p->y = y;
}
if (sHead->direction == LEFT)
{
p->x = x;
p->y = y - 1;
}
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

🛫蛇的移动 二

这里是考虑按键后的移动。

设计的时候需要考虑到,蛇移动是不能往回走的。
我们知道移动是上下左右,设计的时候肯定会想到用数字去表示。

这里就能体现枚举的好处了
用枚举代替数字会更好。
你也可以选择用宏

enum MoveKey
{
LEFT = 1,
DOWN,
RIGHT,
UP,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

LEFT初始值赋值为1,那后面的值是依次以1递增的。值分别为1,2,3,4

移动之后,需要去打印,才能看到效果

 void SnakeMove(int x)
 {
 //不可往相反的方向走
 switch (x)
 {
 case UP:
 if (x != DOWN)
 {
 Move(sHead->x,sHead->y - 1);
 ShowSnake(sHead);
 }
 break;
 case DOWN:
 if (x != UP)
 {
 Move(sHead->x, sHead->y + 1);
 ShowSnake(sHead);
 }
 break;
 case LEFT:
 if (x != RIGHT)
 {
 Move(sHead->x - 2, sHead->y);
 ShowSnake(sHead);
 }
 break;
 case RIGHT:
 if (x != LEFT)
 {
 Move(sHead->x + 2 , sHead->y);
 ShowSnake(sHead);
 }
 break;
 }
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

🍓开始考虑整体

整个程序跑起来后,主要活动是靠按键,进行方向控制,设计的时候需要把握住这里。
移动不可能只移动一次,肯定需要用循环

GetKeyboard()是按键操作的函数,通过这个函数的返回值,判断是继续,还是退出程序。同时也是通过这个函数,对蛇进行移动和操作。

GetKeyboard() 它的返回值看你如何设计,使它结束循环

不能忘记随机种子,把它在main函数的文件中。放在其他文件话,随机值可能不太妙

void Game()
{
srand((unsigned int)time(NULL));//随机种子
GameWelcome();//游戏欢迎界面
GameDescription();//游戏说明
while (1)
{
DrawGameInterface();//绘制游戏运行界面
if ( GetKeyboard())//按键,进去游戏
{
break;
}
}
}
int main()
{
Game();
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

🍎又局部—按键设计

这里就需要用上一开头上面讲的getch()kbhit()

下面那些数字75 80 72 70对应是键盘上的方向键(–>)这些键

真的如下所示吗?

int GetKeyboard()
{
while (1)
{
key = getch();
switch (key)
{
case 'a':
case 'A':
case 75:
if (key1 != RIGHT)
key = LEFT;
SnakeMove(key);
break;
case 's':
case 'S':
case 80:
if (key1 != UP)
key = DOWN;
SnakeMove(key);
break;
case 'd':
case 'D':
case 77:
if (key1 != LEFT)
key = RIGHT;
SnakeMove(key);
break;
case 'w':
case 'W':
case 72:
if (key1 != DOWN)
key = UP;
SnakeMove(key);
break;
default:
   break;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

🍉按键的细节事项

上面的代码看似通顺,跑起来的话还有很多问题的。

🍉细节一

蛇开始运动一开始是要运动的。

在创建蛇的时候是不是定了一个方向吗,这个时候可以派上用场了。

可以解释当初为何要这样设置了
具体代码如下

sHead->direction = rand() % 4 + 1;
while (sHead->direction == LEFT)
{
sHead->direction = rand() % 4 + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5

这里的设置,需要看你实现的蛇头和蛇身的位置关系。
如果蛇头是在右边,蛇身是在左边,那么一开始自动走的时候,这个方向不能是左边,不然真的是落地成盒。

你也可以一开始把方向固定死了,同样也不要注意不能落地成盒。

🍉细节二

开篇有介绍到 kbhit()函数,这里没有用合理嘛?
有人可能会疑惑,只要按键不就行了嘛,为什么还要kbhit()呢?

假设没有kbhit()去检测是否按键。那代码实现出来的效果是蛇的移动靠按键来完成。也就是说,只有按键了蛇移动,不按键蛇不移动,这和我们预期效果不符合。

我们要的效果是蛇自动走。按键只用来控制蛇方向(速度),蛇的移动是靠代码完成的。因此kbhit()是必须要用的。

这样的话,必须把kbhit()的返回值用起来(kbhit()的返回值是检测你有无按键,按键了返回非0的值,没按键返回0)怎么用?肯定是通过函数的返回值进行,条件判断。

🍉细节三

完成了上面所说的还不行哦。
是不是还需要考虑按键失败的情况呢?

仅仅需要判断按键是否合法,这样肯定还是不行。
为什么呢?

每次按键的话,(key=getch()) key的值都会刷新,去switch中进行匹配,如果按的键不是我们所需要的,尽管default出去了,但蛇是不会动的,那又如何继续呢?

为什么呢?因为key的值刷新了! 已经找不到原来的方向了!
所以我们需要新增一个变量key1记录前一次合法情况下运动的方向
什么时候使用key1呢?按键不匹配的时候都要用上。
同时也不能忘记更新蛇的方向

🔥不匹配的情况有哪些?

①按了和当前运动方向相反的键,比如蛇往上走,你按了向下的键

②按了其他键,也包括加速,减速,正常速的这些键。

🍉细节四

速度的设置

我们需要知道,这点代码对于电脑来说,太小了,一下就可以运行完。上面那样写,速度贼快,根本毫无游戏体验。
我们需要增加停屏来控制速度

#include<windows.h>
Sleep(250);
  • 1
  • 2

里面数字的单位是毫秒

🍉细节五

蛇的移动函数放在哪?
上面有提到通过kbhit()的返回值来判断是否按键。
我们需要的效果是自动走。

什么情况下会自动走,我们没按键的情况下会自动走。
这不就和 细节 一中说的对应上了吗。蛇一开始随机向一个方向运动,通过kbhit()函数检测是否按键,没有按键,蛇运动方向不变,继续运动。

如果蛇开局不自动走,只有我们按了第一个方向键后蛇才会开始移动。
在明确一个点,就是按键是改变蛇的方向

🍉细节六

你可能会问能不能在switch也中放SnakeMove(key),按照我写的这个逻辑,这样写会导致:你连续按某一个方向键时,会加速有加速效果。

int key = 0;//当前方向的值
int key1 = 0; //只记录前一次蛇移动按键
int j = 0;//加速标记
int i = 0; //减速标记
int o = 1; //正常速度标记
int GetKeyboard()
{
key = sHead->direction;
while (1)
{
//int b = kbhit();调试的时候检测用
sHead->direction = key;
if (kbhit())
{    //左右需要移动2个单位移动,上下1个单位移动
if (key == UP || key == DOWN || key == LEFT || key == RIGHT)
key1 = key;
key = getch();
switch (key)
{
case 'a':
case 'A':
case 75:
if (key1 != RIGHT)
key = LEFT;
else
key = key1;
break;
case 's':
case 'S':
case 80:
if (key1 != UP)
key = DOWN;
else
key = key1;
break;
case 'd':
case 'D':
case 77:
if (key1 != LEFT)
key = RIGHT;
else
key = key1;
break;
case 'w':
case 'W':
case 72:
if (key1 != DOWN)
key = UP;
else
key = key1;
break;
case 'j':
case 'J':
j = 1;//加速标记
i = 0; //减速标记
o = 0; //正常速度标记
key = key1;
break;
case 'I':
case 'i':
j = 0;//加速标记
i = 1; //减速标记
o = 0; //正常速度标记
key = key1;
break;
case 'o':
case 'O':
j = 0;//加速标记
i = 0; //减速标记
o = 1; //正常速度标记
key = key1;
break;

default:
key = key1;
break;
}
}
     else//自动走的设置
{
SnakeMove(key);
if (j)
Sleep(150);
if (i)
Sleep(250);
if (o)
Sleep(200);
}
if (int ret = Judge())//判断是否吃到食物/是否死亡/赢
{
//返回值为真结束游戏 --- 死亡
return ret;
}
}
return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

🌱判断

什么时候判断呢?
当然是走一步判断一步,因此我们只要在移动后加上判断即可

局部代码

     else//自动走的设置
{
SnakeMove(key);
if (j)
Sleep(150);
if (i)
Sleep(250);
if (o)
Sleep(200);
}
if (int ret = Judge())//判断是否吃到食物/是否死亡/赢
{
//返回值为真结束游戏 --- 死亡
return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
 int Judge()
 {
 //判断输赢
 if (play.score == Win)
 return JudgeWin();
 //判断是否撞墙
 if (sHead->x == Map_x_left + 1 || sHead->x == Map_x_left ||
 sHead->x == Map_x_right - 1 || sHead->x == Map_x_right ||
 sHead->y == Map_y_up || sHead->y == Map_y_down)
 return JudgeHitWall();
 //判断是否迟到食物,吃到食物  将食物变为头,原来的头变为身体 -->Move函数
 if (sHead->x == food->x && sHead->y == food->y )
  return JudgeFood();

 //判断是否撞到自己
 if (JudgeHitSelf())
 {
 ;
 }
 else//是否撞到障碍物
return JudgeObstacle();

 return 0;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

🌱判断什么

🌱判断赢

看你怎么设置,你可以根据得分去判断,也可以根据蛇的长度去判断

int JudgeWin()
{
gotoxy(68, 16);
//每一个输出对应位置要等长
printf("游戏胜利      (●'-'●)       ");
gotoxy(68, 18);
printf("成功获得蛇王的称号      ");
gotoxy(68, 20);
printf("按Esc退出,按L继续");
win = 1;
return FinishSet();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

🌱判断吃到食物

void AddBody()
{
Snake* p = sHead;
//找到最后的一个节点
while (p->next)
{
p = p->next;
}
Snake* t = AllocSnakeBody(p->x, p->y,0);
p->next = t;
}

void EatFoodSet()
{
//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。

AddBody(); //移动身体
CreatFood();//产生新的食物
play.score++;
switch (play.score)
{
case 2:
case 4:
case 6:
case 8:
case 10:
case 12:
case 14:
CreatObstacle();//障碍物
default:
break;
}
gotoxy(68, 13);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x05);
printf("得分:  %d ( ̄︶ ̄) ", play.score);
SnakeLenth++;
}
int JudgeFood()
{
EatFoodSet();//吃到食物设置
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

🌱判断撞墙

int JudgeHitWall()
{
return  FinishSet();
}
  • 1
  • 2
  • 3
  • 4

🌱判断撞到自身/障碍物

两者实现的时候差不多,遍历链表 + 判断

撞自身的时候,是蛇头撞身体,所以需要从第一个节点开始判断

int JudgeHitSelf()
{
Snake* p = sHead->next;
while (p)
{
if (sHead->x == p->x && sHead->y == p->y)
return  FinishSet();
p = p->next;
}
return 0;
}
int JudgeObstacle()
{
Obstacle* p = obstacle;
while (p)
{
if (sHead->x == p->x && sHead->y == p->y)
return  FinishSet();
p = p->next;
}
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

💦结束设置

FinishSet()这些代码重复出现分装成函数。
结束设置,要考虑结束游戏,或者重新开始。
重新开始的话,一些全局变量的值需要去改变,置为0
结束的话,很简单。

ObstacleConut是全局变量,障碍物的个数。

int FinishSet()
 {
if (ObstacleConut > 0)
{
Obstacle* p = obstacle->next;
free(p);
obstacle->next = NULL;
}
switch (win)
{
case 0:
gotoxy(68, 16);
printf("游戏结束,继续加油 `(*>﹏<*)′");
gotoxy(68, 18);
printf("==>按Esc退出,按L继续<==");
gotoxy(68, 20);
printf("                  ");
break;
case 1:
break;
}
int b = getch();
 while (b)
 {
 switch (b)
 {
 case 27:
 system("cls");
 return 1;
 case 'l':
 case 'L':
 system("cls");
 conut = 0; //蛇头创建处
 win = 0;//输赢打印
 play.score = 0;
 return 2;
 default:
 break;
 }
 b = getch();
 }
 return 0;
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

💥又一细节

最后呢,需要提醒的是游戏重新开始的时候是要回到Game()函数中,通过循环去重新开始。尽量不要在GetKeyboard()中去执行DrawGameInterface()函数,执行的话会有点点问题。

void Game()
{
srand((unsigned int)time(NULL));//随机种子
GameWelcome();//游戏欢迎界面
GameDescription();//游戏说明
while (1)
{
DrawGameInterface();//绘制游戏运行界面
if ( GetKeyboard() )//按键,进去游戏
{
break;
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

说到这,整个游戏的思路也就分析完了。

💤在说一些要注意的东西

💦多设置宏

在绘图的时候,数字用宏,这样写起来方便。
随机数设置也可以写成宏的形式

💦声明

如果你分装成多个文件的话,全局变量,以及函数都要申明一下。

💖获取自动的时间设置以及清屏操作

直接使用即可。

菜鸟教程更多记载

#include <stdio.h>
#include <time.h>
 
int main ()
{
   time_t rawtime;
   struct tm *info;
   char buffer[80];
 
   time( &rawtime );
 
   info = localtime( &rawtime );
 
   strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
   printf("格式化的日期 & 时间 : |%s|\n", buffer );
  
   return(0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

清屏

#include<windows.h>
system("cls");
  • 1
  • 2

🎑完整代码

✨snake.h

#pragma once
#include<stdio.h>
#include<windows.h>
#include<ctime>
#include<string.h>
#include<conio.h>//getch()函数的头文件
#include<iostream> //输入、输出流的头文件
#include<time.h>
#include<stdlib.h>

//屏蔽 4996 报错
#pragma warning(disable: 4996)

enum MoveKey
{
LEFT = 1,
DOWN,
RIGHT,
UP,
};

//蛇的属性
typedef struct Snake 
{
int x; //x,y 蛇的位置
int y;
const char* head;
const char* body;
struct Snake* next; //链接蛇身(单链表的操作)
int direction;
}Snake;

//食物
typedef struct Food
{
int x; //x,y 食物的位置
int y;
const char* head;
}Food;

//障碍物属性 随着分数的增加而增加
typedef struct Obstacle
{
int x; //x,y 障碍物的位置
int y;
const char* body;
struct  Obstacle* next;   //5
}Obstacle;

typedef struct Player
{
int score;
}Player;

//坠落星星的下标参数
typedef struct Star
{
int x;
int y;
}Star;


//全局变量
extern int j;//加速标记
extern int i; //减速标记
extern int o; //正常速度标记

extern int win;//输赢的判断
extern Player play;//玩家属性
extern int SnakeLenth;//蛇长度
extern Snake* sHead;//蛇头指针
extern Snake* sEnd;//蛇尾指针创建的时候用
extern Food* food;//食物
extern Obstacle* obstacle;//障碍物
extern int conut; //AllocSnakeHead函数中的全局变量 添加身体所用
extern int ObstacleConut;//障碍物个数
extern int flagob;
// 界面大小设置
// 地图  
#define Map_x_left 2
#define Map_x_right 52
#define Mid_x 25
#define Map_y_up 2
#define Map_y_down 29
#define Mid_y 12

//获胜个数
#define Win  20

//随机值的产生
//蛇
#define Diff_x    (Map_x_right - Map_x_left - 4*2 )//头+身 -- 4
#define Dif_x     (4*2 + Map_x_left)   
#define Diff_y    (Map_y_down  - Map_y_up - 1)
#define Dif_y     (Map_y_up + 1)
//食物
#define Foodd_x   (Map_x_right - Map_x_left - 3)//4-50-->0-46-->47
#define Food_x    4
#define Foodd_y   (Map_y_down - Map_y_up - 2)
#define Food_y    4


// 欢迎/说明 界面
#define Infa_x_left  38  //20
#define Infa_x_right 78//60
#define Infa_y_up 2
#define Infa_y_down 23
//玩家得分界面
#define Play_x_left 60
#define Play_x_right 98
#define Play_y_up 9
#define Play_y_down 22

//函数声明

//从指定位置输出
extern void gotoxy(int x, int y);
//刚进入游戏的界面
extern void GameWelcome();
//游戏说明
extern void GameDescription();
//绘制游戏运行界面 (地图、玩家属性界面)
extern void DrawGameInterface();
//从键盘获取信息
extern int GetKeyboard();
//按键
extern int GetKeyboard();

//Creat中函数声明
extern  Snake* AllocSnakeHead(int x, int y);
extern  Snake* AllocSnakeBody(int x, int y,int z);
extern void InitSnake(Snake** sEnd);
extern void ShowSnake(Snake* sHead);
extern void CreatSnake();

extern Food* AllocFood(Snake* sHead);
extern void ShowFood(Food* food);
extern void CreatFood();

extern void ShowObstacle(Obstacle* obstacle);
extern Obstacle* AllocObstacle();
extern void InitObstacle(Obstacle* obstacle);
extern  void CreatObstacle();

//Paint中函数
extern void gotoxy(int x, int y);
extern void gotopaintWel(int x, int y);
extern void gotopaintDes(int x, int y);
extern void gotopaintWall(int x, int y);
extern void gotopaintPler(int x, int y);
extern void PaintInterface();
extern void GameWelcome();
extern void GameDescription();
extern void PaintWall();
extern char* Gettime();
extern void PlayInfaAttr();
extern void PaintPlayInfa();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157

✨Creat.cpp

#include"snake.h"

//初始化蛇头/蛇身
int conut = 0;
Snake* AllocSnakeHead(int x, int y)
{
Snake* p = (Snake*)malloc(sizeof(Snake));//蛇头

//第一次
if (!conut)
{
p->x = rand() % Diff_x + Dif_x;
p->y = rand() % Diff_y + Dif_y;
while (p->x % 2)//x的坐标要为偶数
{
p->x = rand() % Diff_x + Dif_x;
}
conut = 1;
}
else
{
p->x = x;
p->y = y;
}
p->head = "⊙";
p->next = NULL;
return p;
}
//初始化蛇身

void Add(Snake* p,int x, int y)
{
if (x < Mid_x && y > Mid_y)
{
if (sHead->direction == UP)
{
p->x = x + 2;
p->y = y;
}
if (sHead->direction == RIGHT)
{
p->x = x;
p->y = y - 1;
}

}
else if (x < Mid_x && y < Mid_y)
{
if (sHead->direction == DOWN)
{
p->x = x + 2 ;
p->y = y;
}
if (sHead->direction == RIGHT)
{
p->x = x;
p->y = y + 1;
}
}
else if (x > Mid_x && y < Mid_y)
{
if (sHead->direction == DOWN)
{
p->x = x - 2;
p->y = y;
}
if (sHead->direction == LEFT)
{
p->x = x;
p->y = y + 1;
}
}
else if (x < Mid_x && y < Mid_y)
{
if (sHead->direction == UP)
{
p->x = x - 2;
p->y = y;
}
if (sHead->direction == LEFT)
{
p->x = x;
p->y = y - 1;
}
}
}
Snake* AllocSnakeBody(int x, int y,int z)
{
Snake* p = (Snake*)malloc(sizeof(Snake));
if (z)
{
p->x = x - 2;
p->y = y;
}
else
{
Add(p ,x, y);
}
p->body = "●";
p->next = NULL;
return p;
}

//蛇身和蛇头链接
void InitSnake(Snake** sEnd)
{
Snake* p = (Snake*)AllocSnakeBody((*sEnd)->x, (*sEnd)->y,1);
(*sEnd)->next = p;//sHead->next = p
*sEnd = p;
}
//在地图上画蛇
void ShowSnake(Snake* sHead)
{
Snake* p = sHead;
int flag = 1;
while (p)
{
gotoxy(p->x, p->y);
if (flag)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x03);
printf("%s", p->head);
flag = 0;
}
else
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);
printf("%s", p->body);
}
p = p->next;
}
}

Snake* sHead;//全局变量--在创建食物的时候有用
Snake* sEnd;//全局变量
int SnakeLenth = 0;//蛇的长度
//创建蛇

void CreatSnake()
{
sHead = AllocSnakeHead(0, 0);
sEnd = sHead;
int i = 3;
while (i--)
{
InitSnake(&sEnd);
SnakeLenth++;
}
//蛇的随机方向
sHead->direction = rand() % 4 + 1;
while (sHead->direction == LEFT)
{
sHead->direction = rand() % 4 + 1;
}
ShowSnake(sHead);
}

//初始化食物
Food* AllocFood(Snake* sHead)
{
Food* p = (Food*)malloc(sizeof(Food));//食物

p->x = rand() % Foodd_x + Food_x;
p->y = rand() % Foodd_y + Food_y;
while (p->x % 2)
{
p->x = rand() % Foodd_x + Food_x;

}
p->head = "★";
int flag = 0;
//判断食物的坐标是否合法
while (1)
{
Snake* t = sHead;
while (t)
{
if (t->x == p->x && t->y == p->y)
{
flag = 1;
break;
}
t = t->next;
}
if (flag)
{
p->x = rand() % Foodd_x + Food_x;
while (p->x % 2)
{
p->x = rand() % Foodd_x + Food_x;
}
p->y = rand() % Foodd_y + Food_y + 1;
}
else
{
break;
}
}
return p;
}
//在地图上显示食物
void ShowFood(Food* food)
{
gotoxy(food->x, food->y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0e);
printf("%s", food->head);
}
//创建食物
Food* food;//初始化食物
void CreatFood()
{
food = AllocFood(sHead);
flagob = 1;
ShowFood(food);//展示
}

void ShowObstacle(Obstacle* obstacle)
{
Obstacle* p = obstacle;
int flag = 1;
while (p)
{
gotoxy(p->x, p->y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x09);
printf("%s", p->body);
p = p->next;
}
}

int flagob = 0;//标记:食物创建完,在判断是否重复
Obstacle* AllocObstacle()
{
Obstacle* p = (Obstacle*)malloc(sizeof(Obstacle));//障碍物
p->x = rand() % Foodd_x + Food_x;
p->y = rand() % Foodd_y + Food_y;
while (p->x % 2)
{
p->x = rand() % Foodd_x + Food_x;
}
p->body = "■";
p->next = NULL;

int flag = 0;
//判断障碍物的坐标是否合法
while (1)
{
Snake* t = sHead;
//判断蛇坐标
while (t)
{
if (t->x == p->x && t->y == p->y)
{
flag = 1;
break;
}
t = t->next;
}
//判断食物坐标
if (flagob)
{
if (food->x == p->x && food->y == p->y)
{
flag = 1;
}
}
if (flag)
{
p->x = rand() % Foodd_x + Food_x + 1;
while (p->x % 2)
{
p->x = rand() % Foodd_x + Food_x;
}
p->y = rand() % Foodd_y + Food_y + 1;
}
else
{
break;
}
}
return p;
}

void InitObstacle(Obstacle* obstacle)
{
Obstacle* p = AllocObstacle();
p->next = obstacle->next;
obstacle->next = p;
}

Obstacle* obstacle = AllocObstacle(); //全局变量
//创建障碍物
int ObstacleConut = 0;
void CreatObstacle()
{
InitObstacle(obstacle);//头插法创建
ObstacleConut++;
ShowObstacle(obstacle);//展示
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298

✨main.cpp

#include"snake.h"

void Game()
{
srand((unsigned int)time(NULL));//随机种子
GameWelcome();//游戏欢迎界面
GameDescription();//游戏说明
while (1)
{
DrawGameInterface();//绘制游戏运行界面
if ( GetKeyboard()== 1 )//按键,进去游戏
{
break;
}
}
}
int main()
{
Game();
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

✨Paint.cpp

#include"snake.h"
void gotoxy(int x, int y)
{
COORD pos; //位置 COORD是库里定义的类型。
HANDLE hOutput;
pos.X = x;//水平方向
pos.Y = y;//垂直方向
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//获取缓冲区中的数据,地址赋值给句柄
SetConsoleCursorPosition(hOutput, pos);
/*定位光标位置的函数,坐标为GetStdHandle()返回标准的输出的句柄,
也就是获得输出屏幕缓冲区的句柄,并赋值给对象 pos*/

/*隐藏光标操作 */
CONSOLE_CURSOR_INFO cursor;
cursor.bVisible = FALSE;
cursor.dwSize = sizeof(cursor);
SetConsoleCursorInfo(hOutput, &cursor);
}

/*注意这里横坐标是每次+2 因为控制台字符宽高比为 1:2 */

//游戏欢迎界面设置
void gotopaintWel(int x, int y)
{
/*字体颜色*/
gotoxy(x, y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("□");
}

//游戏说明界面设置
void gotopaintDes(int x, int y)
{
gotoxy(x, y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);
printf("□");
}

//游戏运行界面--围墙
void gotopaintWall(int x, int y)
{
gotoxy(x, y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0f);
printf("■");
}

//玩家属性界面
void gotopaintPler(int x, int y)
{
gotoxy(x, y);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0e);
printf("□");
}

//绘制 欢迎/说明界面
void PaintInterface()
{
/*注意这里横坐标是每次+2 因为控制台字符宽高比为 1:2  x = 2y  */
int i = 0;
static int j = 1;//每一次调用打印不同的形状
//坐标
// (20,2)-(60,2)
// (20,23)-(60,23)
for (i = Infa_x_left; i < Infa_x_right + 1; i += 2)//上边
{
if (j == 1)
gotopaintWel(i, Infa_y_up);
if (j == 2)
gotopaintDes(i, Infa_y_up);

}
for (i = Infa_y_up; i < Infa_y_down; i++)//左边
{
if (j == 1)
gotopaintWel(Infa_x_left, i);
if (j == 2)
gotopaintDes(Infa_x_left, i);
}
for (i = Infa_x_left; i < Infa_x_right + 1; i += 2)//下边
{
if (j == 1)
gotopaintWel(i, Infa_y_down);
if (j == 2)
gotopaintDes(i, Infa_y_down);
}
for (i = Infa_y_up; i < Infa_y_down; i++)//右边
{
if (j == 1)
gotopaintWel(Infa_x_right, i);
if (j == 2)
gotopaintDes(Infa_x_right, i);
}
j++;
}

//欢迎界面
void GameWelcome()
{
PaintInterface();
//区域坐标范围   20,2 -- 60,2
//               20,22-- 60,22 
gotoxy(48, 7);
printf("▄︻┻┳═一 ○○○○○○");
gotoxy(48, 9);
printf("Welcome to Snake Game");
gotoxy(48, 11);
printf("你做好准备了嘛-.-");
gotoxy(48, 13);
printf("游戏即将开始:");
//倒计时
int i = 3;
for (; i >= 0; i--)
{
gotoxy(66, 13);
printf("%d", i);
Sleep(1000);
}
if (i == -1)
system("cls");

}

//游戏说明界面
void GameDescription()
{
PaintInterface();

gotoxy(46, 5);
printf("按键说明:");
gotoxy(46, 7);
printf("w ↑:上,s↓:下,a←:左 ,d→:右");
gotoxy(46, 9);
printf("加速:J/j 减速:I/i 正常:O/o");
gotoxy(46, 11);
printf("Esc:退出游戏 L:游戏继续");
gotoxy(46, 11);
printf("按任意键进入游戏");
gotoxy(46, 15);
printf("游戏说明:\n");
gotoxy(46, 17);
printf("食物:★,障碍物:■\n");
gotoxy(46, 19);
printf("小蛇不可回头,撞墙/障碍物死亡");
Sleep(5000);
system("cls");
}

//绘制墙
void PaintWall()
{
int i = 0;
//2,4-52,4
//2,29-52,29
for (i = Map_x_left; i < Map_x_right + 1; i += 2)//上边
{
gotopaintWall(i, Map_y_up);
}
for (i = Map_y_up; i < Map_y_down; i++)//左边
{
gotopaintWall(Map_x_left, i);
}
for (i = Map_x_left; i < Map_x_right + 1; i += 2)//下边
{
gotopaintWall(i, Map_y_down);
}
for (i = Map_y_up; i < Map_y_down; i++)//右边
{
gotopaintWall(Map_x_right, i);
}
}

//绘制玩家属性界面
char* Gettime()
{
time_t rawtime;
struct tm* info;
//static char buffer[80];
char* buffer = (char*)malloc(80);
time(&rawtime);

info = localtime(&rawtime);

strftime(buffer, 80, "%Y-%m-%d %H:%M:%S   %A", info);
return  buffer;

}
//玩家属性
Player play = { 0 };
//全局变量

void PlayInfaAttr()
{
gotoxy(58, 2);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0b);
printf("贪吃蛇");

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x02);
gotoxy(74, 10);
printf("o(*^@^*)o");
gotoxy(72, 16);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);
//获取当前时间
gotoxy(64, 21);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x09);
printf("%s", Gettime());

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x08);
gotoxy(68, 16);
printf("游戏进行中                    ");
gotoxy(68, 18);
printf("还不错哟,继续加油  ^_^  ");
gotoxy(68, 20);
printf("                  ");

gotoxy(64, 28);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x03);
printf("制作者:日向晚,声声慢");
}

//绘制玩家属性界面
void PaintPlayInfa()
{
int i = 0;
for (i = Play_x_left; i < Play_x_right + 1; i += 2)//上边
{
gotopaintPler(i, Play_y_up);
}
for (i = Play_y_up; i < Play_y_down; i++)//左边
{
gotopaintPler(Play_x_left, i);
}
for (i = Play_x_left; i < Play_x_right + 1; i += 2)//下边
{
gotopaintPler(i, Play_y_down);
}
for (i = Play_y_up; i < Play_y_down; i++)//右边
{
gotopaintPler(Play_x_right, i);
}
PlayInfaAttr();
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245

✨SnakeGame.cpp

#include"snake.h"
void Move(int x, int y);//函数声明
void SnakeMove(int x);//函数声明

 //删除移动时的尾巴
 void DeleBody(int x, int y)
 {
 gotoxy(x, y);
// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x00);
 printf("  ");
 }

//移动过程的实现
 void Move(int x,int y)
 {
 Snake* p = AllocSnakeHead(x, y);
 //新的头
 Snake* t = sHead;
 Snake* t1 = t->next;
 //记录旧的头

 t->body = "●";
 //原来的头变为身体

 p->next = t;
 //链接
 sHead = p;
 //新的头赋值给sHead

 //去掉最后一个节点
 while (t1->next)
 {
 t = t->next;
 t1 = t1->next;
 }
 DeleBody(t1->x, t1->y);
 t->next = NULL;
 free(t1);
 p = NULL;
 }

 //蛇移动
 void SnakeMove(int x)
 {
 //不可往相反的方向走
 switch (x)
 {
 case UP:
 if (x != DOWN)
 {
 Move(sHead->x,sHead->y - 1);
 ShowSnake(sHead);
 }
 break;
 case DOWN:
 if (x != UP)
 {
 Move(sHead->x, sHead->y + 1);
 ShowSnake(sHead);
 }
 break;
 case LEFT:
 if (x != RIGHT)
 {
 Move(sHead->x - 2, sHead->y);
 ShowSnake(sHead);
 }
 break;
 case RIGHT:
 if (x != LEFT)
 {
 Move(sHead->x + 2 , sHead->y);
 ShowSnake(sHead);
 }
 break;
 }
 }


int FinishSet()
 {
if (ObstacleConut > 0)
{
Obstacle* p = obstacle->next;
free(p);
obstacle->next = NULL;
}
switch (win)
{
case 0:
gotoxy(68, 16);
printf("游戏结束,继续加油 `(*>﹏<*)′");
gotoxy(68, 18);
printf("==>按Esc退出,按L继续<==");
gotoxy(68, 20);
printf("                  ");
break;
case 1:
break;
}
int b = getch();
 while (b)
 {
 switch (b)
 {
 case 27:
 system("cls");
 return 1;
 case 'l':
 case 'L':
 system("cls");
 conut = 0; //蛇头创建处
 win = 0;//输赢打印
 play.score = 0;
 return 2;
 default:
 break;
 }
 b = getch();
 }
 return 0;
 }

//添加身体
void AddBody()
{

//也可以使用尾插入,会更简单。
Snake* p = sHead;
//找到最后的一个节点
while (p->next)
{
p = p->next;
}
Snake* t = AllocSnakeBody(p->x, p->y,0);
p->next = t;
}

void EatFoodSet()
{
//原来的食物,会被蛇头覆盖掉,因此是要添加蛇尾就可以。

AddBody(); //移动身体
CreatFood();//产生新的食物
play.score++;
switch (play.score)
{
case 2:
case 4:
case 6:
case 8:
case 10:
case 12:
case 14:
CreatObstacle();
CreatObstacle();
default:
break;
}
gotoxy(68, 13);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x05);
printf("得分:  %d ( ̄︶ ̄) ", play.score);
SnakeLenth++;
}
int win = 0;
int JudgeWin()
{
gotoxy(68, 16);
//每一个输出对应位置要等长
printf("游戏胜利      (●'-'●)       ");
gotoxy(68, 18);
printf("成功获得蛇王的称号      ");
gotoxy(68, 20);
printf("按Esc退出,按L继续");
win = 1;
return FinishSet();
}
int JudgeHitWall()
{
return  FinishSet();
}
int JudgeFood()
{
   EatFoodSet();//吃到食物设置
   return 0;
}
int JudgeHitSelf()
{
Snake* p = sHead->next;
while (p)
{
if (sHead->x == p->x && sHead->y == p->y)
return  FinishSet();
p = p->next;
}
return 0;
}
int JudgeObstacle()
{
Obstacle* p = obstacle;
while (p)
{
if (sHead->x == p->x && sHead->y == p->y)
return  FinishSet();
p = p->next;
}
return 0;
}
 //判断
 int Judge()
 {
 //判断输赢
 if (play.score == Win)
 return JudgeWin();
 //判断是否撞墙
 if (sHead->x == Map_x_left + 1 || sHead->x == Map_x_left ||
 sHead->x == Map_x_right - 1 || sHead->x == Map_x_right ||
 sHead->y == Map_y_up || sHead->y == Map_y_down)
 return JudgeHitWall();
 //判断是否迟到食物,吃到食物  将食物变为头,原来的头变为身体 -->Move函数
 if (sHead->x == food->x && sHead->y == food->y )
  return JudgeFood();

 //判断是否撞到自己
 if (JudgeHitSelf())
 {
 ;
 }
 else//是否撞到障碍物
return JudgeObstacle();

 return 0;
 }

//按键
int key = 0;//当前方向的值
int key1 = 0; //只记录前一次蛇移动按键
int j = 0;//加速标记
int i = 0; //减速标记
int o = 1; //正常速度标记
int GetKeyboard()
{
key = sHead->direction;
while (1)
{
int b = kbhit();
sHead->direction = key;
if (kbhit())
{    //左右需要移动2个单位移动,上下1个单位移动
if (key == UP || key == DOWN || key == LEFT || key == RIGHT)
key1 = key;
key = getch();
switch (key)
{
case 'a':
case 'A':
case 75:
if (key1 != RIGHT)
key = LEFT;
else
key = key1;
break;
case 's':
case 'S':
case 80:
if (key1 != UP)
key = DOWN;
else
key = key1;
break;
case 'd':
case 'D':
case 77:
if (key1 != LEFT)
key = RIGHT;
else
key = key1;
break;
case 'w':
case 'W':
case 72:
if (key1 != DOWN)
key = UP;
else
key = key1;
break;
case 'j':
case 'J':
j = 1;//加速标记
i = 0; //减速标记
o = 0; //正常速度标记
key = key1;
break;
case 'I':
case 'i':
j = 0;//加速标记
i = 1; //减速标记
o = 0; //正常速度标记
key = key1;
break;
case 'o':
case 'O':
j = 0;//加速标记
i = 0; //减速标记
o = 1; //正常速度标记
key = key1;
break;

default:
key = key1;
break;
}
}
     else//自动走的设置
{
SnakeMove(key);
if (j)
Sleep(150);
if (i)
Sleep(250);
if (o)
Sleep(200);
}
if (int ret = Judge())//判断是否吃到食物/是否死亡/赢
{
//返回值为真结束游戏 --- 死亡
return ret;
}
}
return 0;
}


//游戏创建
void DrawGameInterface()
{
//绘制墙
PaintWall();
//绘制玩家属性
PaintPlayInfa();
//创建食物
CreatFood();
//创建蛇
CreatSnake();
//创建障碍物
CreatObstacle();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览48445 人正在系统学习中