贪吃蛇的玩法我想应该大家都是耳熟能详了。但是这游戏虽然简单,但是编写的难度对一个刚刚学完c++,准备考研的苦逼大学生来说却是一件非常艰难的事情。
date:10月3日,国庆节的头3天,大家在外玩耍我却苦逼的在这里写代码痛苦ing,不知道痛苦会不会持续(有发泄情绪的成分嘿嘿),i don't want that
接下来,我会说明本次的代码中实现的各个函数的功能,已经调用外部一些包的解释---同样也是我新学的,对我自己来说也是一种学习。
那贪吃蛇需要实现什么功能呢,如何分步骤?
1.如何画网格?
2.蛇撞墙或者吃东东后会怎么样?
3.蛇的方向控制怎么实现?
问完自己3个问题后就可以开始开发啦,首先先定义一个20*20(可改)的一个正方形的网格图,那这种网格图的话,一定是有墙的喽,我这里把墙和蛇儿可以移动的砖块分别用1,0表示,这和扫雷有些类似哦,所以这些的变量得通过一个函数init_map函数来实现,这个函数就是用来初始化区域的。
void init_map(int** map,int high,int width) {for (int i = 0; i < width; ++i) {for (int j = 0; j < high; ++j) {if (i == 0 || i == width - 1) map[i][j] =1;//表示是墙else if(j == high - 1 || j == 0)map[i][j] = 1;elsemap[i][j] = 0;//剩余的情况就是会是空格}cout << "\n";//这个就是一个输入输出流,是属于instream包中的}return;
}
如果定义好了活动区域和墙壁区域,那蛇的定义还会远 吗???很明显,不会啊,那好蛇如何来定义呢?蛇头是不是应该用一个别的图形来当比较好,这里的话我把具体的蛇抽象成了链表,刚刚好,头尾相连非常的适宜,那使用链表之前也得在一开始时候就有定义吧,所以,一开始得定义一个链表的结构体---在最后完整代码中会给上,那使用了结构体后就得对蛇开始一个初始化喽,我这里用到的函数就是init_snake,但是用的也是结构体指针的形式哦,指针真是一个神奇的东西呢,这个p其实就是指向s的一个移动结构体。通过这次的小游戏开发确实是让我受益匪浅。
就好像我和你一样处于同个起跑线,10年后,我还是我,你已经不是你了。哦我的天
struct snake* init_snake() {struct snake* s = (struct snake*)malloc(sizeof(struct snake));//申请一个头struct snake* p = s;//其实这个p是一个移动的结构体指针int x[6] = {4,5,6,7,8,9};int y[6] = {4,4,4,4,4,4};//这是一开始蛇头的位置for (int i = 0; i < 6; i++) {struct snake* node = (struct snake*)malloc(sizeof(struct snake));p->next = node;//这里等同于s->next =nodep = p->next;p->i = x[i];p->j = y[i];//定义了的坐标}p->next = NULL;return s;//这里是关键哦,返回的值还是s表头,但是它里面的数据居然是通过for循环后的,你说神奇不神奇呢???
}
接下来的函数来头就有些大了,他是用来获取某个位置的,x,y表示,当然了,我认为现在的工作中应该不会用了,就知道一下就好。//这个只需要脊柱就行了
void gotoxy(int i, int j) {COORD position;position.X = i;position.Y = j;//调用PISetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);//这条语句前面的第一个参数一般是固定的呢,后面的参数是需要填写一个结构体的//刚刚好position是符合这一特性的
}
根据名字就知道了,是用来画蛇的,之前就定义过了嘛,因为这里的话蛇它的身体的表示是和墙一样的,这里很巧妙的一个设计就是,它自己的身体也是墙!!!就是撞自己等于撞墙了,这里我把蛇头定义成了2,其他的部分就变成了1,然后显示在map中,别急后边有一个判定的可以用来,把数据重新加载。
void drawsnake(int** map, struct snake* s) {struct snake* p = s->next;map[p->i][p->j] = 2;//第一个表示额是蛇头p = p->next;while (p){map[p->i][p->j] = 1;p = p->next;}return;
}
很好,蛇,墙壁,版面都画好了,接下来就还剩下那个魔鬼撒旦要吃的“苹果”了,这里是用小星星来实现的,首先是对场上所有的空白的那个格子统计,就是得统计有多少个,然后通过在全局模式下定义的,然后再行进逐条排查当是空格子的时候再给他减掉,最后剩下的那个空格子就是随机的啦,然后给他塞进去“食物”就好了。//其实这个也不算画,就是对星星一个定义罢了
void draw_star(int** map, int* star, int hight, int width) {int sum = 0;int index = 0;//计算出空格子的数量for (int i = 0; i < hight; ++i) {for (int j = 0; j < width; ++j) {if (map[i][j] == 0) sum += 1;}}index = random(sum) + 1;//从1到sum中随机生成星星for (int i = 0; i < hight; ++i) {for (int j = 0; j < width; ++j) {if (map[i][j] == 0) {index -= 1;}if (index == 0) {star[0] = i;star[1] = j;return;}}}return;
}
接下来就是画图了,你看最开始的时候是有一个清屏的,所以这次的游戏就是建立在,每次的不断刷新上的,再看第二句就是给定位重新的到0,0,接下来就是判定了,根据之前写的那些鬼玩意儿,进行判定是蛇头,蛇神,墙体(当然墙体和蛇神是一个道理),食物,空砖头等。
void draw(int** map,int* star,int height,int width) {system("cls");//清除屏幕,就是每次的屏幕刷新gotoxy(0, 0);//光标定位到0,0for (int i = 0; i < height; ++i) {for (int j = 0; j < width; ++j) {if (map[i][j] == 1)//1表示的是墙cout << "■";else if (map[i][j] == 2)//2表示的是哪个蛇头cout << "●";else if (i == star[0] && j == star[1])cout << "★";elsecout << "□";}cout << "\n";//每次输出完后需要空一行}return;
}
接下来的话就是比较过分的一个代码了,之所以说他很过分的原因就是他这个又臭又长,而且重点是什么?不好意思,刚刚差点骂人,backspace回去了,里面有些代码真的很重复,我后面看看能不能给他改进了吧。好接下来说一下这个代码,首先是定一个新的蛇结构体指针,对它进行传参判定,还有蛇的吃,移动,撞墙的判定。
void updata_snake(char c, struct snake* s, int* star) {snake* newsnake;newsnake = (snake*)malloc(sizeof(snake));if (c == 'w') {//把蛇网上移动一格newsnake->i = s->next->i - 1;//网上的话就是i-1,也就是直角坐标系上纵坐标+1newsnake->j = s->next->j;if (map[newsnake->i][newsnake->j] == 1) {//这里就是判断这蛇撞墙了的情况sign = 3;//给个信号就是蛇挂了}if (newsnake->i == star[0] && newsnake->j == star[1]) {//表示吃到星星了哦,congratulationnewsnake->next = s->next;s->next = newsnake;//这里就是添加struct snake* q = s;sign = 2;//这个就是吃到星星了的意识}else {newsnake->next = s->next;//因为移动的话一般都是关乎蛇头的嘛//然后后面的也不会动的,这时候的蛇头就变成蛇身子了s->next = newsnake;//这里进行好后,也就是说会多出一个尾巴来struct snake* q = s;//定位到本来是蛇头的位置???while (q->next->next != NULL) {q = q->next;}map[q->next->i][q->next->j] = 0;//这里是定位到蛇尾的位置的free(q->next);q->next = NULL;}}else if (c == 'a') {//把蛇往左移一格newsnake->i = s->next->i;newsnake->j = s->next->j-1;if (map[newsnake->i][newsnake->j] == 1) {//这里就是判断这蛇撞墙了的情况sign = 3;//给个信号就是蛇挂了}if (newsnake->i == star[0] && newsnake->j == star[1]) {//表示吃到星星了哦,congratulationnewsnake->next = s->next;s->next = newsnake;//这里就是添加struct snake* q = s;sign = 2;//这个就是吃到星星了的意识}else {newsnake->next = s->next;//因为移动的话一般都是关乎蛇头的嘛//然后后面的也不会动的,这时候的蛇头就变成蛇身子了s->next = newsnake;//这里进行好后,也就是说会多出一个尾巴来struct snake* q = s;//定位到本来是蛇头的位置???while (q->next->next != NULL) {q = q->next;}/* gotoxy(q->i, q->j);printf("■");*/map[q->next->i][q->next->j] = 0;//这里是定位到蛇尾的位置的free(q->next);q->next = NULL;}}else if (c == 'd') {newsnake->i = s->next->i;newsnake->j = s->next->j + 1;if (map[newsnake->i][newsnake->j] == 1) {//这里就是判断这蛇撞墙了的情况sign = 3;//给个信号就是蛇挂了}if (newsnake->i == star[0] && newsnake->j == star[1]) {//表示吃到星星了哦,congratulationnewsnake->next = s->next;s->next = newsnake;//这里就是添加struct snake* q = s;sign = 2;//这个就是吃到星星了的意思}else {newsnake->next = s->next;//因为移动的话一般都是关乎蛇头的嘛//然后后面的也不会动的,这时候的蛇头就变成蛇身子了s->next = newsnake;//这里进行好后,也就是说会多出一个尾巴来struct snake* q = s;//定位到本来是蛇头的位置???while (q->next->next != NULL) {q = q->next;}map[q->next->i][q->next->j] = 0;//这里是定位到蛇尾的位置的free(q->next);q->next = NULL;}}else {newsnake->i = s->next->i+1;//因为移动的话一般都是关乎蛇头的嘛//然后后面的也不会动的,这时候的蛇头就变成蛇身子了newsnake->j = s->next->j;if (map[newsnake->i][newsnake->j] == 1) {//这里就是判断这蛇撞墙了的情况sign = 3;//给个信号就是蛇挂了}if (newsnake->i == star[0] && newsnake->j == star[1]) {//表示吃到星星了哦,congratulationnewsnake->next = s->next;s->next = newsnake;//这里就是添加struct snake* q = s;sign = 2;//这个就是吃到星星了的意识}else{newsnake->next = s->next;//因为移动的话一般都是关乎蛇头的嘛//然后后面的也不会动的,这时候的蛇头就变成蛇身子了s->next = newsnake;//这里进行好后,也就是说会多出一个尾巴来struct snake* q = s;//定位到本来是蛇头的位置???while (q->next->next != NULL) {q = q->next;}map[q->next->i][q->next->j] = 0;//这里是定位到蛇尾的位置的free(q->next);q->next = NULL;}}
}
这个就是完全为了上面的函数服务的,我觉得上面那个函数应该是难点。这个函数就是判定输入的是字符是多少
void action_snack(struct snake* s,int* star) {//if (_kbhit()) {// c = _getch();// //这里呢_kbhit函数是一个连续敲击,就是会一直执行下去// //而c呢就是记录每次敲击下的字母或者是字符//}switch (c){case 'w':updata_snake(c, s,star);break;//这里还有一个函数就是更新数据,相当于每次敲击后//蛇儿的动向都需要更新的case 's':updata_snake(c, s,star); break;case 'a':updata_snake(c, s,star);break;case 'd':updata_snake(c, s,star); break;}}
接下来就是自动化了,因为定义的c是一个全局所以在后续每次的循环中都是起到同样的作用,还有判定按键冲突的bug修复
void check_snake() {DWORD time = 500;//1表示一毫秒 1000表示一秒 要改变速度可以修改timeDWORD time_start = GetTickCount();//gettickcount返回的是操作系统到现在经历的毫秒数char t;//Dword 全程是double word//那这里为何要用到DWORD呢?原因是int在各个机器上的位数不一样//而且DWORD是windows包里面的东西while (true) {if (_kbhit()) {char ch = _getch();//这俩就是一个组合,获得当前键位用的if (ch == 97 || ch == 100 || ch == 115 || ch == 119) {//a-97 d-100 s-115 w-119t = ch;if (t == 97 && c == 'd') {c = 'd';}else if (t == 100 && c == 'a') {c = 'a';}else if (t == 115 && c == 'w') {c = 'w';}else if (t == 119 && c == 's') {c = 's';}else {c = ch;}}}DWORD time_end = GetTickCount();//获取键位if (time_end - time_start > time) {time_start = time_end;break;}}return;
}
最后的最后就是一个main,和之前说的一样,首先是进行对墙,空地,食物进行标出
int main() {//1.首先是画出地图哦int hight = 20;int width = 20;struct snake* s = init_snake();//定一个mapmap = (int**)malloc(sizeof(int*) * hight);for (int i = 0; i < hight; ++i) {map[i] = (int*)malloc(sizeof(int) * width);//申请的是行的大小}init_map(map, hight, width);//以上的效果大概drawsnake(map, s);int star[2] = { 0,0 };draw_star(map, star, hight, width);draw(map, star, hight, width);while (1) {//这个循环就是会一直执行下去的if (sign == 3) {break;}//也就是说吃到了三个星星就会退出啦check_snake();action_snack(s, star);//action_snack里面嵌套着updata函数init_map(map, hight, width);//重新的初始化一下地图drawsnake(map, s);//同样也得初始化一下蛇if (sign == 2) {draw_star(map, star, hight, width);sign = 0;//sign=2是吃到星星了然后重新计数的}draw(map, star, hight, width);gotoxy(0, 0);//光标重新到0,0}system("cls");gotoxy(hight / 2, width / 2);cout << "游戏结束";system("pause");return 0;
}
截稿---10.4 凌晨1点,洗洗睡了,顶不住
10.5日早上10:46,起锅烧炉,开始干活啊哈哈哈。