简单贪吃蛇的实现

发布于:2024-05-13 ⋅ 阅读:(157) ⋅ 点赞:(0)

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


网站公告

今日签到

点亮在社区的每一天
去签到