C语言实现贪吃蛇(二)----局部刷新
2016-11-17 21:16
399 查看
前言:
在上一篇博客《C语言实现贪吃蛇(一)—-数组实现》,我们使用数组来存储坐标,并且不断的通过全屏刷新的方式来实现蛇移动的动态效果。但是全屏刷新使得该游戏整个过程中的闪烁现象,究其原因,无非就是在于频繁的清空与打印。但是想想看,整个游戏过程中并不需要重复打印整个界面,比如围墙,比如未被吃掉的食物。要实现蛇的移动,我们只要打印出新的蛇头,清除原来的蛇尾就好了。食物只有在被吃掉时才需要重新打印,边界更是只用打印一次。好了,既然我们看到了可提升的地方,就开始动手优化吧。
PS:以下内容都是基于《C语言实现贪吃蛇(一)—-数组实现》,大家可能得去看看。
准备工作:
为了避免全屏刷新,我们应直接定位到需要打印的地方进行打印操作,而不是像之前一样通过循环打印。因此我们将需要一个可以自由移动光标的函数,这样我们才能做到在需要的地方打印。
TC上有一个叫gotoxy()的很方便的函数,该函数,顾名思义,就是将光标移动到(x,y)位置。然而现在估计很少有人还在使用TC。那么我们就着手自己编写一个gotoxy()函数。
void gotoxy(unsigned char x,unsigned char y){ //COORD是Windows API中定义的一种结构,表示一个字符在控制台屏幕上的坐标 COORD cor; //句柄 HANDLE hout; //设定我们要定位到的坐标 cor.X = x; cor.Y = y; //GetStdHandle函数获取一个指向特定标准设备的句柄,包括标准输入,标准输出和标准错误。 //STD_OUTPUT_HANDLE正是代表标准输出(也就是显示屏)的宏 hout = GetStdHandle(STD_OUTPUT_HANDLE); //SetConsoleCursorPosition函数用于设置控制台光标的位置 SetConsoleCursorPosition(hout, cor); }
如果是没有接触过windows编程的同学看到这段代码可能会有些懵逼,不过没关系。整段代码的逻辑其实十分简单。
COORD是windows API定义的结构,其声明如下:
typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;
正如其名字coordinate(坐标)一样,这是一个存储二维坐标的结构体。
HANDLE(句柄)在windows编程中是一个十分重要的概念。在window编程中,对于一个Object(对象)我们只能通过Handle来访问它。觉得不好理解的同学把句柄当作指针来看待就好了。
GetStdHandle函数获取一个指向特定标准设备的句柄,包括标准输入,标准输出和标准错误。STD_OUTPUT_HANDLE正是代表标准输出(也就是显示屏)的宏。
SetConsoleCursorPosition函数用于设置控制台光标的位置。
如果还不懂的同学还是 baidu 一下吧,毕竟我也是差不多的。。。。。
有了gotoxy函数,适当地修改上一篇的代码就可以解决闪屏的问题。
程序实现:
现在我们先来看看程序的最主要部分:主函数int main(void){ int dir = UP; //初始方向默认向上,UP是我们定义的宏 init_game(); //初始化游戏 while(1){ dir = get_dir(dir); //获取方向(我们摁下的方向) move_snake(dir); //移动蛇身 if(!isalive()){ //判断蛇的生命状态 break; } } //清除屏幕 system("cls"); printf("Game Over!\n"); return 0; }
与上一篇博客相比,我们的主函数中去掉 print_game 函数,加入 init_game 函数。
我们来看看程序修改后的一些变量和函数声明(基本上跟之前的差不多的)
#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <windows.h> #include <time.h> //72,80,75,77是方向键对应的键值 #define UP 72 #define DOWN 80 #define LEFT 75 #define RIGHT 77 #define SNAKE 1 #define FOOD 2 #define BAR 3 //初始化地图 char map[17][17] = {0}; //初始化蛇头坐标 unsigned char snake[50] = {77}; //初始化食物坐标 unsigned char food = 68; //蛇长 char len = 1; //存储坐标数字与x、y的转换函数 void tran(unsigned char num,unsigned char * x,unsigned char * y); //初始化游戏 void init_game(void); //获取方向函数(注意当蛇身长度超过一节时不能回头) int get_dir(int old_dir); //移动蛇身函数(游戏大部分内容在其中) void move_snake(int dir); //其中有个生产食物的函数,generate_food(),它利用随机数生成函数生成食物坐标 unsigned char generate_food(void); //判断蛇死活的函数(判断了蛇是否撞到边界或者自食) int isalive(void); //将光标移动到命令行的 (x,y)位置 void gotoxy(unsigned char x,unsigned char y); int main(void){ int dir = UP; //初始方向默认向上,UP是我们定义的宏 init_game(); //初始化游戏 while(1){ dir = get_dir(dir); //获取方向(我们摁下的方向) move_snake(dir); //移动蛇身 if(!isalive()){ //判断蛇的生命状态 break; } } //清除屏幕 system("cls"); printf("Game Over!\n"); return 0; }
与前一篇博客对比,这里的一下变化:
修改了 main() 主函数
用 init_game() 替换 print_game()
修改了 move_snake() 函数
增加了 gotoxy() 光标坐标定位函数
main() 函数和 gotoxy() 函数我们已经介绍过了,现在我们看看 init_game() 函数:
//初始化游戏
void init_game(void);
//初始化游戏 (打印初始状态) void init_game(void) { int i, j; unsigned char x, y, fx, fy; tran(snake[0], &x, &y); tran(food, &fx, &fy); for (j = 0; j<17; j++) { for (i = 0; i<17; i++) { //打印围墙 if (i == 0 || i == 16 || j == 0 || j == 16){ putchar('#'); } //打印蛇头 else if (i == x&&j == y){ putchar('*'); } //打印食物 else if (i == fy&&j == fx){ putchar('$'); } //空白地方 else{ putchar(' '); } } putchar('\n'); } }
从 init_game() 函数中和在 main() 调用可以看出,该函数的作用仅仅是初始化作用(将一成不变的围墙画好,打印食物和蛇头的初始位置)。后面就没它什么事了。
再来看看 move_snake() 函数:
//移动蛇身函数(游戏大部分内容在其中)
void move_snake(int dir);
void move_snake(int dir){ int last = snake[0],current; //last与current用于之后蛇坐标的更新 int i; int grow=0; //判断是否要长身体 unsigned char x, y, fx, fy; //蛇坐标与食物坐标 tran(food, &fx, &fy); //食物坐标 tran(snake[0], &x, &y); //蛇头坐标 switch (dir){ //更新蛇头坐标(坐标原点是左上角) case UP: y--; break; case DOWN: y++; break; case LEFT: x--; break; case RIGHT: x++; break; } //按位抑或(妙!) snake[0] = ((x ^ 0) << 4) ^ y; //将x,y换回一个数 //蛇吃到了食物 if (snake[0] == food) { grow = 1; food = generate_food(); //产生新食物 } for (i = 0; i<len; i++) { //蛇移动的关键,通过将蛇头原来的坐标赋给第二节,原来的第二节赋给第三节,依次下去,完成蛇坐标的更新 if (i == 0) //如果只有头,跳过,因为前面已更新蛇头坐标 continue; current = snake[i]; //将当前操作的蛇节坐标存储到current里 snake[i] = last; //完成当前操作蛇节坐标的更新 last = current; //last记录的是上一次操作蛇节的坐标,这次操作已经结束,故把current赋给last } gotoxy(x, y); //将光标移动到指定位置 putchar('*'); //打印新的蛇头 if (grow) { //如果要长节的话就不去除旧的蛇尾 snake[len] = last; len++; tran(food, &fx, &fy); // 打印食物(上面已经产生新的食物坐标) gotoxy(fx,fy); putchar('$'); }else { //这是为了避免当你把蛇绕成一个圈的时候(蛇头紧跟蛇尾,没咬到),清除蛇尾顺便也把蛇头清除掉了 if(snake[0] != last){ tran(last, &x, &y); gotoxy(x, y); putchar(' '); //去除旧的蛇尾 } } //避免光标一直跟着蛇尾(或食物) gotoxy(0,17); Sleep(500); }
从该函数中可以看出,我们利用 gotoxy() 函数,实现了局部刷新的功能,从而避免了游戏的闪烁现象。
本博客参考自《C语言实现贪吃蛇之局部刷新篇 》,在后续的博客中,我将跟随着原作者的脚步继续折腾这个游戏,感兴趣的同学可以看看后续的文章。
相关文章推荐
- javascript脚本轻松实现局部刷新
- 利用ajax实现页面的局部刷新
- AJAX(prototype)实现的局部刷新搜索框实例
- Ajax 实现页面局部刷新
- GetCallbackEventReference方法实现局部刷新,回调非WebSerice实现
- 用WebService实现web页面的局部刷新
- 如何用UpatePanel实现省市区的局部刷新
- 使用AJAX技术实现网页无闪自动局部刷新
- Ajax中,ModalPopup与UpdatePanel结合,实现局部刷新的登录效果 (调用cs服务)
- javascript脚本轻松实现局部刷新
- 实现验证码局部刷新
- AJAX技术实现网页无闪自动局部刷新
- 利用Ajax实现页面局部刷新
- 用iframe实现的网页局部刷新
- 实现Web页面中级联菜单的设计/实现动态加载列表框/实现自动刷新页面/实现Web页面的局部动态更新/实现自动完成功能
- 实现web页面的局部刷新
- 利用Ajax实现页面局部刷新
- Ajax中,ModalPopup与UpdatePanel结合,实现局部刷新的登录效果 (调用cs服务)
- 局部刷新的两种实现方式
- 使用 AJAX,局部刷新 GridView 进行数据绑定的简单实现