您的位置:首页 > 编程语言 > C语言/C++

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语言实现贪吃蛇之局部刷新篇 》,在后续的博客中,我将跟随着原作者的脚步继续折腾这个游戏,感兴趣的同学可以看看后续的文章。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: