简单贪吃蛇的实现

贪吃蛇的实现是再windows控制台上实现的,需要win32 API的知识

Win32 API-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/bkmoo/article/details/138698452?spm=1001.2014.3001.5501

游戏说明

●地图的构建

●蛇身的移动(使用↑ . ↓ . ← . → 分别控制蛇的移动)

●F3加速,F4减速

●吃食物加分(加速可获得更高的分数,减速食物分数下降)

●蛇撞墙(游戏结束)

●蛇咬到自己(游戏退出)

●游戏暂停(空格操作)

●游戏退出(ESC正常退出)

下面这张图片是游戏细化的实现过程

头文件的声明

创建Snake.h文件存放游戏函数的声明,蛇的结构,需要的头文件。


#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <locale.h>
#include <windows.h>
#include <stdbool.h>#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 0x1) ? 1 : 0)#define WALL L'□'
#define BOOY L'●'
#define FOOD L'★'//蛇的初始位置
#define POS_X 24
#define POS_Y 5//游戏状态
enum GAME_STATUS
{OK = 1,ESC,KILL_BY_WALL,  //撞墙wallKILL_BY_SELF   //撞到自己self
};//方向:上下左右
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;typedef struct Snake
{pSnakeNode pSnake;//维护贪吃蛇的指针,指向贪吃蛇的指针pSnakeNode pFood;//指向食物的指针int score; //当前累计的分数int FoodWeight; //当前食物的分数int SleepTime;//蛇休眠的时间enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前的走向
}Snake, *pSnake;//光标位置的定位
void Setpos(int x, int y);//一 游戏开始前的准备(初始化)
void GameStark(pSnake ps);//游戏欢迎界面
void welcometoGame();//绘制地图
void CreateMap();//初始化蛇
void InitSnake(pSnake ps);//生成食物
void CreateFood(pSnake ps);//二 游戏运行的整个逻辑
void GameRun(pSnake ps);//贪吃蛇移动函数, 每次一步
void SnakeMove(pSnake ps);//蛇的下一步的位置是食物,吃掉
void EntFood(pSnake ps, pSnakeNode pnext);//蛇的下一步的位置不是食物
void NotEntFood(pSnake ps, pSnakeNode pnext);//检测是否撞墙
void KillByWall(pSnake ps);//检测是否撞到自己
void KillBySelf(pSnake ps);//三 游戏结束
void GameEnd(pSnake ps);

游戏实现

将游戏的实现分割成三个大块,分别是

1、游戏开始前的初始化

    GameStark(&snake);

2、游戏过程的实现
    GameRun(&snake);

3、游戏结束的善后工作
    GameEnd(&snake);

一、游戏地图的实现

想要实现地图,这里就要控制台窗⼝的⼀些知识,如果想在控制台的窗⼝中指定位置输出信息,我们得知道 该位置的坐标,所以⾸先介绍⼀下控制台窗⼝的坐标知识。 控制台窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓。

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★ ,普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节

注意:打印的中文符号的宽字符,因此在使用之前需要本地化

附加:

C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊和宽字符的类型wchar_t 和宽字符的输⼊和输出函数,加⼊和<locale.h>头⽂件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

<locale.h>本地化

<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准可以中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式

类项:

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

setlocale函数

setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参
数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"和" "。
在任意程序执⾏开始,都会隐藏式执⾏调⽤:
1 setlocale (LC_ALL, "C" );
当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。⽐如:切换到我们的本地模式后就⽀ 持宽字符(汉字)的输出等。
1 setlocale (LC_ALL, " " ); // 切换到本地环境
上述介绍完成后开始地图的构建

地图的构建

假设设置的地图是一个棋盘,棋盘的大小是58列27行。
C语言的特点,一行跟两列的长度相当,因此设置列大约是行的两倍。
一定要修改环境,不然打印出的是问号
//修改适配中文环境
setlocale(LC_ALL, "");
创建地图函数CreateMap()
void CreateMap()
{int i = 0;//地图的上Setpos(0, 0);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);//打印宽字符用%lc,wprintf(L"%lc", L'□');}//下Setpos(0, 25);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i < 25; i++){Setpos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 25; i++){Setpos(56, i);wprintf(L"%lc", WALL);}
}

二、蛇身和食物

初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半 ⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。
关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然 后打印★。

蛇身的构建

关于蛇身,使用链表来维护,结构体成员分别为x坐标,y坐标,和下一节点
typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
创建struct SnakeNode结构体,并typedef命名为SnakeNode,struct SnakeNode*命名为pSnakeNode。

关于整条贪吃蛇的维护

要管理SnakeNode就还要创建一个Snake结构体,里面包含整个贪吃蛇的信息。

typedef struct Snake
{
    pSnakeNode pSnake;//维护贪吃蛇的指针,指向贪吃蛇的指针
    pSnakeNode pFood;//指向食物的指针
    int score; //当前累计的分数
    int FoodWeight; //当前食物的分数
    int SleepTime;//蛇休眠的时间
    enum GAME_STATUS status;//游戏当前的状态
    enum DIRECTION dir;//蛇当前的走向
}Snake, *pSnake;

这里使用了枚举,分别是GAME_STATUS表示游戏状态,DIRECTION表示蛇当前的走向。

//游戏状态
enum GAME_STATUS
{
    OK = 1,
    ESC,
    KILL_BY_WALL,  //撞墙wall
    KILL_BY_SELF   //撞到自己self
};

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

三、游戏开始界面

有了前面的准备工作后,就可以开始包装游戏了。进入游戏要有开始界面吧,这里就用到了Win32 API。

创建贪吃蛇Snake snake

创建游戏的初始化函数 GameStark();

1.GameStark()

void GameStark(pSnake ps)
{
    //设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列  设置cmd窗⼝名称
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");

    //隐藏光标
    CONSOLE_CURSOR_INFO cursor_info = { 0 };
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleCursorInfo(handle, &cursor_info);//获取控制台光标信息
    cursor_info.bVisible = false;//false是bool类型 头文件 stdbool.h,隐藏光标.
    SetConsoleCursorInfo(handle, &cursor_info);

    //打印欢迎信息
    welcometoGame();

    //绘制游戏地图
    CreateMap();
    //初始化蛇
    InitSnake(ps);
    //生成食物
    CreateFood(ps);
}

welcometoGame();

创建函数 welcometoGame();打印欢迎信息

void welcometoGame()
{
    Setpos(35, 15);
    printf("欢迎来到贪吃蛇小游戏\n");
    
    Setpos(36, 25);
    system("pause");
    system("cls");//清理屏幕!!!

    Setpos(15, 10);
    printf("使用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    Setpos(30, 11);
    printf("加速获得更高的分数\n");
    Setpos(36, 25);
    system("pause");
    system("cls");
}

Setpos()

在欢迎信息函数里有Setpos函数,这个是位置定位函数,可以将光标定位到需要的位置

//定位光标信息
void Setpos(int x, int y)
{

    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { x, y };
    //设置光标位置
    SetConsoleCursorPosition(handle, pos);
}

InitSnake()

创建蛇的初始化函数

void InitSnake(pSnake ps)
{

    int i = 0;
    for (i = 0; i < 5; i++)
    {
        //创建蛇身的五个节点
        pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("InitSnake()::malloc");
            return;
        }
        cur->x = POS_X + 2 * i;
        cur->y = POS_Y;
        cur->next = NULL;

        //头插法
        if (ps->pSnake == NULL)
        {
            ps->pSnake = cur;
        }
        else
        {
            cur->next = ps->pSnake;
            ps->pSnake = cur;
        }
    }
    //打印蛇身
    pSnakeNode cur = ps->pSnake;
    while (cur)
    {
        Setpos(cur->x, cur->y);
        wprintf(L"%lc", BOOY);
        cur = cur->next;
    }


    //蛇的其他信息初始化
    ps->dir = RIGHT;//初始化方向为右
    ps->FoodWeight = 10;//初始化食物
    ps->pFood = NULL;
    ps->SleepTime = 200;//时间间隔200毫秒
    ps->status = OK;//游戏状态OK
    ps->score = 0; //当前累计的分数
}

使用头插法创建链表,使得从头节点开始可以依次向后找到每个节点。然后初始化其他信息和答应你蛇身。

 CreateFood()

创建食物生成函数

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

again:
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 24 + 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(pSnakeNode));
    if (pFood == NULL)
    {
        perror("CreateFood()::malloc");
        return;
    }
    pFood->x = x;
    pFood->y = y;

    //打印食物
    Setpos(pFood->x, pFood->y);
    wprintf(L"%lc", FOOD);
    ps->pFood = pFood;
}

需要考虑到食物不能创建在地图外,不能创建在蛇身上。这里用到了goto语句,遍历蛇身节点。

2.游戏过程的实现

创建GameRun()函数,用来实现游戏的逻辑,里面有帮助信息,按键的检测蛇的下一步

GameRun()

//二 游戏逻辑实现
void GameRun(pSnake ps)
{
    //打印帮助信息
    PrintHelpInfo();

    //循环
    do
    {
        Setpos(60, 10);
        printf("总分:%5d", ps->score);
        Setpos(60, 11);
        printf("当前食物的分值:%.2d", ps->FoodWeight);
        
        //检测按键
        //上下左右 ESC 空格 F3 F4
        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 != RIGHT)
        {
            ps->dir = RIGHT;
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            ps->status = ESC;
        }
        else if (KEY_PRESS(VK_SPACE))
        {
            //空格游戏暂停和恢复
            pause();
        }
        else if (KEY_PRESS(VK_F3))
        {
            if(ps->SleepTime > 80)
            {
                ps->SleepTime -= 30;
                ps->FoodWeight += 2;
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            if (ps->FoodWeight > 2)
            {
                ps->SleepTime += 30;
                ps->FoodWeight -= 2;
            }
        }

        //睡眠一下
        Sleep(ps->SleepTime);

        //下一步
        SnakeMove(ps);

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

}

在Win32 API这篇文章中已经介绍了GetAsyncKeyState()函数,并且宏定义,这里直接使用

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk) & 0x1) ? 1 : 0)

使用游戏状态GAME_STATUS来判定循环是否继续,以此来实现只要游戏正常进行,按键就一直检测。

并且在进行F3 F4按键检测后,需要实现加速和食物分数增加,减速和食物分数减少。

故用 ps->SleepTime 加减30,ps->FoodWeight 减加2实现。同时还要考虑,睡眠时间不能为0,食物分数不能为0。

PrintHelpInfo()

创建帮助信息函数,打印帮助信息

void PrintHelpInfo()
{
    Setpos(60, 14);
    printf("1. 不能穿墙,不能咬到自己");
    Setpos(60, 15);
    printf("2. 使用↑ ↓ ← → 分别控制蛇的移动");
    Setpos(60, 16);
    printf("3. F3为加速,F4为减速");
    Setpos(60, 17);
}

pause();

创建暂停函数,使用空格游戏暂停,再次按下游戏继续。

void pause()
{
    while (1)
    {
        Sleep(100);
        if (KEY_PRESS(VK_SPACE))
        {
            break;
        }
    }
}

这里使用死循环Sleep来实现游戏的暂停,并且使用按键检测,一旦检测到空格,就会break退出。

SnakeMove()

此时到了一个非常关键的地方,就是蛇的移动函数,也就是蛇的下一步。

创建一个新的节点pnext作为下一步。

考虑蛇的下一步时需要考虑方向的问题,比如当你向上走时不能向下走吧,就是不能向与当前蛇的走向的相反放向。

还要考虑蛇的下一步是否是食物这个问题,如果是食物就要吃掉,吃掉后就要加分,蛇身加长

下一步不是食物,就要维持原长。

还要考虑下一步是否撞墙,否则游戏结束。

下一步是否咬到自己,否则游戏结束。


void SnakeMove(pSnake ps)
{
    //创建下一个节点
    pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
    pnext->next = NULL;

    switch (ps->dir)
    {
    case UP:
        pnext->x = ps->pSnake->x;
        pnext->y = ps->pSnake->y - 1;
        break;
    case DOWN:
        pnext->x = ps->pSnake->x;
        pnext->y = ps->pSnake->y + 1;
        break;
    case LEFT:
        pnext->x = ps->pSnake->x - 2;
        pnext->y = ps->pSnake->y;
        break;
    case RIGHT:
        pnext->x = ps->pSnake->x + 2;
        pnext->y = ps->pSnake->y;
        break;
    }
    
    //需要考虑下一步是否是食物,如果是食物则蛇身加长,否则不变
    if (pnext->x == ps->pFood->x && pnext->y == ps->pFood->y)
    {
        EntFood(ps, pnext);
    }
    else
    {
        NotEntFood(ps, pnext);
    }

    //检测是否撞墙
    KillByWall(ps);

    //检测是否撞到自己
    KillBySelf(ps);

}

EntFood()

下一步是食物

void EntFood(pSnake ps, pSnakeNode pnext)
{
    //头插
    pnext->next = ps->pSnake;
    ps->pSnake = pnext;

    //打印蛇
    pSnakeNode cur = ps->pSnake;
    while (cur)
    {
        Setpos(cur->x, cur->y);
        wprintf(L"%lc", BOOY);
        cur = cur->next;
    }

    //释放被吃掉的食物
    free(ps->pFood);
    //新生成食物
    CreateFood(ps);

    //吃到了食物,更新总分数
    ps->score += ps->FoodWeight;

}

这里直接将下一步的节点,进行头插。

食物被吃掉后要释放旧的食物节点,还要生成新的食物,吃掉食物后要更新分数。

NotEntFood()

下一步不是食物

void NotEntFood(pSnake ps, pSnakeNode pnext)
{
    //头插
    pnext->next = ps->pSnake;
    ps->pSnake = pnext;

    //因为要保持原长度,所以要释放尾节点,找到倒数第二个节点
    pSnakeNode cur = ps->pSnake;
    while (cur->next->next)
    {
        cur = cur->next;
    }
    //找到最后一个节点的位置,将旧的信息(图标)覆盖,  
    Setpos(cur->next->x, cur->next->y);
    //两个空格(宽字符)
    printf("  ");

    //释放尾节点
    free(cur->next);
    cur->next = NULL;
    
    //打印蛇身
    cur = ps->pSnake;
    while (cur)
    {
        Setpos(cur->x, cur->y);
        wprintf(L"%lc", BOOY);
        cur = cur->next;
    }

}//蛇移动的过程就是把最后一个节点释放掉,再插入到头部,打印出来

下一步不是食物,头插下一步的节点,因为要维持原长度,所以要释放最后一个节点,使用cur->next->next找到倒数第二个节点,来操作尾节点。

尾节点要释放,还要再尾节点的位置进行覆盖,要打印两个空格覆盖释放的蛇身图标,避免拖尾

KillByWall()

检测蛇是否撞墙,如果撞墙就更新游戏状态,ps->status = KILL_BY_WALL

void KillByWall(pSnake ps)
{
    if (ps->pSnake->x == 0 ||
        ps->pSnake->x == 56 ||
        ps->pSnake->y == 0 ||
        ps->pSnake->y == 25)
    {
        ps->status = KILL_BY_WALL;
        return;
    }
}

只要判断蛇头的坐标即可,头节点的x坐标是否0或56,y坐标是否0或25

KillBySelf()

是否咬到自己,咬到自己更新游戏状态ps->status = KILL_BY_SELF

void KillBySelf(pSnake ps)
{
    pSnakeNode cur = ps->pSnake->next;
    while (cur)
    {
        if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
        {
            ps->status = KILL_BY_SELF;
            return;
        }
        cur = cur->next;
    }
}

遍历节点坐标是否相同即可。

3.游戏结束的善后工作

游戏结束了,总要有些结束语吧。而且使用malloc动态开辟的空间要释放吧。

GameEnd()

void GameEnd(pSnake ps)
{
    Setpos(15, 12);
    switch (ps->status)
    {
    case ESC:
        printf("主动退出游戏,游戏结束");
        break;
    case KILL_BY_WALL:
        printf("很遗憾,撞墙了,游戏结束");
        break;
    case KILL_BY_SELF:
        printf("很遗憾,咬到自己了,游戏结束");
        break;
    }

    //游戏结束要释放内存
    pSnakeNode cur = ps->pSnake;
    pSnakeNode prv = NULL;

    while(cur)
    {
        prv = cur;
        cur = cur->next;
        free(prv);
    }
    free(ps->pFood);
    ps->pFood = NULL;
    ps->pSnake = NULL;
}

别忘了指向食物的节点也要释放。

游戏代码的实现

创建Snake.c文件实现函数的功能

#include "Snake.h"//定位光标信息
void Setpos(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x, y };//设置光标位置SetConsoleCursorPosition(handle, pos);
}void CreateMap()
{int i = 0;//地图的上Setpos(0, 0);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);//打印宽字符用%lc,wprintf(L"%lc", L'□');}//下Setpos(0, 25);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i < 25; i++){Setpos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 25; i++){Setpos(56, i);wprintf(L"%lc", WALL);}
}void welcometoGame()
{Setpos(35, 15);printf("欢迎来到贪吃蛇小游戏\n");Setpos(36, 25);system("pause");system("cls");//清理屏幕!!!Setpos(15, 10);printf("使用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");Setpos(30, 11);printf("加速获得更高的分数\n");Setpos(36, 25);system("pause");system("cls");
}void InitSnake(pSnake ps)
{int i = 0;for (i = 0; i < 5; i++){//创建蛇身的五个节点pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//头插法if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇身pSnakeNode cur = ps->pSnake;while (cur){Setpos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//蛇的其他信息初始化ps->dir = RIGHT;//初始化方向为右ps->FoodWeight = 10;//初始化食物ps->pFood = NULL;ps->SleepTime = 200;//时间间隔200毫秒ps->status = OK;//游戏状态OKps->score = 0; //当前累计的分数
}void CreateFood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 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(pSnakeNode));if (pFood == NULL){perror("CreateFood()::malloc");return;}pFood->x = x;pFood->y = y;//打印食物Setpos(pFood->x, pFood->y);wprintf(L"%lc", FOOD);ps->pFood = pFood;
}void GameStark(pSnake ps)
{//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列  设置cmd窗⼝名称system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标CONSOLE_CURSOR_INFO cursor_info = { 0 };HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &cursor_info);//获取控制台光标信息cursor_info.bVisible = false;//false是bool类型 头文件 stdbool.h,隐藏光标.SetConsoleCursorInfo(handle, &cursor_info);//打印欢迎信息welcometoGame();//绘制游戏地图CreateMap();//初始化蛇InitSnake(ps);//生成食物CreateFood(ps);
}void PrintHelpInfo()
{Setpos(60, 14);printf("1. 不能穿墙,不能咬到自己");Setpos(60, 15);printf("2. 使用↑ ↓ ← → 分别控制蛇的移动");Setpos(60, 16);printf("3. F3为加速,F4为减速");Setpos(60, 17);
}void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}void EntFood(pSnake ps, pSnakeNode pnext)
{//头插pnext->next = ps->pSnake;ps->pSnake = pnext;//打印蛇pSnakeNode cur = ps->pSnake;while (cur){Setpos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//释放被吃掉的食物free(ps->pFood);//新生成食物CreateFood(ps);//吃到了食物,更新总分数ps->score += ps->FoodWeight;}void NotEntFood(pSnake ps, pSnakeNode pnext)
{//头插pnext->next = ps->pSnake;ps->pSnake = pnext;//因为要保持原长度,所以要释放尾节点,找到倒数第二个节点pSnakeNode cur = ps->pSnake;while (cur->next->next){cur = cur->next;}//找到最后一个节点的位置,将旧的信息(图标)覆盖,  Setpos(cur->next->x, cur->next->y);//两个空格(宽字符)printf("  ");//释放尾节点free(cur->next);cur->next = NULL;//打印蛇身cur = ps->pSnake;while (cur){Setpos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}}//蛇移动的过程就是把最后一个节点释放掉,再插入到头部,打印出来void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 25){ps->status = KILL_BY_WALL;return;}
}void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}cur = cur->next;}
}void SnakeMove(pSnake ps)
{//创建下一个节点pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));pnext->next = NULL;switch (ps->dir){case UP:pnext->x = ps->pSnake->x;pnext->y = ps->pSnake->y - 1;break;case DOWN:pnext->x = ps->pSnake->x;pnext->y = ps->pSnake->y + 1;break;case LEFT:pnext->x = ps->pSnake->x - 2;pnext->y = ps->pSnake->y;break;case RIGHT:pnext->x = ps->pSnake->x + 2;pnext->y = ps->pSnake->y;break;}//需要考虑下一步是否是食物,如果是食物则蛇身加长,否则不变if (pnext->x == ps->pFood->x && pnext->y == ps->pFood->y){EntFood(ps, pnext);}else{NotEntFood(ps, pnext);}//检测是否撞墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);}//二 游戏逻辑实现
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();//循环do{Setpos(60, 10);printf("总分:%5d", ps->score);Setpos(60, 11);printf("当前食物的分值:%.2d", ps->FoodWeight);//检测按键//上下左右 ESC 空格 F3 F4if (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 != RIGHT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){ps->status = ESC;}else if (KEY_PRESS(VK_SPACE)){//空格游戏暂停和恢复pause();}else if (KEY_PRESS(VK_F3)){if(ps->SleepTime > 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//下一步SnakeMove(ps);} while (ps->status == OK);}void GameEnd(pSnake ps)
{Setpos(15, 12);switch (ps->status){case ESC:printf("主动退出游戏,游戏结束");break;case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束");break;case KILL_BY_SELF:printf("很遗憾,咬到自己了,游戏结束");break;}//游戏结束要释放内存pSnakeNode cur = ps->pSnake;pSnakeNode prv = NULL;while(cur){prv = cur;cur = cur->next;free(prv);}free(ps->pFood);ps->pFood = NULL;ps->pSnake = NULL;
}

test.c

#include "Snake.h"void test()
{int ch = 0;//创建贪吃蛇Snake snake = { 0 };do{//游戏开始前的初始化GameStark(&snake);//游戏过程的实现GameRun(&snake);//游戏结束的善后工作GameEnd(&snake);Setpos(18, 15);printf("是否再来一局:Y/N");ch = getchar();} while (ch == 'Y' || ch == 'y');
}int main()
{//修改适配中文环境setlocale(LC_ALL, "");test();Setpos(0, 26);return 0;
}

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

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

相关文章

图片逐层矢量化

摘要 图像光栅化是计算机图形学中一个成熟的技术&#xff0c;而图像向量化&#xff0c;即光栅化的逆过程&#xff0c;仍然是一个主要的挑战。最近&#xff0c;基于深度学习的先进模型实现了向量化和向量图的语义插值&#xff0c;并展示了生成新图形的更好拓扑结构。然而&#…

在vue3中,如何优雅的使用echarts之实现大屏项目

前置知识 效果图 使用技术 Vue3 Echarts Gasp Gasp&#xff1a;是一个 JavaScript动画库,它支持快速开发高性能的 Web 动画。在本项目中&#xff0c;主要是用于做轨迹运动 所需安装的插件 npm i echarts npm i countup.js 数字滚动特效 npm i gsap javascript动画库 np…

适合优化yaml文件编辑效果的.vimrc简单配置

yaml文件编辑最重要的就是缩进对齐&#xff08;一个tab键对应2个空格&#xff09;&#xff0c;最后加上添加横&#xff0c;纵线的效果 某xx.yaml文件或者xx.yml在vim编辑器中效果如图所示&#xff1a;&#xff1a; 简单的~/.vimrc文件配置内容&#xff1a; vim ~/.vimrc set…

2024中国(重庆)人工智能展览会8月举办

2024中国(重庆)人工智能展览会8月举办 邀请函 主办单位&#xff1a; 中国航空学会 重庆市南岸区人民政府 招商执行单位&#xff1a; 重庆港华展览有限公司 【报名I59交易会 2351交易会 9466】 展会背景&#xff1a; 2024中国航空科普大会暨第八届全国青少年无人机大赛在…

JVM的原理与性能

1 JVM 内存结构 1.1 运行时数据区 1.1.1 栈&#xff08;虚拟机栈&#xff09; 每个线程在创建时都会创建一个私有的Java虚拟机栈&#xff0c;在执行每个方法时都会打包成一个栈帧&#xff0c;存储了局部变量表、操作数栈、动态链接、方法出口等信息&#xff0c;然后放入栈中。…

MF自定义控件方法

在MFC中&#xff0c;您可以通过自定义控件来实现特定的用户界面元素或功能&#xff0c;以满足您的应用程序需求。自定义控件通常是从CWnd类派生的子类&#xff0c;您可以在其中重写绘制、处理事件等方法&#xff0c;以实现您想要的功能和外观。以下是一般步骤&#xff1a; 创建…

Linux 操作系统MySQL 数据库1

1.MySQL 数据库 数据库是“按照数据结构来组织、 存储和管理数据的仓库”。 是一个长期存储在计算机内的、 有组织的、 可共享的、 统一管理的大量数据的集合。 它的存储空间很大&#xff0c; 可以存放百万条、 千万条、 上亿条数据。 但是数据库并不是随意地将数据进行…

在另外一个页面,让另外一个页面弹框显示操作(调佣公共的弹框)

大概意思是&#xff0c;登录弹框在另外一个页面中&#xff0c;而当前页面不存在&#xff0c;在当前页面中判断如果token不存在&#xff0c;就弹框出登录的弹框 最后一行 window.location.href … 如果当前用户已登录&#xff0c;则执行后续操作(注意此处&#xff0c;可不要)

哈希表Hash table

哈希表是根据关键码的值而直接进行访问的数据结构。 数组就是⼀张哈希表。 哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访问数组中的元素&#xff0c;如下图所示&#xff1a; 那么哈希表能解决什么问题呢&#xff0c;一般哈希表都是用来快速判断⼀个元素是…

Git Bash和Git GUI设置中文的方法

0 前言 Git是一个分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。一般默认语言为英文&#xff0c;本文介绍修改Git Bash和Git GUI语言为中文的方法。 1 Git Bash设置中文方法 &#xff08;1&#xff09;鼠标右键&#xff0c;单击“Git B…

redis深入理解之实战

1、SpringBoot整合redis 1.1 导入相关依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId&g…

HTML标签快速入门

文章目录 一、HTML语法规范1.1 基本语法概述1.2 标签关系 二、HTML基本结构标签2.1 第一个HTML网页2.2 基本结构标签总结 三、网页开发工具3.1 文档类型声明标签3.2 lang 语言种类3.3 字符集3.4 总结 四、HTML常用标签4.1 标签语义4.2 标题标签\<h1> - \<h6>&#…

[Linux][网络][数据链路层][一][以太网][局域网原理]详细讲解

目录 0.对比理解"数据链路层"和网络层1.以太网1.认识以太网2.以太网帧格式3.认识MAC地址4.以太网帧格式如何封装/解包&#xff1f;5.以太网帧格式如何分用&#xff1f; 2.重谈局域网通信原理0.如何形象的理解&#xff1f;1.理解局域网通信2.在发送数据的时候&#xf…

STC8增强型单片机开发【LED呼吸灯(PWM)⭐⭐】

目录 一、引言 二、硬件准备 三、PWM技术概述 四、电路设计 五、代码编写 EAXSFR&#xff1a; 六、编译与下载 七、测试与调试 八、总结 一、引言 在嵌入式系统开发中&#xff0c;LED呼吸灯是一种常见的示例项目&#xff0c;它不仅能够展示PWM&#xff08;脉冲宽度调制…

STM32快速入门(总线协议之I2C一主多从(软件实现 硬件实现))

STM32快速入门&#xff08;总线协议之I2C一主多从&#xff08;软件实现 & 硬件实现&#xff09;&#xff09; 前言 支持一对多&#xff08;一主多从&#xff09;、多对多传输&#xff08;多主多从&#xff09;&#xff0c;只支持半双工&#xff0c;一般有两根数据线&…

使用Baidu Comate五分钟 , 工作时间摸鱼8小时

Baidu Comate&#xff1a;引领智能编码新时代 文章目录 Baidu Comate&#xff1a;引领智能编码新时代一、明日工具&#xff0c;今日领先——百度Comate智能编码助手二、万变不离其宗——适配场景需求三、功能研究3.1 指挥如指掌——指令功能3.2 助手增援——插件功能使用3.3 实…

8个免费无版权视频素材网站,高清无水印视频任性下载

在数字化时代&#xff0c;优质的视频素材成为各种项目不可缺少的元素&#xff0c;从短片制作到商业广告&#xff0c;高品质的视频能显著提高作品的吸引力和传播效果。然而&#xff0c;寻找既免费又无版权问题的高清视频素材并非易事。以下介绍几个优秀的免费视频素材网站&#…

猜猜歇后语

页面 在输入框中填写你猜的答案&#xff0c;点击“显示答案”按钮&#xff0c;显示正确答案。 页面代码 function showAnswer(element){var elem$(element);elem.next().show();} //# // 初始化DataGrid对象 $(#dataGrid).dataGrid({searchForm: $(#searchForm),columnModel:…

k8s 数据流向 与 核心概念详细介绍

目录 一 k8s 数据流向 1&#xff0c;超级详细版 2&#xff0c;核心主键及含义 3&#xff0c;K8S 创建Pod 流程 4&#xff0c;用户访问流程 二 Kubernetes 核心概念 1&#xff0c;Pod 1.1 Pod 是什么 1.2 pod 与容器的关系 1.3 pod中容器 的通信 2&#xff0c; …

Sping源码(七)—ConfigurationClassPostProcessor ——@PropertySources解析

序言 先来简单回顾一下ConfigurationClassPostProcessor大致的一个处理流程&#xff0c;再来详细的讲解PropertySources注解的处理逻辑。 详细的步骤可参考ConfigurationClassPostProcessor这篇帖子。 流程图 从获取所有BeanDefinition -> 过滤、赋值、遍历 -> 解析 -&…