贪吃蛇项目

引言:

本文章使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。

实现基本功能:

1.贪吃蛇地图绘制。

2.蛇吃食物的功能(上、下、左、右方向键控制蛇的动作)

3.蛇撞墙死亡

4.蛇咬到自己死亡

5.计算得分

6.蛇加速、减速

7.暂停游戏

技术要求:

C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API……

控制台及相关函数的介绍

一.首先让我们先来了解一下控制台程序,也就是我们平时运行起来的黑框程序。

1.设置控制台大小

使用cmd命令来设置控制台窗口的长宽:

mode con clos=100 lines=30

2.通过命令设置控制台窗口的名字:

title 贪吃蛇 

这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行

代码展示:

int main()

{

        system("mode con clos=100 lines=30");

        system("title 贪吃蛇");

        return 0;

}

 二.控制台屏幕上光标的坐标COORD

COORD是WindowsAPI中定义的一个结构体,表示光标在控制台屏幕上的坐标

typedef struct  COORD

{

        SHORT  X;

        SHORT  Y;

}COORD,*PCOORD;

 给坐标赋值

COORD pos={10,15};

利用好这个就可以实现将想在控制台屏幕上打印的字符出现在预先设想好的位置

 三.GetStdHandle函数

GetStdHandle是一个WindowsAPI函数,它用于从一个特定的标准设备(标准输入[STD_INPUT_HANDLE]、标准输出[STD_OUTPUT_HANDLE]或标出错误[STD_ERROR_HANDLE])中获取一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备

HANDLE GetStdHandle(DWORD,nStdHandle);

运用示例 :

HANDLE handle =GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄

四.GetConsoleCursorInfoa

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息

BOOL WINAPI GetConsoleCursorinfo

{

        HANDLE;

        PCONSOLE_CURSOR_INFO lpConsoleCursorInfo

} ;

五.CONSOLE_CURSOR_INFO

这个结构体包含有关控制台光标的信息

typedef strcut _CONSOLE_CURSOR_INFO{

        DWORD  dwSize;

        BOOL  bVisible;

}CONSOLE_CURSOR_INFO,*PCONSOLE_CURSOR_INFO;

dwSize 由光标填充的字符单元格的百分比。此值介于1到100之间 。光标外观会发生变化,范围从完全填充单元格到单元格底部的水平线。

bVisible 游标的可见性。如果为true则光标可见。

CursorInfo.bVisible=false;

六.SetConsoleCursorInfo 

设置指定控制台屏幕缓冲区的光标大小和可见性

BOOL WINAPI SetConsoleCursorInfo {

        HANDLE        hConsoleOutput;

        const        CONSOLE_CURSOR_INFO*lpConsoleCursorInfo

};

了解上面的几点后,我们需要将几点进行整合,实现光标的隐藏。

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);

 七.SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定位置。

BOOL WINAPI SetConsoleCursorPosition{

        HANDLE HConsoleOutput;

        COORD  POS;

}

运用这些我们可以封装成一个函数SetPos(int x,int y)将光标位置调置到(x,y)

void SetPos(int x,int y)

{

        //获得设备句柄
        HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);
        //根据句柄设置光标的位置
        COORD pos = { x, y };
        SetConsoleCursorPosition(hanlde, pos);

注意:控制台的坐标从左到右依次递增,从上到下依次递增 

八.GetAsyncKeyState

获取按键情况

SHORT GetAsyncKeyState{

        int vKey

} ;

GetAsyncKeyState的返回值是short类型,调用GetAsyncKeyState后如果返回的16为数据中最高位是1,说明该键是按下状态,如果是0,则是抬起状态。如果最低位是1则说明,该键被按过,否则为0。

所以判断一个键是否被按过我们可以判断GetAsyncKeyState函数返回值最低位是否为1.

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

具体实现步骤

1.相关信息的构建(蛇的身体部分我们依靠链表来实现)

1.1创建蛇的节点(包括该节点在控制台屏幕的位置以及指向下一节点的指针)

typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

1.2创建整条蛇的结构体

typedef struct Snake
{
    pSnakeNode pSnake;//维护整条蛇的指针(头指针)
    pSnakeNode pFood;//食物的制作
    int score;//累积得分
    int Foodweight;//食物的分数
    int sleeptime;//蛇休眠的时间
    enum GAME_STATUS status;//游戏当前的状态
    enum DIRECTION dir;//蛇当前走的方向
}Snake,*pSnake; 

1.3游戏当前的状态GAME_STATUS(正常状态,按下ESC键退出状态,蛇撞到墙或咬到自己了游戏结束状态) 

enum GAME_ATATUS
{
    OK = 1,
    ESC,
    KILL_BY_WALL,
    KILL_BY_SELF
};
 

1.4蛇的方向(上下左右)

 enum DIRECTION 
{
    UP=1,
    DOWN,
    RIGHT,
    LEFT
};

2.相关信息定义好后,我们可以将整个游戏拆分成三个阶段(游戏前准备阶段、游戏开始运行阶段、游戏结束阶段)

2.1游戏前准备阶段(void GameStart(pSnake ps))

2.1.1首先设置控制台的大小以及名称
2.2.2将光标进行隐藏(以上两点在控制台及相关函数的介绍中已经讲过这里不多阐述)
2.2.3对游戏进入界面欢迎信息的打印(void WelcomeToGame())

实现效果:

void WelcomeToGame()
{
    //欢迎信息
    SetPos(40, 12);//定好相应位置进行信息打印
    printf("欢迎来到贪吃蛇小游戏");
    SetPos(42, 24);
    system("pause");
    system("cls");//清除屏幕信息

    

    //功能介绍信息
    SetPos(35, 12);
    printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");
    SetPos(35, 13);
    printf("加速能获得更高分");
    SetPos(42, 24);
    system("pause");
    system("cls");
}

2.2.4绘制地图(CreateMap())

效果实现:

 这里运用到了宽字符墙体用□,蛇身用●,食物用★(宽字符占用两个字节)

#define Wall L'□'
#define BODY L'●'
#define FOOD L'★' 

void CreateMap()
{
    SetPos(0, 0);
    int i = 0;
    //上
    for (i = 0; i <= 56; i += 2)//宽字符占用两个字节
    {
        wprintf(L"%lc", Wall);//宽字符打印用到wprintf函数
    }
    SetPos(0, 26);
    //下
    for (i = 0; i <= 56; i += 2)
    {
        wprintf(L"%lc", Wall);
    }
    //左
    for (i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", Wall);
    }
    //右
    for (i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%lc", Wall);
    }

2.2.5初始化蛇(void InitSnake(pSnake ps))

对蛇的相关信息进行初始化,首先我们先默认蛇的开始长度为5个节点。创建五个蛇节点并将其打印出来,然后再对剩余数据进行初始化。

设置设节点开始坐标(POS_X,POS_Y)

 #define POS_X    24
#define POS_Y   5

void InitSnake(pSnake ps)
{
    //创建五个蛇身节点
    pSnakeNode cur = NULL;
    for (int i = 0; i < 5; i++)
    {
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("InitSnake()::malloc fail!");
            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;
        }
    }

    cur = ps->pSnake;
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }

    ps->Foodweight = 10;//每一个食物的初始分数
    ps->score = 0;//当前的得分
    ps->sleeptime = 200;//蛇每走一步休眠的时间
    ps->status = OK;//状态设置为正常
    ps->dir = RIGHT;//默认蛇开始的移动的方向是右
}

2.2.6创建食物 (void CreateFood(pSnake ps))

创建食物节点时我们需要注意两个点

1.食物必须在地图内且x坐标是2的倍数(由于蛇节点时宽字符打印占两个字节且从偶数开始)

2.食物不能与蛇身重合

void CreateFood(pSnake ps)
{
    int x = 0, y = 0;
again:
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 24 + 1;

    } while (x % 2 != 0);

    //判断食物节点位置是否在蛇身上
    pSnakeNode cur = ps->pSnake;
    while (cur)
    {
        if (x == cur->x && y == cur->y)
            goto again;
        cur = cur->next;
    }

    pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (pFood == NULL)
    {
        perror("CreateFood()::malloc fail!");
        return;
    }

    pFood->x = x;
    pFood->y = y;

    ps->pFood = pFood;

    SetPos(x, y);
    wprintf(L"%lc", FOOD);
}

2.2游戏开始进行阶段(void GameRun(pSnake ps))

2.2.1打印相关提示信息(游戏规则)(void PrintHelpInfo())

void PrintHelpInfo()
{
    SetPos(62, 15);
    printf("1。不能穿墙 不能咬到自己");
    SetPos(62, 16);
    printf("2.用 ↑.↓.←.→ 来控制蛇的移动");
    SetPos(62, 17);
    printf("3.F3是加速,F4是减速");

    SetPos(62,19);
    printf("版权@M--Y");
}

2.2.2将累积总分和每个食物的分数打印出来

SetPos(62, 10);
printf("累积总分:%5d", ps->score);
SetPos(62, 11);
printf("每个食物得分:%02d", ps->Foodweight); 

 2.2.3判断按键情况,↑键蛇向上移动,↓键蛇向下移动,←键向左移动,→键向右移动,Esc键退出游戏,空格键暂停,F3键加速,F4键减速。

这里需要运用到上面提到的GetAsyncKeyState()函数判断按键情况和虚拟键值(虚拟键盘对照表(KEY 按键) – 源码巴士)

//显然当蛇准备向某个方向移动时,原运动方向肯定不是与准备方向相反的

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_RIGHT)&&ps->dir!=LEFT)
{
    ps->dir = RIGHT;
}
else if (KEY_PRESS(VK_LEFT)&&ps->dir!=RIGHT)
{
    ps->dir = LEFT;
}
else if (KEY_PRESS(VK_ESCAPE))
{
    ps->status = ESC;
    break;
}
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;
    }
}

上面紫色标注出的暂停函数实现:

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

2.2.4按键情况清楚后需要对贪吃蛇进行移动(void SnakeMove(pSnake ps)) 

根据按键方向来确定蛇头下一节点的坐标(cur是新增节点)

switch (ps->dir)
{
case UP:
    cur->x = ps->pSnake->x;
    cur->y = ps->pSnake->y - 1;
    break;
case DOWN:
    cur->x = ps->pSnake->x;
    cur->y = ps->pSnake->y + 1;
    break;
case RIGHT:
    cur->x = ps->pSnake->x+2;
    cur->y = ps->pSnake->y ;
    break;
case LEFT:
    cur->x = ps->pSnake->x-2;
    cur->y = ps->pSnake->y ;
    break;
}

但是这就里就存在一个问题了,需要考虑下一个节点是不是食物。如果是食物就将这个节点加入到蛇身中,如果不是就将蛇往这个方向移动一个节点并将尾节点除去。

判断语句(if (ps->pFood->x == cur->x && ps->pFood->y == cur->y))

1.如果是食物就加入蛇身(void EATFOOD(pSnake ps,pSnakeNode cur)) 

void EATFOOD(pSnake ps,pSnakeNode cur)
{

    //头插
    cur->next = ps->pSnake;
    ps->pSnake = cur;

    pSnakeNode p = ps->pSnake;
    while (p)//打印蛇身
    {
        SetPos(p->x, p->y);
        wprintf(L"%lc", BODY);
        p = p->next;
    }
    ps->score += ps->Foodweight;

    free(ps->pFood);//释放旧食物的指针
    CreateFood(ps);//创建新食物
}

2.不是食物 (void NOTEATFOOD(pSnake ps, pSnakeNode cur))

void NOTEATFOOD(pSnake ps, pSnakeNode cur)
{
    cur->next = ps->pSnake;
    ps->pSnake = cur;

    pSnakeNode p = ps->pSnake;
    while (p->next->next)
    {
        SetPos(p->x, p->y);
        wprintf(L"%lc", BODY);
        p = p ->next;
    }
    SetPos(p->next->x, p->next->y);
    printf("  ");//将原蛇身尾节点打印成空字符

    free(p->next);
    p->next = NULL;

}

判断条件处为p->next->next的解释:

蛇移动好后要判断是否有撞到墙,或是咬到自己

void KILLByWall(pSnake ps);//检查是否有撞墙

void KILLBySelf(pSnake ps);//检查是否咬到自己

 撞墙:判断头节点坐标是否超出边界

void KILLByWall(pSnake ps)
{
    if (ps->pSnake->x == 0 ||
        ps->pSnake->x == 56 ||
        ps->pSnake->y == 0 ||
        ps->pSnake->y == 26)
        ps->status = KILL_BY_WALL;
}

咬到自己:判断头结点是否和身体上的某一个节点的坐标一致

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;
    }

2.3游戏结束阶段(void GameEnd(pSnake ps))

判断是什么原因结束的游戏并打印出来

SetPos(20, 12);
switch (ps->status)
{
case ESC:
    printf("主动正常退出 ");
    break;
case KILL_BY_SELF:
    printf("很遗憾 你咬到自己了,游戏结束");
    break;
case KILL_BY_WALL:
    printf("很遗憾 你撞墙了,游戏结束");
    break;
}

最后进行资源释放,将创建的指针进行释放处理。

SnakeNode cur = ps->pSnake;
    while (cur)
    {
        pSnakeNode next = cur->next;
        free(cur);
        cur = next;
    }

    free(ps->pFood);
    ps->pFood = NULL;

在主函数中进行do while循环使游戏可以重复多次进行、

int  ch = 0;
do
{
    Snake snake = { 0 };
    srand((unsigned int)time(NULL));

    GameStart(&snake);//游戏前准备工作
    GameRun(&snake);//游戏过程
    GameEnd(&snake);//游戏善后工作
    SetPos(20, 15);
    printf("再来一局吗?(Y/N):");
    ch = getchar();
    getchar();//清理\n
} while (ch == 'y' || ch == 'Y');

最后完整代码展示:

Snake.h

#pragma once

#define Wall L'□'
#define BODY L'●'
#define FOOD L'★'

#define POS_X    24
#define POS_Y   5

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

#include<stdio.h>
#include<locale.h>
#include<stdlib.h>
#include<stdbool.h>
#include<windows.h>
#include<time.h>

//游戏当前状态
enum GAME_ATATUS
{
    OK = 1,
    ESC,
    KILL_BY_WALL,
    KILL_BY_SELF
};

//蛇走的方向
enum DIRECTION 
{
    UP=1,
    DOWN,
    RIGHT,
    LEFT
};
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 WelcomeToGame();

//绘制地图
void CreateMao();

//初始化蛇
void InitSnake(pSnake ps);

//创建食物
void CreateFood(pSnake ps);

//游戏前
void GameStart(pSnake ps);

//打印帮助信息
void PrintHelpInfo();

//如果下一节点是食物就吃掉
void EATFOOD(pSnake ps,pSnakeNode cur);
//不是
void NOTEATFOOD(pSnake ps, pSnakeNode cur);

//判断是否撞墙了
void KILLByWall(pSnake ps);

//检查是否咬到就自己
void KILLBySelf(ps);

//走一步
void SnakeMove(pSnake ps);

//游戏过程
void GameRun(pSnake ps);

//游戏善后工作
void GameEnd(pSnake ps);

 

Snake.c

#define _CRT_SECURE_NO_WARNINGS
#include "Snake.h"

void SetPos(int x, int y)
{
    //获得设备句柄
    HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);
    //根据句柄设置光标的位置
    COORD pos = { x, y };
    SetConsoleCursorPosition(hanlde, pos);
}

void WelcomeToGame()
{
    //欢迎信息
    SetPos(40, 12);
    printf("欢迎来到贪吃蛇小游戏");
    SetPos(42, 24);
    system("pause");
    system("cls");

    //功能介绍信息
    SetPos(35, 12);
    printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");
    SetPos(35, 13);
    printf("加速能获得更高分");
    SetPos(42, 24);
    system("pause");
    system("cls");
}

void CreateMap()
{
    SetPos(0, 0);
    int i = 0;
    //上
    for (i = 0; i <= 56; i += 2)
    {
        wprintf(L"%lc", Wall);
    }
    SetPos(0, 26);
    //下
    for (i = 0; i <= 56; i += 2)
    {
        wprintf(L"%lc", Wall);
    }
    //左
    for (i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", Wall);
    }
    //右
    for (i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%lc", Wall);
    }
}

void InitSnake(pSnake ps)
{
    //创建五个蛇身节点
    pSnakeNode cur = NULL;
    for (int i = 0; i < 5; i++)
    {
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("InitSnake()::malloc fail!");
            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;
        }
    }

    cur = ps->pSnake;
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }

    ps->Foodweight = 10;
    ps->score = 0;
    ps->sleeptime = 200;
    ps->status = OK;
    ps->dir = RIGHT;
}

//创建食物
void CreateFood(pSnake ps)
{
    int x = 0, y = 0;
again:
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 24 + 1;

    } while (x % 2 != 0);

    //判断食物节点位置是否在蛇身上
    pSnakeNode cur = ps->pSnake;
    while (cur)
    {
        if (x == cur->x && y == cur->y)
            goto again;
        cur = cur->next;
    }

    pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (pFood == NULL)
    {
        perror("CreateFood()::malloc fail!");
        return;
    }

    pFood->x = x;
    pFood->y = y;

    ps->pFood = pFood;

    SetPos(x, y);
    wprintf(L"%lc", FOOD);
}

void GameStart(pSnake ps)
{
    //设置控制台的信息,窗口大小,窗口名
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");

    //隐藏光标
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO CursorInfo;
    GetConsoleCursorInfo(handle, &CursorInfo);
    CursorInfo.bVisible = false;
    SetConsoleCursorInfo(handle, &CursorInfo);

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

    //绘制地图
    CreateMap();

    //初始化蛇
    InitSnake(ps);

    //创建食物
    CreateFood(ps);
}

//打印帮助信息
void PrintHelpInfo()
{
    SetPos(62, 15);
    printf("1。不能穿墙 不能咬到自己");
    SetPos(62, 16);
    printf("2.用 ↑.↓.←.→ 来控制蛇的移动");
    SetPos(62, 17);
    printf("3.F3是加速,F4是减速");

    SetPos(62,19);
    printf("版权@M--Y");
}

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

void EATFOOD(pSnake ps,pSnakeNode cur)
{
    cur->next = ps->pSnake;
    ps->pSnake = cur;

    pSnakeNode p = ps->pSnake;
    while (p)//打印蛇身
    {
        SetPos(p->x, p->y);
        wprintf(L"%lc", BODY);
        p = p->next;
    }
    ps->score += ps->Foodweight;

    free(ps->pFood);//释放旧食物的指针
    CreateFood(ps);//创建新食物
}

void NOTEATFOOD(pSnake ps, pSnakeNode cur)
{
    cur->next = ps->pSnake;
    ps->pSnake = cur;

    pSnakeNode p = ps->pSnake;
    while (p->next->next)
    {
        SetPos(p->x, p->y);
        wprintf(L"%lc", BODY);
        p = p ->next;
    }
    SetPos(p->next->x, p->next->y);
    printf("  ");

    free(p->next);
    p->next = NULL;

}

void KILLByWall(pSnake ps)
{
    if (ps->pSnake->x == 0 ||
        ps->pSnake->x == 56 ||
        ps->pSnake->y == 0 ||
        ps->pSnake->y == 26)
        ps->status = KILL_BY_WALL;
}

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 cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (cur == NULL)
    {
        perror("SaveMove()::malloc fail!");
        return;
    }
    cur->next = NULL;

    switch (ps->dir)
    {
    case UP:
        cur->x = ps->pSnake->x;
        cur->y = ps->pSnake->y - 1;
        break;
    case DOWN:
        cur->x = ps->pSnake->x;
        cur->y = ps->pSnake->y + 1;
        break;
    case RIGHT:
        cur->x = ps->pSnake->x+2;
        cur->y = ps->pSnake->y ;
        break;
    case LEFT:
        cur->x = ps->pSnake->x-2;
        cur->y = ps->pSnake->y ;
        break;
    }

    if (ps->pFood->x == cur->x && ps->pFood->y == cur->y)
        EATFOOD(ps,cur);//下个节点是食物就吃掉食物
    else
        NOTEATFOOD(ps,cur);

    KILLByWall(ps);//检查是否有撞墙

    KILLBySelf(ps);//检查是否咬到自己
}
 
//游戏过程
void GameRun(pSnake ps)
{
    //打印帮助信息
    PrintHelpInfo();

    do
    {
        //当前的分数情况
        SetPos(62, 10);
        printf("累积总分:%5d", ps->score);
        SetPos(62, 11);
        printf("每个食物得分:%02d", 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_RIGHT)&&ps->dir!=LEFT)
        {
            ps->dir = RIGHT;
        }
        else if (KEY_PRESS(VK_LEFT)&&ps->dir!=RIGHT)
        {
            ps->dir = LEFT;
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            ps->status = ESC;
            break;
        }
        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;
            }
        }
        //走一步
        SnakeMove(ps);

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

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

//游戏后工作
void GameEnd(pSnake ps)
{
    SetPos(20, 12);
    switch (ps->status)
    {
    case ESC:
        printf("主动正常退出 ");
        break;
    case KILL_BY_SELF:
        printf("很遗憾 你咬到自己了,游戏结束");
        break;
    case KILL_BY_WALL:
        printf("很遗憾 你撞墙了,游戏结束");
        break;
    }

    pSnakeNode cur = ps->pSnake;
    while (cur)
    {
        pSnakeNode next = cur->next;
        free(cur);
        cur = next;
    }

    free(ps->pFood);
    ps->pFood = NULL;
}

 

test.c

#define _CRT_SECURE_NO_WARNINGS

#include "Snake.h"

void test()
{
    int  ch = 0;
    do
    {
        Snake snake = { 0 };
        srand((unsigned int)time(NULL));

        GameStart(&snake);//游戏前准备工作
        GameRun(&snake);//游戏过程
        GameEnd(&snake);//游戏善后工作
        SetPos(20, 15);
        printf("再来一局吗?(Y/N):");
        ch = getchar();
        getchar();//清理\n
    } while (ch == 'y' || ch == 'Y');
}

int main()
{
    //修改适配本地中文环境
    setlocale(LC_ALL, "");
    test();
    SetPos(0, 27);

    return 0;
}

 

 

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

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

相关文章

Spark3内核源码与优化

文章目录 一、Spark内核原理1、Spark 内核概述1.1 简介1.2 Spark 核心组件1.3 Spark 通用运行流程概述 2、Spark 部署模式2.1 YARN Cluster 模式(重点)2.2 YARN Client 模式2.3 Standalone Cluster 模式2.4 Standalone Client 模式 3、Spark 通讯架构3.1 Spark 通信架构概述3.2…

【React教程】(2) React之JSX入门与列表渲染、条件渲染详细代码示例

目录 JSX环境配置基本语法规则在 JSX 中嵌入 JavaScript 表达式在 JavaScript 表达式中嵌入 JSXJSX 中的节点属性声明子节点JSX 自动阻止注入攻击在 JSX 中使用注释JSX 原理列表循环DOM Elements 列表渲染语法高亮 条件渲染示例1&#xff1a;示例2&#xff1a;示例3&#xff08…

Spring Security的入门案例!!!

一、导入依赖 <dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--security--><dependency><groupId>…

幻兽帕鲁服务器出租,腾讯云PK阿里云怎么收费?

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

SpringBoot项目接入MQTT协议

mqtt是一种类似于mq的通讯技术 1、mqtt服务端搭建 创建docker网络 docker network create --driver bridge --subnet 172.18.0.0/24 --gateway 172.18.0.1 emqx-net创建容器 docker run -d \--name emqx1 \-e "EMQX_NODE_NAMEemqx172.18.0.2" \--network emqx-ne…

【LeetCode每日一题】56. 合并区间插入区间

一、判断区间是否重叠 力扣 252. 会议室 给定一个会议时间安排的数组 intervals &#xff0c;每个会议时间都会包括开始和结束的时间 intervals[i] [starti, endi] &#xff0c;请你判断一个人是否能够参加这里面的全部会议。 思路分析 因为一个人在同一时刻只能参加一个会…

Zygote的启动流程

在zygote进程对应的文件是app_main.cpp文件&#xff0c;在app_main.cpp文件的main()方法中先解析了init.rc中配置的参数并根据配置的参数设置zygote的状态。 在状态设置阶段主要做了&#xff1a; 设置进程名称为zygote通过startSystemServer true标示启动的是systemServer调…

SpringSecurity笔记

SpringSecurity 本笔记来自三更草堂&#xff1a;https://www.bilibili.com/video/BV1mm4y1X7Hc/?spm_id_from333.337.search-card.all.click&#xff0c;仅供个人学习使用 简介 Spring Security是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;…

消息中间件RabbitMQ介绍

一、基础知识 1. 什么是RabbitMQ RabbitMQ是2007年发布&#xff0c;是一个在AMQP(高级消息队列协议)基础上完成的&#xff0c;简称MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法&#xff0c;由Erlang&#xff08;专门针对于大…

What is Rust? Why Rust?

why Rust&#xff1f; 目前&#xff0c;Rust 变得越来越流行。然而&#xff0c;仍然有很多人&#xff08;和公司&#xff01;&#xff09;误解了 Rust 的主张价值是什么&#xff0c;甚至误解了它是什么。在本文中&#xff0c;我们将讨论 Rust 是什么以及为什么它是一种可以增强…

基于springboot+vue的医院管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

###C语言程序设计-----C语言学习(6)#

前言&#xff1a;感谢老铁的浏览&#xff0c;希望老铁可以一键三连加个关注&#xff0c;您的支持和鼓励是我前进的动力&#xff0c;后续会分享更多学习编程的内容。 一. 主干知识的学习 1. while语句 除了for语句以外&#xff0c;while语句也用于实现循环&#xff0c;而且它…