贪吃蛇—C—基于easyx图形库(下):从画图程序到贪吃蛇【自带穿墙术】
上节我们用方向控制函数写了个小画图程序,它虽然简单好玩,但我们不应该止步于此。革命尚未成功,同志还需努力。
开始撸代码之前,我们先理清一下思路。和前面画图程序不同,贪吃蛇可以有很多节,可以用一个足够大的结构体数组来储存它。 还需要一个食物坐标。定义如下:
typedef struct Position //坐标结构 { int x; int y; }Pos; Pos array; //移动方向向量 Pos snake[300000] = {}; //蛇的结构体数组,谁能够无聊到吃299999个食物~_~
long len=1; //蛇的长度
Pos egg; //食物坐标
之前的画图程序是四个方向都可以走,可蛇是不能倒着走的,所以方向控制函数要改成这样:
void command() //获取键盘命令 { if (_kbhit()) //如果有键盘消息 switch (_getch()) /*这里不能用getchar()*/ { case 'a': if (array.x != 1 || array.y != 0) {//如果命令不是倒着走,就修正方向向量,否则不做改变,下同。 array.x = -1; array.y = 0; } break; case 'd': if (array.x != -1 || array.y != 0) { array.x = 1; array.y = 0; } break; case 'w': if (array.x != 0 || array.y != 1) { array.x = 0; array.y = -1; } break; case 's': if (array.x != 0 || array.y != -1) { array.x = 0; array.y = 1; } break; } }
蛇可能不止一节,所以移动函数需要做出改变。仔细一想就知道,走了一步之后,除了头结点外,每个节点的下一个坐标为它前一个结点之前的坐标,而头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)
还有个问题是蛇走过的痕迹需要擦除,每走一步,它留下的痕迹应该是走这一步之前蛇的最末一个结点的坐标,我们需要擦除掉它。
结果如下:
void move() //修改各节点坐标以达到移动的目的 { setcolor(BLACK); //覆盖尾部走过的痕迹 rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5); for (int i = len-1; i >0; i--) //除了头结点外,每个节点的下一个坐标为它前一个结点当前的坐标 { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x*10; //头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10) snake[0].y += array.y*10; }
另外,我们的蛇是有穿墙术的~~~它的实现方法非常简单:
void break_wall() { if (snake[0].x >= 640) //如果越界,从另一边出来 snake[0].x = 0; else if (snake[0].x <= 0) snake[0].x = 640; else if (snake[0].y >= 480) snake[0].y = 0; else if (snake[0].y <= 0) snake[0].y = 480; }
接下来是食物相关函数,这个算是重点。
1. 食物生成
我们希望食物每次出现的位置都是随机的, 可以这样实现。
1 srand((unsigned)time(NULL)); 2 egg.x = rand() % 80 * 5 + 100; //头节点位置随机化 3 egg.y = rand() % 50 * 5 + 100;
而且食物不能与蛇重合,最好也不要离蛇太近。综合起来就是这样:(srand在初始化中会被调用,所以这里略去了)
void creat_egg() { while (true) { int ok = 0; //这是个标记,用于判断函数是否进入了某一分支 egg.x = rand() % 80 * 5 + 100; //头节点位置随机化 egg.y = rand() % 50 * 5 + 100; for (int i = 0; i < len; i++) //判断是否离蛇太近 { if (snake[i].x == 0 && snake[i].y == 0) continue; if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10) ok = -1; //如果,进入此分支,改变标记 break; } if (ok == 0) //如果不重合了,跳出函数 return; } }
2. 吃到食物
如果吃到食物,那么需要消除被吃掉的食物,生成新食物,蛇也要增长一节。
我觉得这里最麻烦的就是蛇变长的实现:是在蛇头添加一节,还是在蛇尾?添加在蛇头(尾)的上下左右哪一边?
想来想去,只有在蛇头位置,我们可以根据当前方向向量,在移动方向上新添一节。这对应的代码如下:
//add snake node len += 1; for (int i = len - 1; i > 0; i--) //所有数据后移一个单位,腾出snake[0]给新添的一节 { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //这就是新添的这一节的位置 snake[0].y += array.y * 10;
吃到食物的完整代码如下:
void eat_egg() { if (fabs(snake[0].x - egg.x) <= 5 && fabs(snake[0].y - egg.y) <= 5) //判断是否吃到食物,因为食物位置有点小偏差,只好使用范围判定~~ { setcolor(BLACK); //hide old egg circle(egg.x, egg.y, 5);
creat_egg(); //create new egg //add snake node len += 1; for (int i = len - 1; i >0; i--) { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //每次移动10pix snake[0].y += array.y * 10; } }
游戏结束判定
最后,我们还差一个死亡判定,因为自带穿墙术,所以实际的死亡判定只有一个,就是咬到自己,代码如下:
void eat_self() { if (len == 1) //只有一节当然吃不到自己~~ return; for (int i = 1; i < len; i++) if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5) //如果咬到自己(为了不出bug,使用了范围判定) { outtextxy(250, 200, "GAME OVER!"); //你的蛇死了~ Sleep(3000); //3s时间让你看看你的死相~~ closegraph(); exit(0); //退出 } }
当然,你也可以直接丢掉这个函数,然后开心地狂咬自己—_—||
最后:画图函数
画出食物和蛇,其实蛇没必要全部画出来,只要画蛇头就可以了,但这之中有些小问题,谁有兴趣可以自己玩玩,我是懒得动了~
void draw() //画出蛇和食物 { setcolor(BLUE); for (int i = 0; i < len; i++) { rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5); } setcolor(RED); //画蛋(怎么感觉怪怪的~) circle(egg.x, egg.y, 5); Sleep(100); }
到这里,游戏大功告成~~ 什么?你说运行不起来?那是因为少了初始化函数,和游戏循环啦~~这几个都比较简单,就直接放下面了:
void init() //初始化 { initgraph(640, 480); //初始化图形界面 srand((unsigned)time(NULL)); //初始化随机函数 snake[0].x = rand() % 80 * 5 + 100; //头节点位置随机化 snake[0].y = rand() % 50 * 5 + 100; array.x = pow(-1,rand()); //初始化方向向量,左或者右 array.y = 0; creat_egg(); } int main() { init(); while (true) { command(); //获取键盘消息 move(); //修改头节点坐标-蛇的移动 eat_egg(); draw(); //作图 eat_self(); } return 0; }
好了,这是真的大功告成了。给你们看看死亡方式之自尽:
完整代码如下:
1 #include<graphics.h> 2 #include<conio.h> 3 #include<time.h> 4 #include<math.h> 5 6 typedef struct Position //坐标结构 7 { 8 int x; 9 int y; 10 }Pos; 11 12 Pos snake[300000] = {}; 13 Pos array; 14 Pos egg; 15 long len=1; 16 17 void creat_egg() 18 { 19 while (true) 20 { 21 int ok = 0; 22 srand((unsigned)time(NULL)); //初始化随机函数 23 egg.x = rand() % 80 * 5 + 100; //头节点位置随机化 24 egg.y = rand() % 50 * 5 + 100; 25 for (int i = 0; i < len; i++) 26 { 27 if (snake[i].x == 0 && snake[i].y == 0) 28 continue; 29 if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10) 30 ok = -1; 31 break; 32 } 33 if (ok == 0) 34 return; 35 } 36 } 37 38 void init() //初始化 39 { 40 initgraph(640, 480); //初始化图形界面 41 srand((unsigned)time(NULL)); //初始化随机函数 42 snake[0].x = rand() % 80 * 5 + 100; //头节点位置随机化 43 snake[0].y = rand() % 50 * 5 + 100; 44 array.x = pow(-1,rand()); //初始化方向向量 45 array.y = 0; 46 creat_egg(); 47 } 48 49 void command() //获取键盘命令 50 { 51 if (_kbhit()) //如果有键盘消息 52 switch (_getch()/*这里不能用getchar()*/) 53 { 54 case 'a': 55 if (array.x != 1 || array.y != 0) {//如果不是反方向 56 array.x = -1; 57 array.y = 0; 58 } 59 break; 60 case 'd': 61 if (array.x != -1 || array.y != 0) { 62 array.x = 1; 63 array.y = 0; 64 } 65 break; 66 case 'w': 67 if (array.x != 0 || array.y != 1) { 68 array.x = 0; 69 array.y = -1; 70 } 71 break; 72 case 's': 73 if (array.x != 0 || array.y != -1) { 74 array.x = 0; 75 array.y = 1; 76 } 77 break; 78 } 79 } 80 81 void move() //修改各节点坐标以达到移动的目的 82 { 83 setcolor(BLACK); //覆盖尾部走过的痕迹 84 rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5); 85 86 for (int i = len-1; i >0; i--) 87 { 88 snake[i].x = snake[i - 1].x; 89 snake[i].y = snake[i - 1].y; 90 } 91 snake[0].x += array.x*10; //每次移动10pix 92 snake[0].y += array.y*10; 93 94 if (snake[0].x >= 640) //如果越界,从另一边出来 95 snake[0].x = 0; 96 else if (snake[0].x <= 0) 97 snake[0].x = 640; 98 else if (snake[0].y >= 480) 99 snake[0].y = 0; 100 else if (snake[0].y <= 0) 101 snake[0].y = 480; 102 } 103 104 void eat_egg() 105 { 106 if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5) 107 { 108 setcolor(BLACK); //shade old egg 109 circle(egg.x, egg.y, 5); 110 creat_egg(); 111 //add snake node 112 len += 1; 113 for (int i = len - 1; i >0; i--) 114 { 115 snake[i].x = snake[i - 1].x; 116 snake[i].y = snake[i - 1].y; 117 } 118 snake[0].x += array.x * 10; //每次移动10pix 119 snake[0].y += array.y * 10; 120 } 121 } 122 123 void draw() //画出蛇和食物 124 { 125 setcolor(BLUE); 126 for (int i = 0; i < len; i++) 127 { 128 rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5); 129 } 130 setcolor(RED); 131 circle(egg.x, egg.y, 5); 132 Sleep(100); 133 } 134 135 void eat_self() 136 { 137 if (len == 1) 138 return; 139 for (int i = 1; i < len; i++) 140 if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5) 141 { 142 Sleep(1000); 143 outtextxy(250, 200, "GAME OVER!"); 144 Sleep(3000); 145 closegraph(); 146 exit(0); 147 } 148 } 149 150 int main() 151 { 152 init(); 153 while (true) 154 { 155 command(); //获取键盘消息 156 move(); //修改头节点坐标-蛇的移动 157 eat_egg(); 158 draw(); //作图 159 eat_self(); 160 } 161 162 return 0; 163 }
可能还有若干bug留存,欢迎大家指正~~
甲铁城镇~