超简单的C语言贪吃蛇 不闪屏 双缓冲

C语言贪吃蛇

今天把以前自己写的贪吃蛇总结了一下,发到博客上,怕放在电脑上哪天丢失了都不知道, 有不当之处还望指教 (*・ω< ) ヾ(◍°∇°◍)ノ゙

贪吃蛇中, 我们看到的蛇在不断的移动,其实就是在屏幕上不断的打印显示信息,又不断的擦除,就让蛇看起来在移动,在这里要用到语句: system("cls"), 但这个就造成了一个问题,打印不能一下打印完,所以造成了 闪屏, 解决闪屏的办法是 双缓冲, 双缓冲我在后边介绍
在这里插入图片描述

主要思想

大致准备

  1. 首先是用一个二维数组 arr 来存放屏幕上的布局, 用0来表示空白的地方,蛇可以移动的区域,
  2. 其他的食物、还有最外边的墙都用小于零的数来表示
  3. 我们的蛇身的话就用大于零的数表示,蛇尾用1表示,从蛇尾到蛇头依次增大,就可以根据蛇头值的大小来判断这个蛇吃了多少的食物
  4. 还有一些就是键盘上的方向键的, 总行、总列数,我们可以用宏定义, 这样方向键在用的时候就不用写 ASCII码了,直接用标识符又比较明显知道是哪个方向,不易错;
  5. 还要用一个1行2列的数组来存放蛇头的坐标位置,默认蛇是有长度的,所以要默认在数组中给蛇安排好它的位置,就是把数组中蛇身坐标的位置的值改为从蛇尾0到蛇头依次增大的值,
    在这里插入图片描述

随机产生食物

  1. 那我们现在开始随机产生食物,用一个函数 produceFood 专用来随机产生食物
  2. 随机产生一个坐标垂直vertical的水平的 level v = rand() % ROW; l = rand() % COL 判断 arr[v][l] 是否是空白区域,如果不是再重新产生,直到产生的是空白区域

方向选择

  1. 根据按键,判断出是哪个方向,就把相应的方向存好,注意: 如果按的是和蛇移动的方向相反,我们应该不处理,让蛇继续往这个方向移动,不然就会出现蛇吃自己,那就应该死亡了,这样体验要好一些些, 因为有好多时候就这样死亡了体验不好

蛇移动

  1. 那我们怎么来让蛇移动呢,首先我们要默认给蛇一个移动的方向,还要用一个1行2列的数组来存放蛇头的坐标位置,知道了蛇头的移动方向,还有坐标位置,那我们就知道了蛇头要移动到下一个坐标点的位置,那我们根据我们的数组就知道下一个坐标点位置是什么东西(食物,墙,空白地方,蛇身),我们就可以判断了
  2. 如果是 空白地方: 首先,我们让整个数组中大于零的值都 减1, 那原先的蛇尾的值为 1 变为了 0 , 就变为空白区域了,蛇尾就移动了, 之后我们让下一个坐标点位置的值改为蛇头的值 加1 ,蛇头就自然移动到下一个位置了
  3. 如果是 食物:把下一个坐标点位置的值改为蛇头的值 加1,这样蛇移动了,且变长了,吃了一个食物,之后调用 产生食物的函数,继续运行
  4. 如果是 墙或蛇身:死亡了, 根据蛇头的值,判断出吃了多少食物,得了多少分, 显示分数,退出游戏

双缓冲

写这个贪吃蛇已经快有一年了, 我也忘了双缓冲了(>ω・* )ノφ(>ω<*) ,只能模糊的记得些,让我说我也不知道怎样说, 我太菜了┗( ▔, ▔ )┛,但我代码里面有双缓冲,具体的双缓冲可以看
http://m.bubuko.com/infodetail-582228.html
https://blog.csdn.net/weixinhum/article/details/72179593
可以去网上了解了解 , 好了, 看看代码吧,不足之处欢迎指教 ─=≡Σ(((つ•̀ω•́)つ

代码如下:

/*********************************************
 *贪吃蛇 
 *用0表示空白的地方,用大于0的数做蛇,从蛇尾到蛇头的数依次增大,其它(墙,食物)的用负数表示,
 *碰到食物就把食物那个点的值变成蛇头值+1,食物的点变成蛇头,退出
 *没有碰到食物,下一个点为空,就把下一个点的值变成蛇头值+1,
 *在将整个数组大于0的值都减一,蛇尾为一的自然变为0,就消失了,其它情况直接退出
 *@author zhoufei
 *@date 2018-06-03
 *********************************************/
#include<stdio.h>
#include<windows.h>
#define FOOD -4     //食物
#define WALL -9   //墙
#define COL 35
#define ROW 20
#define VK_UP 72   //方向键上下左右的第二个值,方向键有两个值,第一个是224,分别是72,...
#define VK_DOWN 80
#define VK_LEFT 75
#define VK_RIGHT 77
#define int short  //把所有的int类型数据换成short类型节约空间 
/*心得,常量大写,变量有多个单词加下划线,函数多个单词首字母小写,其余首字母大写*/
//用word打特殊符号,再复制
void fangXiangChoice(int arr[][COL]);  //输入的方向的
void start(int p[][COL]);           //开始之前的初始化操作
void print(int* a);                 //输出函数,输出到屏幕
void produceFood(int a[][COL]);        //产生食物
void moveSnake(int (*arr)[COL],int * fx);          //蛇的移动
int head_v = 4,head_h = 5;
    //蛇头的纵向坐标和横向坐标,用大于0的数做蛇,从蛇尾到蛇头的数依次增大,
int arr[ROW][COL];
int speed_snake = 5;  //蛇移动的速度,几次循环执行一次来控制
int fang_xiang = VK_RIGHT;//蛇移动的方向,默认向右
int exitgame = 0;

HANDLE hOutput,hOutBuf;  //控制台屏幕缓冲区句柄
HANDLE *houtpoint;
COORD coord = {1,0};
DWORD bytes = 0;
char data[ROW][COL];
int zhixiang_hOutput = 0;  //通过指针轮流指向两个缓冲区,实现双缓冲 
void main()
{
    int k;
    start(arr);
    produceFood(arr);
    puts("嗨~欢迎你来到贪吃蛇的世界!请君坐好了,要开车了~");
    Sleep(2000);  //休眠2秒
    system("cls");
    hOutBuf = CreateConsoleScreenBuffer(
        GENERIC_WRITE,  //定义进程可以往缓冲区写数据
        FILE_SHARE_WRITE, //定义缓冲区可共享写权限
        NULL,
        CONSOLE_TEXTMODE_BUFFER,
        NULL
    );
    hOutput = CreateConsoleScreenBuffer(
        GENERIC_WRITE,  //定义进程可以往缓冲区写数据
        FILE_SHARE_WRITE, //定义缓冲区可共享写权限
        NULL,
        CONSOLE_TEXTMODE_BUFFER,
        NULL
    );
    SetConsoleTitle(TEXT("贪吃蛇"));
	SMALL_RECT rc =  {0,0,COL+10,ROW+4};  //设置窗口大小,宽度和高度
	SetConsoleWindowInfo(hOutput,1,&rc);
	SetConsoleWindowInfo(hOutBuf,1,&rc);
    //隐藏两个缓冲区的光标
    CONSOLE_CURSOR_INFO cci;
    cci.bVisible = 0;
    cci.dwSize =1;
    SetConsoleCursorInfo(hOutput, &cci);
    SetConsoleCursorInfo(hOutBuf, &cci);
    while(1)
    {
        if(kbhit())
        {
            fangXiangChoice(arr);
            moveSnake(arr,&fang_xiang);
            print(arr[0]);
         }
       if(k++ % speed_snake == 0)
        {
             moveSnake(arr,&fang_xiang);
             print(arr[0]);
        }Sleep(40);
    }
}
void fangXiangChoice(int arr[][COL])
{
  	int key1,key2;
	key1 = getch();
	if(key1 == 27)//ESC建退出
 		exitgame = 1;
 	if(key1 == 224)
  	{
            key2 = getch();
		switch(key2)
  		{
            case VK_UP: if(fang_xiang != VK_DOWN) fang_xiang = VK_UP;  //如果在向下移动,那蛇就不能向上移动,下面的相同
                break;
            case VK_DOWN: if(fang_xiang != VK_UP) fang_xiang = VK_DOWN;
                break;
            case VK_LEFT: if(fang_xiang != VK_RIGHT) fang_xiang = VK_LEFT;
                break;
            case VK_RIGHT: if(fang_xiang != VK_LEFT) fang_xiang = VK_RIGHT;
    	}
	}
}
void start(int p[][COL])
{
    int i,j;
    p[head_v][head_h] = 2; //初始化蛇头,蛇身
    p[head_v][head_h-1] = 1;
    for(i = 0; i < ROW; i++)
    {
       for(j = 0; j < COL; j++)
       {
           if(i == 0 || i == ROW-1 || j == 0 || j == COL-1)
                p[i][j] = WALL;     //初始化墙
       }
    }
}
void produceFood(int a[][COL])
{
    int food_v,food_h;
    srand(time(NULL));
    food_v = rand() % ROW;
    food_h = rand() % COL;
    while(a[food_v][food_h] != 0)
    {
        food_v = rand() % ROW;
        food_h = rand() % COL;
    }
    a[food_v][food_h] = FOOD;
}
/**
//蛇头的纵向坐标和横向坐标,用大于0的数做蛇,从蛇尾到蛇头的数依次增大,
//碰到食物就把食物那个点的值变成蛇头值+1,食物的点变成蛇头,退出
//没有碰到食物,下一个点为空,就把下一个点的值变成蛇头值+1,
//在将整个数组大于0的值都减一,蛇尾为一的自然变为0,就消失了,其它情况直接退出
*/
void moveSnake(int (*arr)[COL],int * fx)  //根据传入的方向移动蛇
{
     int i,j,eat_food = 0; //为0表示没有吃到食物
     int temp = arr[head_v][head_h]; //记录现在蛇头的值
     if(*fx == VK_UP)
     {
         if(arr[head_v - 1][head_h] ==  0)    head_v--;
         else if(arr[head_v - 1][head_h] ==  FOOD)
              head_v--,eat_food = 1; //吃到食物了,蛇身不用减一
         else exitgame = 1;        //其它情况只有撞墙和吃蛇身
     }if(*fx  == VK_DOWN)
     {
         if(arr[head_v + 1][head_h] ==  0)    head_v++;
         else if(arr[head_v + 1][head_h] ==  FOOD)
              head_v++,eat_food =1;
        else exitgame = 1;
     }if(*fx  == VK_LEFT)
     {
         if(arr[head_v][head_h -1] ==  0)     head_h--;
         else if(arr[head_v][head_h -1] ==  FOOD)
              head_h--,eat_food = 1;
         else exitgame = 1;
     }if(*fx  == VK_RIGHT)
     {
         if(arr[head_v][head_h + 1] ==  0)    head_h++;
         else if(arr[head_v][head_h + 1] ==  FOOD)
              head_h++,eat_food = 1;
         else exitgame = 1;
     }
     /**没有吃的食物大于零的数都要减一*/
     arr[head_v][head_h] = temp+1;
     if(!eat_food)
     {
        for(i = 0; i < ROW; i++)
        {
            for(j = 0; j < COL; j++)
            {
                if(arr[i][j] > 0) arr[i][j] = arr[i][j] - 1;
            }
        }
     }
    else
        produceFood(arr); //吃到食物了,产生一个食物
}
void print(int* p)
{
    int i,j;
    char shuo_ming1[] = "请按方向键 ↑ ↓ ← → 的控制上下左右";
    char shuo_ming2[] = "连续按方向键会加速,退出按ESC键";
    for(i = 0; i < ROW; i++)
    {	
	   for(j = 0; j < COL; j++,p++)
       {
           if(*p == WALL)
                data[i][j] = '*'; 	//printf("■");    //墙墙
           if(*p == 0)
                data[i][j] = ' ';	 //printf(" ");
           if(*p > 0)       //蛇
           {
               if(i == head_v && j == head_h) data[i][j] = '0';//printf("⊙");
               else data[i][j] = '@';	//printf("●");
           }
           if(*p == FOOD)
               data[i][j] = '@'; 	//printf("●");
       }//printf("\n");
    }
    //以下是缓冲代码
   zhixiang_hOutput = !zhixiang_hOutput ;
    if(!zhixiang_hOutput)
    {
        houtpoint = &hOutput;
    }
    else
    {
        houtpoint = &hOutBuf;
    }
    coord.Y = 1;
    WriteConsoleOutputCharacterA(*houtpoint,shuo_ming1,strlen(shuo_ming1), coord,&bytes);
    coord.Y++; WriteConsoleOutputCharacterA(*houtpoint,shuo_ming2,strlen(shuo_ming2), coord,&bytes);
    for(i = 0; i < ROW; i++)
    {
        coord.Y++ ;
        WriteConsoleOutputCharacterA(*houtpoint,(char *)data[i],COL, coord,&bytes);
    }
    if(exitgame)
    {
    	char score[20];//得分为蛇吃的食物乘以3
		sprintf(score,"你的得分为:%d",(arr[head_v][head_h]-2)*3);
		coord.Y++;
		WriteConsoleOutputCharacterA(*houtpoint,score,strlen(score), coord,&bytes);
		SetConsoleActiveScreenBuffer(*houtpoint);
		getch();exit(0);
    }
    SetConsoleActiveScreenBuffer(*houtpoint);
}

  • 30
    点赞
  • 182
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值