贪吃蛇大作战【纯c语言】

news2024/7/6 19:46:57

如果有看到不懂的地方或者对c语言某些知识忘了的话,可以找我之前的文章哦!!!

个人主页:小八哥向前冲~-CSDN博客

所属专栏:c语言_小八哥向前冲~的博客-CSDN博客

贪吃蛇游戏演示:

贪吃蛇游戏动画演示

目录

游戏前期准备:

设置控制台相关信息

GetStdHanle

GetConsoleCursorInfo

SetConsoleCursorInfo

SetConsoleCursorPosition

GetAsynckeyState

贪吃蛇游戏设计与分析

本地化

地图,食物和蛇身设计

游戏的初始化

打印欢迎界面

绘制贪吃蛇地图

初始化蛇

初始化食物

游戏的运行

打印帮助信息

贪吃蛇的运行

游戏的结束

贪吃蛇的总代码


游戏前期准备:

需要注意的是,纯使用c语言实现贪吃蛇会使用到一些Win32 API知识,接下来我们一一介绍学习一下。

那么什么是Win32 API呢?

介绍:

Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外,它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便 称之为ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows 32位平台的应⽤程序编程接口。

设置控制台相关信息

我们知道平常我们运行程序弹出来的那个框框就是控制台终端。(如图)

我们可以使用cmd命令来设置控制台窗口的大小。如:

//         列       行
mode con cols=100 lines=30

值得注意的是:

1.使用这个命令之前,需要把这个控制台改为让Windows决定或Windows 控制台主机。

2.使用system函数所需要的头文件既可以是stdlib.h,也可以是Windows.h(不区分大小,也可以使用windows.h或WINDOWS.H等形式引用头文件。

演示一下:

控制台的改变

同样我们也能通过命令来设置控制台名字

title 贪吃蛇

效果:

控制台上的坐标COORD

COORD是什么呢?其实它是Windows API中定义的结构体,表示一个字符在控制台屏幕缓冲区上的坐标,而坐标系(0,0)的原点位于缓冲区的顶部左端单元格。

COORD的结构体声明:

typedef struct _COORD
{
     SHORT X;
     SHORT Y;
}COORD,*PCOORD;

坐标赋值:

COORD pos={20,30};

GetStdHanle

GetStdHanle是一个Windows API函数。它用于从一个特定的标准设备(标准输入,标准输出等)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备

注意:标准输入是指键盘,标准输出指的是屏幕。

我们要知道的是只要得到的这个句柄,咋们就能操控设备。所以我们能用GetStdHanle函数来获得句柄,从而进行一系列操作。

我们来看看这个函数:

那么我们来尝试获取句柄:

HANDLE houtput=NULL;
//从标准输出获取句柄
houtput=GetStdHanle(STD_OUTPUT_HANDLE);

我们在程序运行跳出控制台的时候,是不是有一个光标在闪动?那么我们试想一下,倘若我们不将那个光标隐藏的话,蛇在移动的时候就会有一个光标一直在闪动,不美观。那么我们如何隐藏光标呢?接下来就要用到GetConsoleCursorInfo这个函数。

GetConsoleCursorInfo

同样的我们来看看这个函数的语法:

从中我们知道,这个函数就是用来检索控制台屏幕缓冲区的光标大小和光标可见性的信息

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
//注意:PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO 结构的指针,该结构接受有关主机游标

CONSOLE_CURSOR_INFO 这个结构体这个结构体它是包含了光标信息。

我们来看看它的相关信息:

我们来使用一下:

 //获取标准输出的句柄(用来标识不同设备的数值)
 HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

 //影藏光标操作
 CONSOLE_CURSOR_INFO CursorInfo;
 GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

SetConsoleCursorInfo

那么我们得到了光标信息,下一步就是设置我们想要的光标信息,设置光标相关信息的函数其实就是SetConsoleCursorInfo函数,它是用来设置控制台指定控制台屏幕缓冲区的光标大小和可见性

相关信息:

我们来使用看看:


    //获取标准输出的句柄(用来标识不同设备的数值)
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

    //影藏光标操作
    CONSOLE_CURSOR_INFO CursorInfo;
    GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    CursorInfo.bVisible = false; //隐藏控制台光标
    //设置光标状态
    SetConsoleCursorInfor(hOutput,&CursorInfo);

SetConsoleCursorPosition

设置好了光标的状态,那么能不能设置光标位置,让我们能在任意位置打印我们想要的信息呢?那么我们就要用到SetConsoleCursorPosition函数。我们将坐标位置放到COORD类型中,然后调用SetConsoleCursorPosition函数就能将光标设置指定位置。

看看详情:

使用一下:

    COORD pos = { 10, 20 };
    HANDLE hOutput = NULL;
    //获取标准输出的句柄(用来标识不同设备的数值)
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置标准输出上光标的位置为pos
    SetConsoleCursorPosition(hOutput, pos);

需要注意的是:这个pos位置可能会设置不成功。

GetAsynckeyState

玩过贪吃蛇游戏的都知道,键盘上的上,下,左,右按键来控制蛇的方向。那么我们如何获取玩家是否按了哪个按键呢?GetAsynckeyState函数就能解决这个问题。

老样子,我们来看看它的详情:

详细解释:将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1

虚拟按键代码详细见:虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

我们来使用看看:


#define KEY_PRESS(VK)  ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
 int main()
 { 
    while (1)
    {
        if (KEY_PRESS(0x30))
        {
            printf("0\n");
        }
        else if (KEY_PRESS(0x31))
        {
            printf("1\n");
        }
        else if (KEY_PRESS(0x32))
        {
            printf("2\n");
        }
        else if (KEY_PRESS(0x33))
        {
            printf("3\n");
        }
        else if (KEY_PRESS(0x34))
        {
            printf("4\n");
        }
        else if (KEY_PRESS(0x35))
        {
            printf("5\n");
        }
     }
}

好了,我们现在正式开始写游戏逻辑吧!!!

贪吃蛇游戏设计与分析

要实现的功能:

  • 贪吃蛇地图绘制
  • 蛇的动作(上 ,下,左,右方向按键控制蛇的动作)
  • 蛇撞墙死亡
  • 帮助信息的打印
  • 计算得分
  • 蛇加速,减速
  • 暂停游戏

我们可以打印墙体用宽字符:□,蛇身体用:●,食物用:★。

我们来科普一下宽字符:普通字符是占一个字节,而宽字符占2个字节。

这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。 C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语 国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符 号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编入新的符 号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体 系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪 怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希 伯来语编码中却代表了字⺟Gimel( ),在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。 后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

而打印宽字符需要本地化。

本地化

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:

• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm()

• LC_CTYPE:影响字符处理函数的⾏为。

• LC_MONETARY:影响货币格式。

• LC_NUMERIC:影响 printf() 的数字格式。

• LC_TIME:影响strftime() 和 wcsftime() 。

• LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

setlocale函数:

 char* setlocale (int category, const char* locale);

setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。 setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。 在任意程序执⾏开始,都会隐藏式执⾏调用:

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常⽅式执⾏,小数点是⼀个点。 当程序运行起来后想改变地区,就只能显示调用setlocale函数。用""作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。 比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

setlocale(LC_ALL, " ");//切换到本地环境
 

那如果想在屏幕上打印宽字符,怎么打印呢?宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引 号前⾯,表示宽字符,对应 wprintf() 的占位符为 wprintf() 的占位符为 %ls

我们来举例一下:

#include<locale.h>
#include<stdio.h>
int main()
{
    //修改当前地区为本地模式,为了支持中文宽字符的打印
    setlocale(LC_ALL, "");
    wprintf(L"%s\n",L"小八哥向前冲");
    return 0;
}

地图,食物和蛇身设计

我们创建一个地图:27行,58列。再围绕这个地图画出墙。

由上图,我们不难知道一行的宽度是一列宽的两倍,只要注意这个咋们就能轻易画出强来!

初始化状态:我们可以将蛇的身体设为5,每个节点为宽字符●,在固定的一个坐标处开始,我们这里假设在(24,5)处开始打印5个蛇身节点。值得注意的是:蛇每个节点的x坐标必须是2的倍数,否则可能出现蛇的某一个节点有一半出现在墙体,另一半出现在墙外。接下来就是食物,在墙内随机生成一个坐标(同样x坐标是2的倍数),再者坐标不能和蛇身体重合,才能打印★。

在游戏运行的时候,蛇每吃一个食物,蛇身就变长一节,这里我们使用链表存储蛇节点。每个节点记录蛇身节点在地图上的坐标以及指向下一个指针变量。蛇节点结构如下:

//蛇身节点
typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

我们刚刚分析了,要的变量不止于这些,还需要:指向蛇头的指针,初始时蛇的速度,蛇方向,食物,食物分数,总分,蛇的状态。但是这些变量比较分散也比较麻烦,我们可以同时创建另一个结构体来管理这些变量。

typedef struct Snake
{
    pSnakeNode _pSnake;//维护整条蛇的指针
    pSnakeNode _pFood;//维护食物的指针
    enum DIRECTION _Dir;//蛇头的方向默认是向右
    enum GAME_STATUS _Status;//游戏状态
    int _Socre;//当前获得分数
    int _Add;//默认每个食物10分
    int _SleepTime;//每走一步休眠时间
}Snake, * pSnake;

游戏的状态有:正常运行,正常退出,撞墙死亡,撞到自己死亡。

//游戏状态
enum GAME_STATUS
{
    OK,//正常运行
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF,//咬到自己
    END_NOMAL//正常结束
};

蛇的方向:向上,向下,向左,向右。

//方向
enum DIRECTION
{
    UP = 1,//上
    DOWN,//下
    LEFT,//左
    RIGHT//右
};

现在,我们正式设计游戏逻辑。

为了使游戏逻辑梳理更加清晰,我们封装三个大函数:GameStart()——游戏初始化,GameRun()——游戏运行,GameEnd()——游戏结束。

游戏的初始化

初始化的内容:

  1. 打印欢迎界面

  2. 绘制贪吃蛇身体

  3. 初始化贪吃蛇相关变量和食物

打印欢迎界面

我们首先分装函数来定位坐标:

//设置光标的坐标
void SetPos(short x, short y)
{
    COORD pos = { x, y };
    HANDLE hOutput = NULL;
    //获取标准输出的句柄(用来标识不同设备的数值)
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置标准输出上光标的位置为pos
    SetConsoleCursorPosition(hOutput, pos);
}

而system("pause")是一个用于暂停程序运行的函数。它会出现一个提示信息,直到用户按下任意键程序才会继续运行。

如:

而由上一个欢迎界面跳到这个界面,我们需要清理一下屏幕,要用到清理控制台界面函数。

//屏幕清理
system("cls");

打印欢迎界面:

void WelcomeToGame()
{
    SetPos(40, 15);
    printf("欢迎来到贪吃蛇小游戏");
    SetPos(42, 17);
    printf("@小八哥向前冲");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
    SetPos(25, 12);
    printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    SetPos(25, 13);
    printf("加速将能得到更高的分数。\n");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
}

绘制贪吃蛇地图

我们先来看看地图:

由于墙体是宽字符,我们先将c语言环境转化到本地环境,再利用wprintf打印宽字符。

先打印上,下两行,再打印两列:上一行坐标(2*i,0),i 的范围:0~28。下一行坐标(2*i,25),i 的范围0~28。左一列坐标(0,i),i 的范围:1~25。 右一列坐标:(56,i ),i 范围:1~25。

注意:打印的时候要准确定位好我们的坐标,由于打印的时候默认是从左向右,而我们要从上到下

#define WALL L'□'

//绘制地图
void CreateMap()
{
    int i = 0;
    //上(0,0)-(56, 0)
    SetPos(0, 0);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }
    //下(0,26)-(56, 26)
    SetPos(0, 26);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }
    //左
    //x是0,y从1开始增长
    for (i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%c", WALL);
    }
    //x是56,y从1开始增长
    for (i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%c", WALL);
    }
}

初始化蛇

我们得先创建5个节点,并且初始化好节点坐标,然后将这些节点串起来就行!

 //蛇的初始位置
#define POS_X 24
#define POS_Y 5

//创建蛇身的节点
 cur = (pSnakeNode)malloc(sizeof(SnakeNode));
 if (cur == NULL)
 {
     perror("InitSnake()::malloc()");
     return;
 }
 //设置坐标
 cur->next = NULL;
 cur->x = POS_X + i * 2;
 cur->y = POS_Y;

 //头插法
 if (ps->_pSnake == NULL)
 {
     ps->_pSnake = cur;
 }
 else
 {
     cur->next = ps->_pSnake;
     ps->_pSnake = cur;
 }

然后打印蛇:

#define WALL L'□'
#define BODY L'●'  
#define FOOD L'★'   
//打印蛇的身体
  cur = ps->_pSnake;
  while (cur)
  {
      SetPos(cur->x, cur->y);
      wprintf(L"%c", BODY);
      cur = cur->next;
  }

设置蛇属性:

 //初始化贪吃蛇数据
 ps->_SleepTime = 200;
 ps->_Socre = 0;
 ps->_Status = OK;
 ps->_Dir = RIGHT;
 ps->_Add = 10;

初始化食物

既然创建好了蛇,就差食物了,我们首先创建一个节点给食物,然后将食物打印出来。

这个食物的x坐标必须是2的倍数,也要再在墙体中,且这个食物随机生成。

void CreateFood(pSnake ps)
{
    int x = 0;
    int y = 0;

again:
    //产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐。
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 25 + 1;
    } while (x % 2 != 0);

    pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
    //食物不能和蛇身冲突
    while (cur)
    {
        if (cur->x == x && cur->y == y)
        {
            goto again;
        }
        cur = cur->next;
    }

    pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物
    if (pFood == NULL)
    {
        perror("CreateFood::malloc()");
        return;
    }
    else
    {
        pFood->x = x;
        pFood->y = y;
        SetPos(pFood->x, pFood->y);
        wprintf(L"%c", FOOD);
        ps->_pFood = pFood;
    }
}

游戏的运行

游戏运行时我们需要做的事:

  1. 打印帮助信息
  2. 贪吃蛇运行信息
  3. 判断蛇的状态

游戏运行的界面:

打印帮助信息

void PrintHelpInfo()
{
    //打印提示信息
    SetPos(64, 15);
    printf("不能穿墙,不能咬到自己\n");
    SetPos(64, 16);
    printf("用↑.↓.←.→分别控制蛇的移动.");
    SetPos(64, 17);
    printf("F1 为加速,F2 为减速\n");
    SetPos(64, 18);
    printf("ESC :退出游戏.space:暂停游戏.");
    SetPos(64, 20);
    printf("小八哥向前冲@版权");
}

贪吃蛇的运行

蛇的运行,我们需要自己按键去控制蛇的方向。我们需要判断哪个按键是否摁过判断蛇的方向,只要蛇的状态不是OK,此时就不需要走了。

void GameRun(pSnake ps)
{
    //打印右侧帮助信息
    PrintHelpInfo();
    do
    {
        SetPos(64, 10);
        printf("得分:%d ", ps->_Socre);
        printf("每个食物得分:%d分", ps->_Add);
        if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
        {
            ps->_Dir = UP;
        }
        else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
        {
            ps->_Dir = DOWN;
        }
        else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
        {
            ps->_Dir = LEFT;
        }
        else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
        {
            ps->_Dir = RIGHT;
        }
        else if (KEY_PRESS(VK_SPACE))
        {
            pause();
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            ps->_Status = END_NOMAL;
            break;
        }
        else if (KEY_PRESS(VK_F3))
        {
            if (ps->_SleepTime >= 50)
            {
                ps->_SleepTime -= 30;
                ps->_Add += 2;
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            if (ps->_SleepTime < 350)
            {
                ps->_SleepTime += 30;
                ps->_Add -= 2;
                if (ps->_SleepTime == 350)
                {
                    ps->_Add = 1;
                }
            }
        }
        //蛇每次一定之间要休眠的时间,时间短,蛇移动速度就快
        Sleep(ps->_SleepTime);
        SnakeMove(ps);
        KillByWall(ps);
        KillBySelf(ps);
    } while (ps->_Status == OK);
}

而暂停函数的实现只需要一直休眠就行,直到按了空格就跳出休眠。

void pause()//暂停
{
    while (1)
    {
        Sleep(300);
        if (KEY_PRESS(VK_SPACE))
        {
            break;
        }
    }
}

通过蛇头的下一个位置和蛇身以及释放蛇的尾节点,如果下一个位置是食物,就要吃掉食物,且再创建一个食物,如果不是食物,就正常走就行,所以我们走一步就需要判断是否为食物。

void SnakeMove(pSnake ps)
{
    //创建下一个节点
    pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (pNextNode == NULL)
    {
        perror("SnakeMove()::malloc()");
        return;
    }
    //确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定
    switch (ps->_Dir)
    {
    case UP:
    {
        pNextNode->x = ps->_pSnake->x;
        pNextNode->y = ps->_pSnake->y - 1;
    }
    break;
    case DOWN:
    {
        pNextNode->x = ps->_pSnake->x;
        pNextNode->y = ps->_pSnake->y + 1;
    }
    break;
    case LEFT:
    {
        pNextNode->x = ps->_pSnake->x - 2;
        pNextNode->y = ps->_pSnake->y;
    }
    break;
    case RIGHT:
    {
        pNextNode->x = ps->_pSnake->x + 2;
        pNextNode->y = ps->_pSnake->y;
    }
    break;
    }

    //如果下一个位置就是食物
    if (NextIsFood(pNextNode, ps))
    {
        EatFood(pNextNode, ps);
    }
    else//如果没有食物
    {
        NoFood(pNextNode, ps);
    }
}

下一个位置是食物就吃掉食物,再创建新食物

void EatFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    ps->_Socre += ps->_Add;

    free(ps->_pFood);
    CreateFood(ps);
}

下一个不是食物

void NoFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur->next->next)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }

    //最后一个位置打印空格,然后释放节点
    SetPos(cur->next->x, cur->next->y);
    printf("  ");
    free(cur->next);
    cur->next = NULL;
}

注意:将尾节点释放后,还需要将尾节点位置打印空格,否则蛇身只会越来越长。走一步,休眠一下,让我们知道蛇走到哪里了。

我们还需检测是否撞墙,是否撞墙检测头节点是否撞墙就行。

int KillByWall(pSnake ps)
{
    if ((ps->_pSnake->x == 0)
        || (ps->_pSnake->x == 56)
        || (ps->_pSnake->y == 0)
        || (ps->_pSnake->y == 26))
    {
        ps->_Status = KILL_BY_WALL;
        return 1;
    }
    return 0;
}

而检测是否撞到自己,只需要检测蛇头和某一个身体节点相撞(是否重合)就行。

int KillBySelf(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake->next;
    while (cur)
    {
        if ((ps->_pSnake->x == cur->x)
            && (ps->_pSnake->y == cur->y))
        {
            ps->_Status = KILL_BY_SELF;
            return 1;
        }
        cur = cur->next;
    }
    return 0;
}

游戏的结束

其实到这里基本的游戏就能运行起来了,只不过我们创建的节点需要释放掉,且玩家结束游戏的提示。

void GameEnd(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake;
    SetPos(24, 12);
    switch (ps->_Status)
    {
    case END_NOMAL:
        printf("您主动退出游戏\n");
        break;
    case KILL_BY_SELF:
        printf("您撞上自己了 ,游戏结束!\n");
        break;
    case KILL_BY_WALL:
        printf("您撞墙了,游戏结束!\n");
        break;
    }

    //释放蛇身的节点
    while (cur)
    {
        pSnakeNode del = cur;
        cur = cur->next;
        free(del);
    }
}

贪吃蛇的总代码

Snack.h文件

#pragma once
#include <windows.h>
#include <time.h>
#include <stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define KEY_PRESS(VK)  ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

//方向
enum DIRECTION
{
    UP = 1,
    DOWN,
    LEFT,
    RIGHT
};

//游戏状态
enum GAME_STATUS
{
    OK,//正常运行
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF,//咬到自己
    END_NOMAL//正常结束
};

#define WALL L'□'
#define BODY L'●'  //★○●◇◆□■
#define FOOD L'★'  //★○●◇◆□■

//蛇的初始位置
#define POS_X 24
#define POS_Y 5

//蛇身节点
typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

typedef struct Snake
{
    pSnakeNode _pSnake;//维护整条蛇的指针
    pSnakeNode _pFood;//维护食物的指针
    enum DIRECTION _Dir;//蛇头的方向默认是向右
    enum GAME_STATUS _Status;//游戏状态
    int _Socre;//当前获得分数
    int _Add;//默认每个食物10分
    int _SleepTime;//每走一步休眠时间
}Snake, * pSnake;



//游戏开始前的初始化
void GameStart(pSnake ps);

//游戏运行过程
void GameRun(pSnake ps);

//游戏结束
void GameEnd(pSnake ps);

//设置光标的坐标
void SetPos(short x, short y);

//欢迎界面
void WelcomeToGame();

//打印帮助信息
void PrintHelpInfo();

//创建地图
void CreateMap();

//初始化蛇
void InitSnake(pSnake ps);

//创建食物
void CreateFood(pSnake ps);

//暂停响应
void pause();

//下一个节点是食物
int NextIsFood(pSnakeNode psn, pSnake ps);

//吃食物
void EatFood(pSnakeNode psn, pSnake ps);

//不吃食物
void NoFood(pSnakeNode psn, pSnake ps);

//撞墙检测
int KillByWall(pSnake ps);

//撞自身检测
int KillBySelf(pSnake ps);

//蛇的移动
void SnakeMove(pSnake ps);

//游戏初始化
void GameStart(pSnake ps);

//游戏运行
void GameRun(pSnake ps);

//游戏结束
void GameEnd(pSnake ps);

Snack.c文件

#include"Snake.h"

//设置光标的坐标
void SetPos(short x, short y)
{
    COORD pos = { x, y };
    HANDLE hOutput = NULL;
    //获取标准输出的句柄(用来标识不同设备的数值)
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置标准输出上光标的位置为pos
    SetConsoleCursorPosition(hOutput, pos);
}

void WelcomeToGame()
{
    SetPos(40, 15);
    printf("欢迎来到贪吃蛇小游戏");
    SetPos(42, 17);
    printf("@小八哥向前冲");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
    SetPos(25, 12);
    printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    SetPos(25, 13);
    printf("加速将能得到更高的分数。\n");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
}

void CreateMap()
{
    int i = 0;
    //上(0,0)-(56, 0)
    SetPos(0, 0);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }
    //下(0,26)-(56, 26)
    SetPos(0, 26);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }
    //左
    //x是0,y从1开始增长
    for (i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%c", WALL);
    }
    //x是56,y从1开始增长
    for (i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%c", WALL);
    }
}


void InitSnake(pSnake ps)
{
    pSnakeNode cur = NULL;
    int i = 0;
    //创建蛇身节点,并初始化坐标
    //头插法
    for (i = 0; i < 5; i++)
    {
        //创建蛇身的节点
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("InitSnake()::malloc()");
            return;
        }
        //设置坐标
        cur->next = NULL;
        cur->x = POS_X + i * 2;
        cur->y = POS_Y;

        //头插法
        if (ps->_pSnake == NULL)
        {
            ps->_pSnake = cur;
        }
        else
        {
            cur->next = ps->_pSnake;
            ps->_pSnake = cur;
        }
    }

    //打印蛇的身体
    cur = ps->_pSnake;
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }

    //初始化贪吃蛇数据
    ps->_SleepTime = 200;
    ps->_Socre = 0;
    ps->_Status = OK;
    ps->_Dir = RIGHT;
    ps->_Add = 10;
}



void CreateFood(pSnake ps)
{
    int x = 0;
    int y = 0;

again:
    //产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐。
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 25 + 1;
    } while (x % 2 != 0);

    pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
    //食物不能和蛇身冲突
    while (cur)
    {
        if (cur->x == x && cur->y == y)
        {
            goto again;
        }
        cur = cur->next;
    }

    pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物
    if (pFood == NULL)
    {
        perror("CreateFood::malloc()");
        return;
    }
    else
    {
        pFood->x = x;
        pFood->y = y;
        SetPos(pFood->x, pFood->y);
        wprintf(L"%c", FOOD);
        ps->_pFood = pFood;
    }
}

void PrintHelpInfo()
{
    //打印提示信息
    SetPos(64, 15);
    printf("不能穿墙,不能咬到自己\n");
    SetPos(64, 16);
    printf("用↑.↓.←.→分别控制蛇的移动.");
    SetPos(64, 17);
    printf("F1 为加速,F2 为减速\n");
    SetPos(64, 18);
    printf("ESC :退出游戏.space:暂停游戏.");
    SetPos(64, 20);
    printf("小八哥向前冲@版权");
}

void pause()//暂停
{
    while (1)
    {
        Sleep(300);
        if (KEY_PRESS(VK_SPACE))
        {
            break;
        }
    }
}

//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(pSnakeNode psn, pSnake ps)
{
    return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}

//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    ps->_Socre += ps->_Add;

    free(ps->_pFood);
    CreateFood(ps);
}

//pSnakeNode psn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void NoFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur->next->next)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }

    //最后一个位置打印空格,然后释放节点
    SetPos(cur->next->x, cur->next->y);
    printf("  ");
    free(cur->next);
    cur->next = NULL;
}

//pSnake ps 维护蛇的指针
int KillByWall(pSnake ps)
{
    if ((ps->_pSnake->x == 0)
        || (ps->_pSnake->x == 56)
        || (ps->_pSnake->y == 0)
        || (ps->_pSnake->y == 26))
    {
        ps->_Status = KILL_BY_WALL;
        return 1;
    }
    return 0;
}

//pSnake ps 维护蛇的指针
int KillBySelf(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake->next;
    while (cur)
    {
        if ((ps->_pSnake->x == cur->x)
            && (ps->_pSnake->y == cur->y))
        {
            ps->_Status = KILL_BY_SELF;
            return 1;
        }
        cur = cur->next;
    }
    return 0;
}


void SnakeMove(pSnake ps)
{
    //创建下一个节点
    pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (pNextNode == NULL)
    {
        perror("SnakeMove()::malloc()");
        return;
    }
    //确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定
    switch (ps->_Dir)
    {
    case UP:
    {
        pNextNode->x = ps->_pSnake->x;
        pNextNode->y = ps->_pSnake->y - 1;
    }
    break;
    case DOWN:
    {
        pNextNode->x = ps->_pSnake->x;
        pNextNode->y = ps->_pSnake->y + 1;
    }
    break;
    case LEFT:
    {
        pNextNode->x = ps->_pSnake->x - 2;
        pNextNode->y = ps->_pSnake->y;
    }
    break;
    case RIGHT:
    {
        pNextNode->x = ps->_pSnake->x + 2;
        pNextNode->y = ps->_pSnake->y;
    }
    break;
    }

    //如果下一个位置就是食物
    if (NextIsFood(pNextNode, ps))
    {
        EatFood(pNextNode, ps);
    }
    else//如果没有食物
    {
        NoFood(pNextNode, ps);
    }

    KillByWall(ps);
    KillBySelf(ps);
}



void GameStart(pSnake ps)
{
    //设置控制台窗口的大小,30行,100列
    //mode 为DOS命令
    system("mode con cols=100 lines=30");
    //设置cmd窗口名称
    system("title 贪吃蛇");

    //获取标准输出的句柄(用来标识不同设备的数值)
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

    //影藏光标操作
    CONSOLE_CURSOR_INFO CursorInfo;
    GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    CursorInfo.bVisible = false; //隐藏控制台光标
    SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

    //打印欢迎界面
    WelcomeToGame();
    //打印地图
    CreateMap();
    //初始化蛇
    InitSnake(ps);
    //创造第一个食物
    CreateFood(ps);
}


void GameRun(pSnake ps)
{
    //打印右侧帮助信息
    PrintHelpInfo();
    do
    {
        SetPos(64, 10);
        printf("得分:%d ", ps->_Socre);
        printf("每个食物得分:%d分", ps->_Add);
        if (KEY_PRESS(VK_UP) && ps->_Dir != DOWN)
        {
            ps->_Dir = UP;
        }
        else if (KEY_PRESS(VK_DOWN) && ps->_Dir != UP)
        {
            ps->_Dir = DOWN;
        }
        else if (KEY_PRESS(VK_LEFT) && ps->_Dir != RIGHT)
        {
            ps->_Dir = LEFT;
        }
        else if (KEY_PRESS(VK_RIGHT) && ps->_Dir != LEFT)
        {
            ps->_Dir = RIGHT;
        }
        else if (KEY_PRESS(VK_SPACE))
        {
            pause();
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            ps->_Status = END_NOMAL;
            break;
        }
        else if (KEY_PRESS(VK_F3))
        {
            if (ps->_SleepTime >= 50)
            {
                ps->_SleepTime -= 30;
                ps->_Add += 2;
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            if (ps->_SleepTime < 350)
            {
                ps->_SleepTime += 30;
                ps->_Add -= 2;
                if (ps->_SleepTime == 350)
                {
                    ps->_Add = 1;
                }
            }
        }
        //蛇每次一定之间要休眠的时间,时间短,蛇移动速度就快
        Sleep(ps->_SleepTime);
        SnakeMove(ps);

    } while (ps->_Status == OK);
}

void GameEnd(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake;
    SetPos(24, 12);
    switch (ps->_Status)
    {
    case END_NOMAL:
        printf("您主动退出游戏\n");
        break;
    case KILL_BY_SELF:
        printf("您撞上自己了 ,游戏结束!\n");
        break;
    case KILL_BY_WALL:
        printf("您撞墙了,游戏结束!\n");
        break;
    }

    //释放蛇身的节点
    while (cur)
    {
        pSnakeNode del = cur;
        cur = cur->next;
        free(del);
    }
}

test.c文件

#include"Snake.h"
#include<locale.h>
void test()
{
    int ch = 0;
    srand((unsigned int)time(NULL));

    do
    {
        Snake snake = { 0 };
        GameStart(&snake);
        GameRun(&snake);
        GameEnd(&snake);
        SetPos(20, 15);
        printf("再来一局吗?(Y/N):");
        ch = getchar();
        getchar();//清理\n

    } while (ch == 'Y');
    SetPos(0, 27);
}

int main()
{
    //修改当前地区为本地模式,为了支持中文宽字符的打印
    setlocale(LC_ALL, "");
    //测试逻辑
    test();
    return 0;
}

好了!今天的贪吃蛇代码想必你看到这里已经恍然大悟了!下一期我们不见不散!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1627792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国产3D自研技术如何突围?眸瑞科技给3D建设、管理带来全新模式

眸瑞科技是全球领先的数字孪生引擎技术及服务提供商&#xff0c;它专注于让一切3D模型在全网多端轻量化处理与展示&#xff0c;为行业数字化转型升级与数字孪生应用提供成套的国产自研3D可视化技术、产品与服务。 引言 眸瑞科技是全球领先的数字孪生引擎技术及服务提供商&…

Zynq 7000 系列中的Interconnect(互联)简介

PS&#xff08;处理器子系统&#xff09;内部的互联结构包含了多个交换机&#xff0c;用于通过AXI点对点通道连接系统资源。这些通道负责在主机和从机客户端之间进行地址、数据和响应事务通信。该互联结构负责管理多个待处理的事务&#xff0c;并且为Arm CPU设计了低延迟路径&a…

Spark 基础

/* Why Spark一、MapReduce编程模型的局限性1、繁杂&#xff1a;只有Map和Reduce两个操作&#xff0c;复杂的逻辑需要大量的样板代码2、处理效率低&#xff1a;2.1、Map中间结果写磁盘&#xff0c;Reduce写HDFS&#xff0c;多个Map通过HDFS交换数据2.2、任务调度与启动开销大3、…

Unity对应的c#版本

本文主要是记录一下unity已经开始兼容c#的版本和.net版本&#xff0c;以便更好的利用c#的特性。 c#和.net对应情况 微软已经将.net开发到.net 9了&#xff0c;但是unity的迭代速度远没有c#迭代速度快&#xff0c;已知unity最新的LTS版本unity2023已经兼容了c#9 可以在unity手册…

汽车底盘域的学习笔记

前言&#xff1a;底盘域分为传统车型底盘域和新能源车型底盘域&#xff08;新能源系统又可以分为纯电和混动车型&#xff0c;有时间可以再研究一下&#xff09; 1&#xff1a;传统车型底盘域 细分的话可以分为四个子系统 传动系统 行驶系统 转向系统 制动系统 1.1传动系…

中电金信:向“新”而行——探索融合架构的项目管理在保险行业的应用

近年来&#xff0c;险企在政策推动、市场牵引、自身发展、新技术应用日趋成熟等内外部因素的驱动下&#xff0c;积极投身到数字化转型的浪潮中。在拜访各类保险客户和合作项目的过程中&#xff0c;我们发现不少险企在数字化转型中或多或少都面临着战略如何落地、技术如何承接和…

Java 基础常见面试题整理

目录 1、java的基本数据类型有哪些&#xff1f;2、java为什么要有包装类型&#xff1f;3、String a "123" 和 String a new String("123") 区别&#xff1f;4、String、StringBuilder和StringBuffer的区别&#xff1f;5、如何理解面向对象和面向过程&…

第72天:漏洞发现-Web框架中间件联动GobyAfrogXrayAwvsVulmap

案例一&#xff1a;某 APP-Web 扫描-常规&联动-Burp&Awvs&Xray Acunetix 一款商业的 Web 漏洞扫描程序&#xff0c;它可以检查 Web 应用程序中的漏洞&#xff0c;如 SQL 注入、跨站脚本攻击、身份验证页上的弱口令长度等。它拥有一个操作方便的图形用户界 面&#…

C++系列-输入输出

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” C输入和输出 我们都知道C语言的输出是用printf函数来实现的&#xff0c;那么C呢&#xff0c;它的实现逻辑是什么呢&#xff0c;让我们一起来看一下&#xff0c; #include<i…

miniTry:Python实现web搜索(全自动+程序操控)

声明&#xff1a;本问给出了全部代码--可以复现--亲测有效 :) [ 代码为图片--> 强制自己去敲一次 又不多] 1.打开网站&#xff1a; 2.利用id去定位到我们要进行输入的内容&#xff08;bing可以直接进行搜索&#xff0c;而csdn需要登录&#xff0c;所以我们用csdn做演示&…

【论文速读】|理解基于大语言模型的模糊测试驱动程序生成

本次分享论文&#xff1a;Understanding Large Language Model Based Fuzz Driver Generation 基本信息 原文作者&#xff1a;Cen Zhang, Mingqiang Bai, Yaowen Zheng, Yeting Li, Xiaofei Xie, Yuekang Li, Wei Ma, Limin Sun, Yang Liu 作者单位&#xff1a;南洋理工大学…

elasticsearch 常用语法汇总

文章目录 前言elasticsearch 常用语法汇总1. 创建索引2. 检索索引信息3. 删除索引4. 文档操作4.1. 对blog_new索引指定文档ID新增4.2. 对blog_new索引不指定文档ID新增&#xff0c;随机文档ID:4.3. 获取文档4.4. 更新文档4.5. 删除文档 5. 查询5.1. 匹配查询5.2. 范围查询5.3. …

掌握TypeScript,成为前端高手(AI写作一键生成免费)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

免费获取!遗传算法+多目标规划算法+自适应神经模糊系统程序代码!

前言 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世纪70年代提出&#xff0c;该算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型&#xff0c;通过数学的方式&#xff0c;将问题的求解过程转…

pyqt QSplitter控件

pyqt QSplitter控件 QSplitter控件效果代码 QSplitter控件 PyQt中的QSplitter控件是一个强大的布局管理器&#xff0c;它允许用户通过拖动边界来动态调整子控件的大小。这个控件对于创建灵活的、用户可定制的用户界面非常有用。 QSplitter控件可以水平或垂直地分割其包含的子…

靠生成式人工智能赚钱? 扎克伯格:再等几年吧

不用多说&#xff0c;AI人工智能就是2024年最热的技术&#xff0c;企业也希望通过AI技术大赚特赚。不过Meta CEO扎克伯格在公司2024年第一财季业绩会议上表示&#xff0c;从生成式人工智能中获利还需要几年时间。 R-C (1).jpg© 由 ITheat热点科技 提供 AI人工智能技术很多…

【数据结构与算法】:手搓顺序表(Python篇)

文章目录 一、顺序表的概念二、顺序表的实现1. 顺序表的创建1.1 扩容1.2 整体建立顺序表 2. 顺序表的基本运算算法2.1 顺序表的添加&#xff08;尾插&#xff09;2.2 指定位置插入2.3 指定位置删除2.4 顺序表的查找2.5 顺序表元素的索引访问2.6 顺序表元素的修改2.7 顺序表长度…

Java毕业设计 基于SpringBoot vue城镇保障性住房管理系统

Java毕业设计 基于SpringBoot vue城镇保障性住房管理系统 SpringBoot 城镇保障性住房管理系统 功能介绍 首页 图片轮播 房源信息 房源详情 申请房源 公示信息 公示详情 登录注册 个人中心 留言反馈 后台管理 登录 个人中心 修改密码 个人信息 用户管理 房屋类型 房源信息管理…

微信小程序:5.数据绑定

在Data中定义数据早wxml中进行数据使用 在data中定义数据 在页面对应的js对象中找到data&#xff0c;然后把数据进行定义即可 Page({data: {motto: Hello World,userInfo: {avatarUrl: defaultAvatarUrl,nickName: ,},hasUserInfo: false,canIUseGetUserProfile: wx.canIUse…

药房管理 T1072

#include<bits/stdc.h> using namespace std; int main(){int m,n;cin>>m>>n;int f[n];for(int i0;i<n;i)cin>>f[i];int count0;for(int i0;i<n;i){if(m>f[i]){mm-f[i];}else {count;}}cout<<count;return 0;}