用C语言实现简易贪吃蛇

简易贪吃蛇(c语言)

贪吃蛇游戏是指在规定的方框内,通过操控键盘上的 ↑ ↓ ← → 键 来进行贪吃蛇的方向控制。在方框内吃掉随机出现的食物来延长蛇的身体。

需要的头文件及函数原型

#ifndef __SNAKE_H__
#define __SNAKE_H__
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#define WALL "X"   //墙壁符号
#define FOOD "O"   //蛇的符号
#define INIT_X 20  //初始化蛇的位置 X坐标
#define INIT_Y 20  //初始化蛇的位置 Y坐标
typedef int SDataType;
typedef struct SnakeNode {
    int x;//节点的x坐标
    int y;//节点的y坐标
    struct SnakeNode * next;
}SnakeNode ,*pSnakeNode;

enum Direction {
    UP = 1,
    DOWN,
    LEFT,
    RIGHT,
};//蛇当前运行方向状态
enum State
{
    OK,  //存活
    NORMAL_END,  //按esc正常退出
    KILL_BY_WALL,//撞到墙壁死亡
    KILL_BY_SELF,//撞到蛇身死亡
};//蛇的状态
typedef struct Snake
{
    SnakeNode *psnake;// 蛇的头结点

    int TotalScore; //总分数
    int AddScore;   //分数增加比
    int SleepTime;  //蛇的运行速度(电脑刷新间隔)
    enum Direction Dir; //蛇当前所处的方向状态
    enum State Status;  //社当前的状态
    SnakeNode *pFood;   //食物所处的位置坐标
 }Snake , *pSnake;
//蛇结构体
void GameStart(pSnake ps);//游戏开始函数
void InitSnake(pSnake ps);//初始化蛇函数
void SetPos(int x, int y);//运用控制台命令设置节点出现的位置
void PrintMap();          //打印地图
pSnakeNode BuyNode();     //生成一个节点
void CreateSnake(pSnake ps);//创建一个蛇
void CreateFood(pSnake ps);//创建一个食物节点
void GameRun(pSnake ps);  //游戏运行函数
void Pause();             //游戏暂停函数
int HasNextNode(pSnakeNode pf, pSnakeNode pn);//判断蛇头所指向的下一个节点是不是食物节点
void EatFood(pSnake ps, pSnakeNode pn);//进食食物使得整体延长
void NoFood(pSnake ps, pSnakeNode pn); //没有食物节点向蛇头方向运行
void SnakeMove(pSnake ps);//蛇的方向操作函数
void helpinfo(pSnake ps); //当前状态
void KillByWall(pSnake ps);//被墙撞死
void KillBySelf(pSnake ps);//蛇吃自己
void GameEnd(pSnake ps); //游戏结束释放空间,打印结果
#endif//__SNAKE_H__
//test.c 
    #include "Snake.h"
void test()
{
    Snake ps;
    GameStart(&ps);
    GameRun(&ps);
    GameEnd(&ps);
}
int main()
{
    test();
    return 0;
}

函数实现分析

    #include "Snake.h"
void InitSnake(pSnake ps)
//初始化蛇函数
{
    ps->AddScore = 10;
    ps->Dir = UP;
    ps->SleepTime = 200;
    ps->TotalScore = 0;
    ps->psnake = NULL;
    ps->Status = OK;
}
void SetPos(int x, int y)
//运用控制台命令设置节点出现的位置
{
    COORD pos; 
    HANDLE handle;
    pos.X = x;
    pos.Y = y;
    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(handle,pos);
}

COORD 是 api函数。它的作用是确定横纵坐标。
HANDLE 是句柄,用来函数运行时的状态。
GetStdHandle()获取句柄的状态。STD_OUT_HANDLE获取标准输出的句柄。
SetConsoleCursorPosition(handle, pos)。将句柄当前的状态传入,该函数
可以确定将光标移动到 pos所指向的位置。

void PrintMap()
//打印地图
{
    int i = 0;
    for (i = 0; i <58; i+=2)
    {
        SetPos(i, 0);
        printf(WALL);
    }
    for (i = 0; i <58; i+=2)
    {
        SetPos(i, 27);
        printf(WALL);
    }
    for (i = 0; i < 27; i++)
    {
        SetPos(0, i);
        printf(WALL);
    }
    for (i = 0; i < 27; i++)
    {
        SetPos(56, i);
        printf(WALL);
    }
}

在给定的范围内打印边界。假设整个地图的范围是 27 * 58.

pSnakeNode BuyNode()
//生成一个节点
{
    pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (cur == NULL)
    {
        printf("BuyNode :: malloc failue ! \n");
    }
    cur->next = NULL;
    return cur;
}
void CreateSnake(pSnake ps)
//创建一个蛇
{
    pSnakeNode cur = NULL;
    pSnakeNode first = NULL;
    first = ps->psnake;

    int i = 0;

    for (i = 0; i < 5; i++)
    {
        cur = BuyNode();
        cur->x = 20 + 2*i;
        cur->y = 20;
        SetPos(cur->x, cur->y);
        printf(FOOD);
        cur->next = first;
        first = cur;
    }


    ps->psnake = first;
}

采用头插的方式初始化蛇的样子。假定蛇身的长度为5。注意在X轴方向上才有用间距为偶数的移动方式。

void CreateFood(pSnake ps)
//创建一个食物节点
{
//对蛇的食物进行初始化
    ps->pFood = BuyNode();
    pSnakeNode cur = ps->psnake;
    //食物不能出现在边界
    ps->pFood->y = rand() % 25 + 1;
    do
    {
        ps->pFood-> x= rand() %53+2;
    } while (ps->pFood->x % 2 != 0);

    //食物不能出现在蛇身
    while (cur)
    {
        if ((cur->x == ps->pFood->x) && (cur->y == ps->pFood->y))
        {
            CreateFood(ps);
            return;
        }
        cur = cur->next;
    }

    SetPos(ps->pFood->x, ps->pFood->y);
    printf(FOOD);
    //食物要随机出现;

}

创建一个食物节点。食物节点要随机出现所以用到 rand()函数获取随机数。另一方面。食物不能出现在边界,并且食物不能出现在蛇的身体里。最后食物的X坐标必须是偶数否则蛇吃不到食物。

void welcome()
{
    system("mode con cols=100 lines=30");
    //采用mode 命令来初始化黑框的大小 假定行为30 列为100
    SetPos(50, 13);
    printf("欢迎来到贪吃蛇游戏!\n");
    system("pause");
    system("cls");
    SetPos(50, 13);
    printf("使用↑↓←→来进行操控,F1加速,F2减速。\n");
    SetPos(50, 14);
    printf("使用空格键进行暂停\n");
    SetPos(50, 15);
    printf("使用esc键进行退出操作。\n");
    system("pause");
    system("cls");
}

mode命令可以确定黑框的大小。 它的使用格式是:

mode con cols=数字 lines=数字

void GameStart(pSnake ps)
//游戏开始函数
{
    InitSnake(ps);
    welcome();
    PrintMap();
    CreateSnake(ps);
    CreateFood(ps);
}

void Pause()
//游戏暂停函数
{
    while (1)
    {
        Sleep(100);
        if(GetAsyncKeyState(VK_SPACE))
        {
            return;
    }
    }
}

游戏暂停函数,程序运行的时候当从键盘上接收到空格键,游戏界面暂停。实际上是将当前程序执行到一个死循环里边。只有当再次按下空格键时跳出循环。游戏继续。

   void GameRun(pSnake ps)
//游戏运行函数
{
    do
    {
        helpinfo(ps);
        if ((GetAsyncKeyState(VK_UP)) && (ps->Dir != DOWN))
        {
            ps->Dir = UP;
        }
        else if ((GetAsyncKeyState(VK_DOWN)) && (ps->Dir != UP))
        {
            ps->Dir = DOWN;
        }
        else if ((GetAsyncKeyState(VK_LEFT)) && (ps->Dir != RIGHT))
        {
            ps->Dir = RIGHT;
        }
        else if ((GetAsyncKeyState(VK_RIGHT)) && (ps->Dir != LEFT))
        {
            ps->Dir = LEFT;
        }
        else if (GetAsyncKeyState(VK_F1))
        {
            //加速
            if (ps->SleepTime > 100)
            {
                ps->AddScore += 2;
                ps->SleepTime -= 20;
            }
        }
        else if (GetAsyncKeyState(VK_F2))
        {
            //减速
            if (ps->SleepTime < 300)
            {
                ps->AddScore -= 2;
                ps->SleepTime += 20;
            }
        }
        else if (GetAsyncKeyState(VK_SPACE))
        {
            Pause();
        }
        else if (GetAsyncKeyState(VK_ESCAPE))
        {
            //退出
            ps->Status = NORMAL_END;
        }
        KillBySelf(ps);
        KillByWall(ps);
        SnakeMove(ps);

        Sleep(ps->SleepTime);
    } while (ps->Status == OK);
}

游戏的运行函数,使用了一个do while()语句来进行运作 当ps->status蛇的状态来判断游戏是否需要继续进行。当蛇的状态不为 OK时跳出循环。GetAsyncKeyState()函数是判断键盘上的虚拟键有没有被按下。VK_UP是上键,依次类推获得 ↑ ↓ ← →, F1加速,F2减速, SPACE空格键暂停 ,ESC退出。
值得注意的是,蛇不可能旋转180度吃自己,所以if条件里要判断该种条件。
Sleep()是睡眠函数,它用来限制程序运行速度,进而限制蛇的移动速度。
当if条件满足的时候就改变蛇的ps->Dir方向状态。

void SnakeMove(pSnake ps)
//蛇的方向操作函数
{

    pSnakeNode pnext =BuyNode(); 
    switch (ps->Dir)
    {
    case UP:
        pnext->x  = ps->psnake->x;
        pnext->y = ps->psnake->y - 1;
        if ( HasNextNode(ps->pFood ,pnext) )
        {
            EatFood(ps ,pnext);
            CreateFood(ps);
            ps->TotalScore += ps->AddScore;
        }
        else
        {
            NoFood(ps,pnext);
        }
         break;
    case DOWN:
        pnext->x = ps->psnake->x;
        pnext->y = ps->psnake->y + 1;
        if (HasNextNode(ps->pFood, pnext))
        {
            EatFood(ps, pnext);
            CreateFood(ps);
            ps->TotalScore += ps->AddScore;
        }
        else
        {
            NoFood(ps, pnext);
        }
         break;
    case LEFT:
        pnext->x = ps->psnake->x+2;
        pnext->y = ps->psnake->y ;
        if (HasNextNode(ps->pFood, pnext))
        {
            EatFood(ps, pnext);
            CreateFood(ps);
            ps->TotalScore += ps->AddScore;
        }
        else
        {
            NoFood(ps, pnext);
        }
        break;
    case RIGHT:
        pnext->x = ps->psnake->x - 2;
        pnext->y = ps->psnake->y;
        if (HasNextNode(ps->pFood, pnext))
        {
            EatFood(ps, pnext);
            CreateFood(ps);
            ps->TotalScore += ps->AddScore;
        }
        else
        {
            NoFood(ps, pnext);
        }
        break;
    }
}

由于在GameRun()中可能改变蛇的运动状态。所以move函数里需要判断下一步所需要移动的位置。如果下一步是一个食物节点,蛇需要加长。如果下一个节点没有食物那就继续往前走。获取一个pnext指针,将pnext 的赋值根据情况区别开来。如果是向上走,那么将ps->psnake蛇头的y轴减1赋值给pnext。然后使用HasNextNode()判断下一个节点是否为食物节点。

int HasNextNode(pSnakeNode pf, pSnakeNode pn)
//判断蛇头所指向的下一个节点是不是食物节点
{

    return (pf->x == pn->x) && (pf->y == pn->y);
}

判断pnext的x .y坐标是否与ps->pfood相等,如果相等代表着下一个节点就是食物。

void EatFood(pSnake ps,pSnakeNode pn)
//进食食物使得整体延长
{
    pn->next = ps->psnake;
    ps->psnake = pn;
    pSnakeNode cur = ps->psnake;
    SetPos(ps->psnake->x, ps->psnake->y);
    printf(FOOD);

}
void NoFood(pSnake ps, pSnakeNode pn)
//没有食物节点向蛇头方向运行
{
    pn->next = ps->psnake;
    ps->psnake  = pn;
    pSnakeNode cur = ps->psnake ;
    while (cur->next->next != NULL)
    {
        SetPos(cur->x, cur->y);
        printf(FOOD);
        cur = cur->next;
    }
    SetPos(cur->next->x, cur->next->y);
    printf(" ");
    free(cur->next);
    cur->next = NULL;
}

EatFood()函数只需要将pnext头插入蛇身体里,然后再次打印蛇头就行了。
NoFood()函数。需要将pnext头插入蛇身体 (现在 5+1)。然后遍历打印蛇,且遍历的条件是
cur->next->next != NULL。(打印了 5)最后一个(剩下的1)置为空格 就不改变蛇的身体完成移动。

void helpinfo(pSnake ps)
//当前状态
{
    SetPos(62, 12);
    printf("Sleeptime : %d \n", ps->SleepTime);
    SetPos(63, 13);
    printf("AddScore : %d \n", ps->AddScore );
    SetPos(63, 14);
    printf("TotalScore : %d \n", ps->TotalScore);
}
void KillByWall(pSnake ps)
//被墙撞死
{
    if (ps->psnake->y == 0 || ps->psnake->y == 27 || ps->psnake->x == 57 || ps->psnake->x == 0)
    {
        ps->Status = KILL_BY_WALL;
    }
}
void KillBySelf(pSnake ps)
//蛇吃自己
{
    pSnakeNode cur = ps->psnake;
    cur = cur->next;
    while (cur)
    {
        if ((ps->psnake->x == cur->x) && (ps->psnake->y == cur->y))
        {
            ps->Status = KILL_BY_SELF;
            break;
        }
        cur = cur->next;
    }
}

蛇被墙撞死要看当前状态是不是在边界上,如果在边界上那么就要改变Status为KILL_BY_WALL。

蛇自杀,判断蛇的头结点在不在蛇的身体里。通过while遍历整个蛇的身体只要发现条件(ps->snake->x == cur->x && ps->snake->y == cur->y)就自杀。

void GameEnd(pSnake ps)
//游戏结束释放空间,打印结果
{
    if (ps->Status == KILL_BY_SELF)
    {
        system("cls");
        printf("有时候饿的连自己都想吃······ \n");
        printf("你获得的总分 : %d \n", ps->TotalScore);
        system("pause");
    }
    if (ps->Status == KILL_BY_WALL)
    {
        system("cls");
        printf("撞了南墙不回头 ! \n");
        printf("你获得的总分 : %d \n", ps->TotalScore);
        system("pause");
    }
    if (ps->Status == NORMAL_END)
    {
        system("cls");
        printf("平稳着陆 ! 稳 ! \n");
        printf("你获得的总分 : %d \n", ps->TotalScore);
        system("pause");
    }

    pSnakeNode cur = ps->psnake;
    pSnakeNode del = NULL;
    while (cur)
    {
        del = cur;
        cur = cur->next;
        free(del);
        del = NULL;
    }
    ps->psnake = NULL;
    cur = ps->pFood;
    free(cur);
    ps->pFood = NULL;

}

检查状态打印结果 ,游戏结束时要释放节点。

运行结果

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/H_Strong/article/details/82051562