简单贪吃蛇的实现

贪吃蛇的实现是再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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

哈希(构造哈希函数)

哈希 哈希也可以叫散列 画一个哈希表 哈希冲突越多&#xff0c;哈希表效率越低。 闭散列开放定址法: 1.线性探测&#xff0c;依次往后去找下一个空位置。 2.二次探测&#xff0c;按2次方往后找空位置。 #pragma once #include<vector> #include<iostream> #i…

基于SpringBoot+Vue的物流管理系统

运行截图 获取方式 Gitee仓库

机器学习(五) ----------决策树算法

目录 1 核心思想 2 决策树算法主要步骤 3 决策树算法的分类 3.1 ID3算法&#xff08;Iterative Dichotomiser 3&#xff09;&#xff1a; 3.1.1 基本步骤 3.1.2 原理 信息增益 3.1.3 注意事项 3.2 C4.5算法&#xff1a; 3.2.1. 信息增益率 计算公式 3.2.2. 构建决策…

Rpcx (一):详解【介绍、基础示例 demo】

一.rpcx介绍 1.1 rpc是什么 远程过程调用的通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。简单地说就是能使应用像调用本地…

服装店会员管理系统结合小程序商城帮你挖掘出潜在客户

在现代社会&#xff0c;随着科技的不断进步和人们消费习惯的变化&#xff0c;传统的服装店已经不再能够满足消费者的需求。为了更好地服务客户&#xff0c;提升销售业绩&#xff0c;许多服装店开始引入会员管理系统&#xff0c;并结合小程序商城&#xff0c;实现线上线下的无缝…

【半夜学习MySQL】表的约束(含主键、唯一键、外键、zerofill、列描述、默认值、空属性详解)

&#x1f3e0;关于专栏&#xff1a;半夜学习MySQL专栏用于记录MySQL数据相关内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 前言空属性默认值列描述zerofill主键主键概述主键删除与追加复合主键 自增长唯一键外键综合案例 前言 上一篇文章中介绍了数…

Android性能:高版本Android关闭硬件加速GPU渲染滑动卡顿掉帧

Android性能&#xff1a;高版本Android关闭硬件加速GPU渲染滑动卡顿掉帧 如果在Androidmanifest.xml配置&#xff1a; <application android:hardwareAccelerated"false" > 或者某个特点View使用代码&#xff1a; myView.setLayerType(View.LAYER_TYPE_SOFT…

带你手撕红黑树! c++实现 带源码

目录 一、概念 二、特性 三、接口实现 1、插入 情况一&#xff1a;p为黑&#xff0c;结束 情况二&#xff1a;p为红 1&#xff09;叔叔存在且为红色 2&#xff09;u不存在/u存在且为黑色 &#xff08;1&#xff09;p在左&#xff0c;u在右 &#xff08;2&#xff09;…

爱分析基于杭州云器Lakehouse实现成本最优的一体化管理,新一代数据平台的建设方式

导读 1.当前&#xff0c;企业在大数据和数据中台建设上取得成果&#xff0c;但数据开发管理仍具挑战性&#xff08;成本、效率、复杂度&#xff09;。 2.随数据平台领域成熟&#xff0c;厂商应结合自身需求&#xff0c;重新思考“基于开源自建数据平台”的重资产模式与“购买…

Windows单机部署RocketMQ5.X并与SpringBoot集成

RocketMQ 5.X下载 《RocketMQ 5.X下载》 下载后&#xff0c;解压缩 修改初始内存 修改runserver.cmd&#xff08;Linux则为runserver.sh&#xff09; 将下面的-Xms2g -Xmx2g -Xmn1g调小 if %JAVA_MAJOR_VERSION% lss 17 (set "JAVA_OPT%JAVA_OPT% -server -Xms2g -X…

传感网应用开发教程--AT指令访问新大陆云平台(ESP8266模块+物联网云+TCP)

实现目标 1、熟悉AT指令 2、熟悉新大陆云平台新建项目 3、具体目标&#xff1a;&#xff08;1&#xff09;注册新大陆云平台&#xff1b;&#xff08;2&#xff09;新建一个联网方案为WIFI的项目&#xff1b;&#xff08;3&#xff09;ESP8266模块&#xff0c;通过AT指令访问…

计算机毕业设计python校园二手交易系统aqj3i-

什么叫三层架构呢&#xff1f;指的是表示层、组件层、数据访问层。组件层是双层架构没有的&#xff0c;它的加入&#xff0c;把复杂的问题分解得更简单、明了&#xff0c;通过组件层&#xff0c;实现控制数据访问层&#xff0c;这样达到功能模块易于管理、易于访问等目的&#…

【python量化交易】qteasy使用教程06——创建自定义因子选股交易策略

创建自定义因子选股策略 使用qteasy创建自定义因子选股交易策略开始前的准备工作本节的目标Alpha选股策略的选股思想计算选股指标用FactorSorter定义Alpha选股策略交易策略的回测结果用GeneralStg定义一个Alpha选股策略回测结果&#xff1a;本节回顾 使用qteasy创建自定义因子选…

【UnityRPG游戏制作】Unity_RPG项目_PureMVC框架应用

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

Redis系列-3 Redis缓存问题

1.缓存的作用 数据库(如Mysql)的持久化特点带来了较低的性能&#xff0c;高并发的场景下&#xff0c;连接池很快被耗尽而出现宕机或DOS&#xff0c;无法继续对外提供服务。相对于数据库的硬盘IO&#xff0c;缓存中间件基于内存进行读写&#xff0c;从而具备较大的吞吐量和高并…

5.10.6 用于乳腺癌超声图像分类的Vision Transformer

医学超声&#xff08;US&#xff09;成像由于其易用性、低成本和安全性已成为乳腺癌成像的主要方式。卷积神经网络&#xff08;CNN&#xff09;有限的局部感受野限制了他们学习全局上下文信息的能力。利用 ViT 对使用不同增强策略的乳房 US 图像进行分类。 卷积神经网络&#…

你知道C++多少——默认成员函数

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

Linux中gitlab-runner部署使用备忘

环境&#xff1a; 操作系统:&#xff1a;CentOS8 gitlab版本&#xff1a;13.11.4 查看gitlab-runner版本 可以从https://packages.gitlab.com/app/runner/gitlab-runner/search找到与安装的gitlab版本相近的gitlab-runner版本以及安装命令等信息&#xff0c;我找到与13.11.4相…

网络 | 应用层-websocket协议概述与握手过程解析

背景&#xff1a;这里为了实现消息实时传输决定引入websocket协议。 不管是发送消息还是接收消息&#xff0c;都需要实时传输&#xff0c;张三发给李四&#xff0c;李四立马就能收到&#xff0c;基于HTTP实现是有些困难的。 但轮询方式也带来了一些问题 1、消耗更多系统资源&…

设计模式——模板设计模式(Template Method)

模板设计-base 什么是模板&#xff1f; 举个简单的例子&#xff0c;以AABB的格式&#xff0c;写出一个词语&#xff0c;你可能会想到&#xff0c;明明白白&#xff0c;干干净净等&#xff0c; 这个AABB就是一个模板&#xff0c;对模板心中有了一个清晰的概念之后&#xff0c;…