贪吃蛇代码学习心得(C语言) | 您所在的位置:网站首页 › 贪吃蛇c语言 › 贪吃蛇代码学习心得(C语言) |
贪吃蛇代码学习心得(C语言)
前言
以前自学C语言时,学了点控制台光标的相关知识,就一直想弄个小游戏出来,第一个想到的就是贪吃蛇了。上下左右控制方向,随机生成食物,吃上食物长度加一,碰到墙壁或咬到自己结束游戏......想到了很多,甚至当时兴奋的睡不着觉,可是自己一上手的时候,发现无从下手。最核心的如何让“蛇”动起来我都不知如何实现。我按照以前的按个键刷新屏幕的思路发现走不通。因为不按键的时候,蛇也是在动的,然后我就在网上看了些代码。当时没有静下心看明白,时隔几年再次学习C时,又想到了它。这次总算把它啃下来了。记录些心得,经验。 定义 #define U 1#define D 2#define L 3#define R 4 //蛇的状态,U:上 ;D:下;L:左 R:右typedef struct SNAKE //蛇身的一个节点{ int x; int y; struct SNAKE *next;}snake;//全局变量//int score = 0, add = 10;//总得分与每次吃食物得分。int status, sleeptime = 150,length = 4;//状态与每次运行的时间间隔与长度snake *head, *food;//蛇头指针,食物指针snake *q;//遍历蛇的时候用到的指针int endGamestatus = 0; //游戏结束的情况,1:撞到墙;2:咬到自己;3:主动退出游戏。定义4个宏,这个好理解,方便以后判断条件 蛇身采用的是链表的结构,其实发现这个结构对于这个游戏来说相当完美的结构。蛇是连续结构,一个连一个,但最重要的是头,可以通过头结点遍历整个蛇身。而且x,y坐标可以同时存储在结构体当中,比较方便。 status和sleeptime是以后蛇动起来的关键,没有按键的情况下。 食物food和蛇身采用同一个结构体。 为了方便还引进了一个q指针指向头部。 函数解析 Pos(); void Pos(int x, int y)//设置光标位置{ COORD pos; HANDLE hOutput; pos.X = x; pos.Y = y; hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//返回标准的输入、输出或错误的设备的句柄,也就是获得输入、输出/错误的屏幕缓冲区的句柄 SetConsoleCursorPosition(hOutput, pos); SetConsoleTextAttribute(hOutput,0x01|0x02); //设置颜色}输入x,y坐标参数,将光标设置在此处。这里面几个东西要记住,以后用到的多。句柄的作用,在网上查说是windows为了控制虚拟内存里的元素所用的标识符号,是内部的东西(就当死记住的东西好啦)。我又在最后加了一句源代码没有的,改变颜色,聊胜于无吧~如果能蛇身一种颜色,食物一种颜色,边框,计分板,随着蛇身变长还能变换。不知道能不能实现。 creatMap(); void creatMap()//创建地图{ int i; for (i = 0; iy = 5; tail->next = NULL; for(i=0;inext = tail; head->x = 24 + 2 * i;//一个蛇身占两个格子 head->y = 5; tail = head; } q = head; while(q->next != NULL) { Pos(q->x,q->y); printf("■"); q = q->next; }}头插法创建蛇身,这样头指针最后指向头部,最后将指针q指向蛇头。 我这里将源代码稍微改了下,初始化蛇身时就将q引进,而且输出蛇身时的while()里面的判断条件改了下,这下长度就是设置值了,源代码总是多一节。。。仔细分析,假设蛇身初始化长度为1,先创建了一个没有尾指针的tail,然后for()执行一次,head->next指向tail,tail再指向head,这里其实就有两个结点了,一个head和一个没有指向NULL的尾结点。如果按原代码的 tail != NULL,其实while()要执行两次,这样就比预想的长度多了一节。 bieteSelf(); int biteSelf()//判断是否咬到了自己{ snake *self; self = head->next; while (self != NULL) { if (self->x == head->x && self->y == head->y) { return 1; } self = self->next; } return 0;}其实这个没什么好说的,本以为很好想,自己敲的时候才发现没那么容易。首先定义一个self指向head->next,然后遍历一遍,看和头部坐标有没有重合。这一个函数贯穿整个过程,每动一下,就要遍历一遍,其实计算量挺大的。(不过都计算机来说小儿科,都没有卡顿) createFood(); void createFood()//随机出现食物{ snake *food_1; srand((unsigned)time(NULL)); food_1 = (snake*)malloc(sizeof(snake)); food_1->x = rand()% 52 + 2; while((food_1->x % 2) != 0) { food_1->x = rand()% 52 + 2; } food_1->y = rand() % 24+1; // q = head; while(q != NULL)//食物不能和身体重合 { if(q->x ==food_1->x && q->y == food_1->y) { free(food_1); createFood(); } q = q->next; } Pos(food_1->x,food_1->y); food = food_1; printf("○");}这里用了srand(),用于产生随机数。 因为蛇身的x轴方向占两个,所以food_1->x 必须为偶数,while()用来筛选。 cantCrossWall(); void cantCrossWall()//不能穿墙{ if (head->x == 0 || head->x == 56 || head->y == 0 || head->y == 26) { endGamestatus = 1; endGame(); }} snakeMove();【核心代码】 void snakeMove()//蛇前进,上U,下D,左L,右R{ snake * nexthead; cantCrossWall(); nexthead = (snake*)malloc(sizeof(snake)); if(status == U) //状态保持向上,即一直向上运动 { nexthead->x = head->x; nexthead->y = head->y - 1; if(nexthead->x == food->x&&nexthead->y == food->y) //如果有食物 { nexthead->next = head; head = nexthead; q= head; while(q->next != NULL) { Pos(q->x,q->y); printf("■"); q = q->next; } score += add; createFood(); } else //没有食物 { nexthead->next = head; head = nexthead; q= head; while(q->next->next != NULL) { Pos(q->x,q->y); printf("■"); q = q->next; } Pos(q->x,q->y); printf(" "); free(q->next); q->next = NULL; //将指针置空 } } if (status == D) { nexthead->x = head->x; nexthead->y = head->y + 1; if (nexthead->x == food->x && nexthead->y == food->y) //有食物 { nexthead->next = head; head = nexthead; q = head; while (q != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } score = score + add; createFood(); } else //没有食物 { nexthead->next = head; head = nexthead; q = head; while (q->next->next != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } Pos(q->x, q->y); printf(" "); free(q->next); q->next = NULL; } } if (status == L) { nexthead->x = head->x - 2; //横向占两格 nexthead->y = head->y; if (nexthead->x == food->x && nexthead->y == food->y)//有食物 { nexthead->next = head; head = nexthead; q = head; while (q != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } score = score + add; createFood(); } else //没有食物 { nexthead->next = head; head = nexthead; q = head; while (q->next->next != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } Pos(q->x, q->y); printf(" "); free(q->next); q->next = NULL; } } if (status == R) { nexthead->x = head->x + 2; //横向占两格 nexthead->y = head->y; if (nexthead->x == food->x && nexthead->y == food->y)//有食物 { nexthead->next = head; head = nexthead; q = head; while (q != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } score = score + add; createFood(); } else //没有食物 { nexthead->next = head; head = nexthead; q = head; while (q->next->next != NULL) { Pos(q->x, q->y); printf("■"); q = q->next; } Pos(q->x, q->y); printf(" "); free(q->next); q->next = NULL; } } if (biteSelf() == 1) //判断是否会咬到自己 { endGamestatus = 2; endGame(); }}这里非常巧秒的引入了一个nexthead的指针来表示蛇的下一步,而蛇的下一步和最后按键又有关系,所以最开声明了status变量。然后根据if()判断条件来选择对应的走法。真的秒! 进入if()后,又分为两种情况,下一步有食物和下一步没有食物。若有食物,吃掉它,食物消失,长度加一;若没有,就保持原状态继续移动。 先来看看有食物:nexthead的坐标和food的坐标重合,就可以满足。然后使用头插法,创建头结点,最后再将q指向新的头结点,之后while()执行输出,这里判断条件为q->next !=NULL;如果按源代码的 q != NULL;长度又会多一节。原因还是初始化(假设长度为1)时,内存中期是有两个结点,而吃掉食物后,创建了一个新的结点,这时内存中有3个,而我们只需要打印两个。吃完之后,相应的分数增加,在生成新的食物。 没有食物:懂了上面的,很显然while这里要用 q->next->next!=NULL 来作判断条件,打印完蛇身,这时你会发现多了一节,(调试过程可以很明显的看出)因为开始的那个位置的图像还存留着,所以这是要用Pos来设置光标位置,while()走完时,q指向q->next,即头最开始位置(这里都假设蛇身长度为一),然后有空格覆盖,注意时两个空格!之后在释放掉 q->next,即初始化时只有数据域的结点,而q变为新的没有尾指针的结点 上下左右,四种状态,只要把坐标变化值稍作改变,其余都一样。需注意的是:x轴方向每次变化值为2。 最后,每走一步需要判断是否会咬到自己。 pause(); void pause()//暂停{ while (1) //使用无限循环达成 { Sleep(300); if (GetAsyncKeyState(VK_SPACE)) { break; } }}runGame();【核心代码】 void runGame()//控制游戏{ Pos(64, 15); printf("不能穿墙,不能咬到自己\n"); Pos(64, 16); printf("用↑.↓.←.→分别控制蛇的移动."); Pos(64, 17); printf("F1 为加速,F2 为减速\n"); Pos(64, 18); printf("ESC :退出游戏.space:暂停游戏."); Pos(64, 20); //printf("C语言研究中心 www.clang.cc"); status = R; //初始状态 while (1) { Pos(64, 10); printf("得分:%d ", score); Pos(64, 11); printf("每个食物得分:%d分", add); if (GetAsyncKeyState(VK_UP) && status != D) { status = U; } else if (GetAsyncKeyState(VK_DOWN) && status != U) { status = D; } else if (GetAsyncKeyState(VK_LEFT) && status != R) { status = L; } else if (GetAsyncKeyState(VK_RIGHT) && status != L) { status = R; } else if (GetAsyncKeyState(VK_SPACE)) { pause(); } else if (GetAsyncKeyState(VK_ESCAPE)) { endGamestatus = 3; break; } else if (GetAsyncKeyState(VK_F1)) { if (sleeptime >= 50) { sleeptime = sleeptime - 30; add = add + 2; if (sleeptime == 320) { add = 2;//防止减到1之后再加回来有错 } } } else if (GetAsyncKeyState(VK_F2)) { if (sleeptime |
CopyRight 2018-2019 实验室设备网 版权所有 |