贪吃蛇项目

引言:

本文章使用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.mzph.cn/news/656655.shtml

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

相关文章

回文子字符串的个数

判断一个字符串是否是一个回文除了从两端向里移动指针&#xff0c;也可以采用指针从字符串中心开始向两端延伸。即如果存在一个长度为m的回文子字符串&#xff0c;再分别向该回文两端延伸一个字符&#xff0c;并判断这两个字符是否相同&#xff0c;如果相同则找到了一个长度为m…

PMP备考笔记:模拟考试知识点总结

1. 答题思路&#xff1a;优先看问题&#xff0c;可节省时间。 2. 考试就按照考试的套路来做&#xff0c;不要过多考虑。 开发团队只专注当前冲刺目标&#xff0c;产品负责人对PB排优先级。 收集需求工具-原型法&#xff1a;能够让用户提前体验&#xff0c;减少返工的风险。 …

centos7上安装mysql5.7并自定义数据目录路径

1、卸载mariadb rpm -qa |grep mariadb #查出来的结果是mariadb-libs-5.5.68-1.el7.x86_64 rpm -e mariadb-libs-5.5.68-1.el7.x86_64 --nodeps #卸载查到的结果 2、官网下载响应的tar.gz包&#xff0c;比如mysql-5.7.38-el7-x86_64.tar.gz &…

线段树分治总结

线段树分治总结 概念例题二分图 /【模板】线段树分治[HAOI2017] 八纵八横[FJOI2015] 火星商店问题EnvyExtending Set of PointsForced Online Queries Problem「雅礼集训 2018 Day10」贪玩蓝月BZOJ4184-shallot[bzoj4644]经典**题 概念 \qquad 线段树分治一般用来解决带有如下两…

MyBatis 的XML实现方法

MyBatis 的XML实现方法 MyBatis 的XML实现方法前情提示创建mapper接口添加配置创建xml文件操作数据库insert标签delete标签select标签resultMap标签 update标签sql标签,include标签 MyBatis 的XML实现方法 前情提示 关于mybatis的重要准备工作,请看MyBatis 的注解实现方法 创…

骨传导耳机对身体有什么危害?危害严重吗

骨传导耳机虽然提供了一种避免直接将声音传输至耳道的新方式&#xff0c;减少了对耳道和鼓膜的潜在损害&#xff0c;但它们也并非完全没有潜在的危害性。尽管存在潜在的注意点&#xff0c;但相比于传统的入耳式耳机&#xff0c;骨传导耳机普遍对听力的影响较小。以下是一些骨传…

排除WLAN故障

排除网络故障 第 1步&#xff1a;测试连接 第2步&#xff1a;调查问题的原因并且记录自己发现的问题 1. 无线路由器IP地址错误&#xff0c;不在同一个网段 2. Home PC 应该要和Home wireless连接 3. table pc应设为DHCP 4. 测试url www.netacad.pt ,发现无法连通&#xf…

echarts条形图添加滚动条

效果展示: 测试数据: taskList:[{majorDeptName:测试,finishCount:54,notFinishCount:21}, {majorDeptName:测试,finishCount:54,notFinishCount:21}, {majorDeptName:测试,finishCount:54,notFinishCount:21}, {majorDeptName:测试,finishCount:54,notFinishCount:21}, {maj…

机器学习 | 掌握 K-近邻算法 的理论实现和调优技巧

目录 初识K-近邻算法 距离度量 K值选择 kd树 数据集划分 特征预处理 莺尾花种类预测(实操) 交叉验证与网格搜索 初识K-近邻算法 K-近邻算法&#xff08;K-Nearest Neighbor&#xff0c;KNN&#xff09;是一种基本的分类和回归算法。它的基本思想是通过找出与新对象最近…

PhpStorm调试docker容器中的php项目

背景 已经通过docker容器启动了一个web服务&#xff0c;并在宿主机可以访问http://localhost:8080访问网页。 现在想使用phpstorm打断点调试代码。 方法 1. 容器内安装xdebug 进入容器 docker exec -it <container-name> bash为php安装xdebug拓展 apt install php8…

TypeScript(六) 循环语句

1. TypeScript循环语句 1.1. 简述 有的时候&#xff0c;我们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。   循环语句允许我们多次执行一个语句或语句组…

C#需要学到什么程度才能做MES系统开发工作?

C#需要学到什么程度才能做MES系统开发工作&#xff1f; 在开始前我分享下我的经历&#xff0c;刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;两年时间从3k薪资涨到18k的&#xff0c; 我师父给了一些C#学习方法和资料&#xff0c;让我不断提升自己&#xff…

GNSS定位技术总结与PPP定位技术

1.统一观测值方程 2.PPP方程构建 站间单差方程如下&#xff1a; 同样的&#xff0c;设计矩阵也更加庞大&#xff1a; 站间单差消除了卫星轨道、卫星钟、电离层、对流层以及卫星端的伪距和载波硬件延迟的影响。但在PPP中&#xff0c;我们无法通过站间单差消除这些影响&#xff…

【Lazy ORM 整合druid 实现mysql监控】

Lazy ORM 整合druid 实现mysql监控 JDK 17 Lazy ORM框架地址 up、up欢迎start、issues 当前项目案例地址 框架版本描述spring-boot3.0.7springboot框架wu-framework-web1.2.2-JDK17-SNAPSHOTweb容器Lazy -ORM1.2.2-JDK17-SNAPSHOTORMmysql-connector-j8.0.33mysql驱动druid-…

四、ES集群安全策略设置 X-pack

本文主要是结合ES集群搭建时使用&#xff0c;并且适用于ES7.x以上版本 背景及安全策略方案对比 ES 7.x以下版本默认几乎没有任何安全策略&#xff0c;如果集群IP、端口被暴露&#xff0c;在可访问的情况下任何用户都可以对索引进行管理以及数据的增删改查等&#xff0c;基于此需…

如何使用docker快速安装Plik并实现固定公网地址远程访问

文章目录 推荐1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点…

GitCode|部分项目开源代码

1.EasyKeyboard 基于MFC的简单软键盘&#xff0c;使用vs2017开发 PangCoder / EasyKeyboard GitCode基于Windows平台的软键盘&#xff0c;使用VS2017开发&#xff0c;使用MFC框架https://gitcode.net/qq_36251561/easykeyboard 2.EncoderSimulator 基于WPF应用的编码器模拟工…

CCF-CSP 202312-1 仓库规划(Java、C++、Python)

文章目录 仓库规划问题描述输入格式输出格式样例输入样例输出子任务 满分代码JavaCPython 仓库规划 问题描述 西西艾弗岛上共有 n n n 个仓库, 依次编号为 1 ⋯ n 1 \cdots n 1⋯n 。每个仓库均有一个 m m m 维向量的位置编码, 用来表示仓库间的物流运转关系。 具体来说,…

全新魅思V20正规视频影视系统源码/APP+H5视频影视源码

全新魅思V20正规视频影视系统源码&#xff0c;APPH5视频影视源码。会员花费三千购入的&#xff0c;具体搭建教程放压缩包了&#xff01; 有兴趣的下载自行研究吧&#xff0c;搭建一共要用到3个域名&#xff0c;可以拿二级域名搭建。

OpenHarmony—环境准备

JS SDK安装失败处理指导 问题现象 下载JS SDK时&#xff0c;下载失败&#xff0c;提示“Install Js dependencies failed”。解决措施 JS SDK下载失败&#xff0c;一般情况下&#xff0c;主要是由于npm代理配置问题&#xff0c;或未清理npm缓存信息导致&#xff0c;可按照如…