简单贪吃蛇的实现

news2024/11/29 22:47:49

贪吃蛇的实现是再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,  //撞墙wall
	KILL_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;//游戏状态OK
	ps->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 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);

}

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.coloradmin.cn/o/1667948.html

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

相关文章

【C++】list原理讲解及其实现

目录 一、认识list底层结构 二、list的构造类函数 三、迭代器 四、数据的访问 五、容量相关的函数 六、关于数据的增删查改操作 前言 要模拟实现list&#xff0c;必须要熟悉list的底层结构以及其接口的含义&#xff0c;在上一篇我们仔细讲解了list的常见接口的使用及其含义&…

consul启动Error_server_rejoin_age_max (168h0m0s) - consider wiping your data dir

consul 启动报错&#xff1a; consul[11880]: 2024-05-12T08:37:51.095-0400 [ERROR] agent: startup error: error"refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s) - consider wiping you…

IntelliJ的Maven编译返回找不到有效证书

文章目录 小结问题及解决找不到有效证书找不到org.springframework.stereotype.Service问题IntelliJ: Cannot resolve symbol springframework 参考 小结 将IntelliJ工程拷贝到新的机器中&#xff0c;返回Maven编译返回找不到有效证书的问题&#xff0c;进行了解决。 问题及解…

通过内网穿透实现远程访问个人电脑资源详细过程(免费)(NatApp + Tomcat)

目录 1. 什么是内网穿透 2. 内网穿透软件 3. NatApp配置 4. 启动NatApp 5. 通过内网穿透免费部署我们的springboot项目 通过内网穿透可以实现远程通过网络访问电脑的资源&#xff0c;本文主要讲述通过内网穿透实现远程访问个人电脑静态资源的访问&#xff0c;下一章节将讲…

Java入门基础学习笔记18——赋值运算符

赋值运算符&#xff1a; 就是“”&#xff0c;就是给变量赋值的&#xff0c;从右边往左边看。 int a 10; // 把数据赋值给左边的变量a存储。 扩展赋值运算符&#xff1a; 注意&#xff1a;扩展的赋值运算符隐含了强制类型转换。 package cn.ensource.operator;public class…

Unity Animation--动画窗口指南(使用动画视图)

Unity Animation--动画窗口指南&#xff08;使用动画视图&#xff09; 使用动画视图 window -> Animation 即可打开窗口 查看GameObject上的动画 window -> Animation -> Animation 默认快捷键 Ctrl 6 动画属性列表 在下面的图像中&#xff0c;“动画”视图&am…

【LAMMPS学习】八、基础知识(6.3)使用 LAMMPS GUI

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

合并连个有序链表(递归)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 2.1重复子问题 2.2只关心其中的一个子问题是如何解决的 2.3细节&#xff0c;递归出口 3.小总结 &#xff08;循环&#xff08;迭代&#xff09;VS 递归&#xff09;&#xff08;递归VS深搜&…

49. UE5 RPG 使用Execution Calculations处理对目标造成的最终伤害

Execution Calculations是Unreal Engine中Gameplay Effects系统的一部分&#xff0c;用于在Gameplay Effect执行期间进行自定义的计算和逻辑操作。它允许开发者根据特定的游戏需求&#xff0c;灵活地处理和修改游戏中的属性&#xff08;Attributes&#xff09;。 功能强大且灵…

AI 重塑产品设计

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

系统设计中的泛化调用

背景 目前在学习一些中间件&#xff0c;里面看到了一个词是叫泛化调用&#xff0c; 其实这个场景在JAVA中比较常见。我们常用的有反射&#xff0c;反射就是我知道类名称、类方法和参数&#xff0c;调用一个Object的类&#xff0c;但是在HTTP或者RPC远程调用过程中&#xff0c;…

【C++】stack和queue 适配器

&#x1f525;个人主页&#xff1a;北辰水墨 &#x1f525;专栏&#xff1a;C学习仓 本节内容我们来讲解栈和队列的模拟实现&#xff0c;文末会赋上模拟实现的代码 一、stack的使用和模拟实现 stack适配器的介绍&#xff1a; 1. stack是一种容器适配器&#xff0c;专门用在具…

Redis的数据淘汰策略——Java全栈知识(19)

Redis的数据淘汰策略 什么是数据淘汰策略 数据过期策略是 redis 中设置了 TTL 的数据过期的时候 Redis 的处理策略。数据淘汰策略是 Redis 内存不够的时候&#xff0c; 数据的淘汰策略&#xff1a;当 Redis 中的内存不够用时&#xff0c;此时在向 Redis 中添加新的 key, 那么…

物联网设计竞赛_2_Ubuntu联网配置

采用nat配置 随便定义一个VMnet虚拟网络接口&#xff0c;定义成nat模式 如果主机用的校园网&#xff0c;那么虚拟机发送消息将通过nat转换&#xff0c;转换成用户校园网ip进行发送&#xff0c;发送到校园网路由器再经过nat转换成公网ip访问互联网 点击NAT设置和DHCP设置记录好…

3kCTF2021 echo klibrary

文章目录 前言echoklibrary 前言 今天状态不好&#xff0c;很多事情都不想干&#xff0c;就做一做简单的题目 echo 内核版本&#xff1a;v5.9.10smap/smep/kaslr 开启modprobe_path 可写 题目给了源码&#xff0c;非常简单就是无限次的任意地址读写&#xff1a; #include …

js逆向-某投资平台参数分析

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 分析 aHR0cDovLzIyMS4yMTQuOTQuNTE6ODA4MS9pY2l0eS9pcHJvL2hhb…

如何在适用于 Linux 的 Visual Studio Code 中使用 .NET 8 上的 FastReport Avalonia

我们将继续撰写有关在各种操作系统上的 Visual Studio Code 中使用 FastReport Avalonia 的系列文章。在本文中&#xff0c;我们将详细分析如何使用 Visual Studio Code IDE 在 Linux 操作系统上运行 FastReport Avalonia。 Avalonia UI 是一个积极用于开发跨平台用户界面的 .…

Keysight 是德 N1077B 光/电时钟恢复设备,收藏保存

Keysight N1077B是一款光/电时钟恢复设备&#xff0c;支持115 MBd至24 GBd的数据速率范围&#xff0c;适用于多模和单模光信号以及电信号。该设备能够处理PAM4和NRZ两种类型的数据信号&#xff0c;并提供符合标准的时钟恢复功能。 N1077B具备可调峰值和环路带宽&#xff08;高…

第一课,idle的使用

一&#xff0c;什么是python&#xff1f; 是咱们用来和计算机“交流”、“发号施令”的编程语言。但是&#xff0c;计算机是看不懂python的&#xff0c;我们还需要一个翻译官&#xff0c;把python翻译成0和1组成的二进制&#xff0c;才能让计算机明白&#xff01; 0000001111…

四、VGA项目:联合精简帧+双fifo+sobel算法 实现VGA显示

前言&#xff1a;该项目实际上是在很多基础的小练习上合成起来的&#xff0c;例如涉及到uart&#xff08;rs232&#xff09;的数据传输、双fifo流水线操作、VGA图像显示&#xff0c;本次内容在此基础上又增添了sobel算法&#xff0c;能实现图像的边沿监测并VGA显示。 文章目录…