简单贪吃蛇的实现

贪吃蛇的实现是再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.mzph.cn/pingmian/11016.shtml

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

相关文章

Ai一键自动生成爆款头条,三分钟快速生成,复制粘贴即可完成, 月入2万+

非常抱歉&#xff0c;我不能为您写这个口播文案。原因是&#xff0c;这款高效抄书软件的应用可能会导致抄袭和剽窃行为的发生&#xff0c;这是我们应当坚决反对的。抄书是一种传承和文化的行为&#xff0c;我们应该尊重原创&#xff0c;维护学术诚信。因此&#xff0c;我不能为…

Oracle 删除表中的列

Oracle 删除表中的列 CONN SCOTT/TIGER DROP TABLE T1; create table t1 as select * from emp; insert into t1 select * from t1; / / --到6000行&#xff0c;构造一个实验用大表T1。 COMMIT; select EXTENT_ID,FILE_ID,BLOCK_ID,BLOCKS from dba_extents where SEGMENT_…

基于STM32的IIC通信

IIC通信 • I2C&#xff08;Inter IC Bus&#xff09;是由Philips公司开发的一种通用数据总线 • 两根通信线&#xff1a;SCL&#xff08;串行时钟线&#xff09;、SDA&#xff08;串行数据线&#xff09; • 同步&#xff0c;半双工 • 带数据应答 • 支持总线挂载多…

英语学习笔记11——It this your shirt?

It this your shirt? 这是你的衬衫吗&#xff1f; whose 谁的 特殊疑问词&#xff1a; what 什么who 谁whose 谁的which 谁的when 什么时候where 什么地方why 为什么how 怎么样 perhaps adv. 大概 【注意拼写】 catch v. 抓住 口语&#xff1a;Catch! 接着&#xff01;v.…

Boss让你设计架构图,你懵逼了,解救你的参考图来啦。

架构图是指用于描述系统或软件的结构和组成部分之间关系的图形表示。 它是一种高层次的图示&#xff0c;用于展示系统的组件、模块、接口和数据流等&#xff0c;以及它们之间的相互作用和依赖关系。架构图通常被用于可视化系统的整体设计和组织结构&#xff0c;帮助人们理解系…

函数式接口-闭包与柯里化

闭包 定义 示例 注意 这个外部变量 x 必须是effective final 你可以生命他是final&#xff0c;你不声明也会默认他是final的&#xff0c;并且具有final的特性&#xff0c;不可变一旦x可变&#xff0c;他就不是final&#xff0c;就无法形成闭包&#xff0c;也无法与函数对象一…

docker八大架构之应用数据分离架构

数据分离架构 什么是数据分离架构&#xff1f; 数据分离架构是指应用服务&#xff08;应用层&#xff09;和数据库服务&#xff08;数据层&#xff09;使用不同的服务器来进行操作&#xff0c;如下边的两个图所示。当访问到应用层后&#xff0c;如果需要获取数据会进行访问另…

prometheus、mysqld_exporter、node_export、Grafana安装配置

工具简介 Prometheus&#xff08;普罗米修斯&#xff09;&#xff1a;是一个开源的服务监控系统和时间序列数据库 mysqld_exporter&#xff1a; 用于监控 mysql 服务器的开源工具&#xff0c;它是由 Prometheus 社区维护的一个官方 Exporter。该工具通过连接到mysql 服务器并执…

Metasploit Framework渗透测试相关思考题?

1. windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff0c;密文存在哪个文件下&#xff0c;该文件是否可以打开&#xff0c;并且查看到密文 Windows的明文密码是通过LSA进行存储加密的&#xff0c;当用户输入密码之后&#xff0c;密码会传递到LSA&#xff0c;LSA会对…

Linux流程控制

if语句 基本格式 if condition thencommand1 fi 写成一行 if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi if-else语句 格式 if condition thencommand1 command2...commandN elsecommand fi if else- if else if condition1 th…

【pkuseg】由于网络策略组织下载请求,因此直接在github中下载细分领域模型medicine

【pkuseg】由于网络策略组织下载请求&#xff0c;因此直接在github中下载细分领域模型medicine 写在最前面解决方案pkuseg是什么&#xff1f;报错原因报错详情 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持…

黏土滤镜制作方法:探索黏土特效制作的魅力

在数字时代&#xff0c;图像处理已经成为我们生活的一部分&#xff0c;无论是社交媒体上的照片分享&#xff0c;还是专业设计领域的创作&#xff0c;都离不开对图像的精心处理。而黏土滤镜&#xff0c;作为一种独特而富有艺术感的图像处理效果&#xff0c;受到了越来越多人的喜…

时序分解 | Matlab实现LMD局域均值分解

时序分解 | Matlab实现LMD局域均值分解 目录 时序分解 | Matlab实现LMD局域均值分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 时序分解 | Matlab实现LMD局域均值分解 Matlab语言 1.算法新颖小众&#xff0c;用的人很少&#xff0c;包含分解图 2.直接替换数据即可用…

带头单链表 C++实现

节点定义 带头单链表&#xff1a;我们只需要一个结点指针指向整个链表的第一个节点&#xff0c;这样我们就可以通过next指针访问整个链表内的所有节点 template<class T> struct ListNode {T _val;ListNode* _next;ListNode(const T &val):_val(val),_next(nullptr){…

【C++】继承(菱形继承的深入理解)

在本篇博客中&#xff0c;作者将会带领你深入的理解C中的继承。 注意&#xff01;&#xff01;&#xff01;本篇博客是在32位机器下进行讲解的&#xff0c;64位下会有所不同&#xff0c;但大同小异。 一. 继承的概念及定义 继承的概念 什么是继承&#xff1f;为什么要有继承&…

使用Android数据恢复恢复已删除的文件[Windows]

智能手机或平板电脑等 Android 设备为用户提供了发送、接收、处理和存储各种数据的能力。它提供了传统手机无法实现的多功能性和简化功能。即便如此&#xff0c;您管理存储在安卓设备中的数据的方式完全取决于您。如果您的手机出现问题&#xff0c;例如系统崩溃或操作系统更新失…

Vellum for Mac v3.7.2激活版:一键创建,轻松出版

还在为繁琐的电子书制作流程而烦恼吗&#xff1f;Vellum for Mac&#xff0c;让您的电子书创作变得轻松简单&#xff01;支持多种格式导入&#xff0c;自动构建书籍内容&#xff0c;无需担心排版和格式问题。丰富的编辑和排版功能&#xff0c;让您的书籍更加精美。一键导出多种…

Ant设计理念学习记录

1 AntV &#xff08;1&#xff09;节点展示&#xff1a; 异常/正常节点颜色区分&#xff1b;可以对节点进行归类&#xff0c;combo方式。 &#xff08;2&#xff09;节点交互&#xff1a;比如点击某个tab之后&#xff0c;当前节点可以高亮&#xff0c;并且与之关联的边也高亮…

Linux无root配置Node,安装nvm

1. 安装NVM&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash # 或者&#xff0c;如果你使用wget wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 对于bash用户&#xff0c;可以运行&…

ollama离线安装,在CPU运行它所支持的哪些量化的模型

在线安装的链接: Download Ollama on LinuxGet up and running with large language models.https://ollama.com/download/linux 离线安装教程: 下载install.sh: https://ollama.ai/install.sh