C语言#贪吃蛇#自动寻路

程序小白的自学编程之路,看完了基础的C语言语法之后,在网上查找了一些贪吃蛇的源码开始学习.本文源码仅用于学习与记录.

从最开始的控制台版本,会出现闪烁的问题;到后面下载了easyx库,解决了闪烁的问题,同时界面更丰富了.此版本有四种模式:普通模式、穿墙模式、无敌模式和自动模式.

该版本的特点是使用数组实现蛇的移动,可自动寻找距离最短的食物,食物数量可设置,位置随机。最后附带源码和注释。

我们先看主函数

int main()
{
    DWORD t1 = 0, t2 = 0; //延时时间(32位无符号整型)

    mciSendString(L"open D:\\22-音效\\ACG经典爆裂双吉他串烧.wav alias bgm", 0, 0, 0); //打开背景音乐
    mciSendString(L"play bgm", 0, 0, 0); //播放背景音乐

    initgraph(500, 520); //初始化图形界面: x=500, y=520
    start(); //开始界面
    outtextxy(170, 100, L"->"); //初始默认模式一

    chose(); //选择游戏模式;Enter键进入游戏

    init();  //初始化游戏参数

    while (1)
    {
        t2 = GetTickCount();

        DrawMap(); //绘制地图
        if (_kbhit()) //等待键盘输入命令
        {
            ChangeDir(); //接收命令
            move(); //移动

            t2 = GetTickCount();
            t1 = t2;
        }
        if (t2 - t1 > time1)
        {
            move(); //移动
            if (mode == 3)
            {
                autoseek();
            }
            t1 = t2; //更新时间
        }
    }
    return 0;
}

t1,t2是计时,time1决定蛇的移动速度,time1的大小可以根据得分越高而减少,提高游戏难度.

mciSendString()是播放背景音乐函数,L字母后面的内容是存放音频文件的位置,该函数的参数设置大家可以百度查询.

initgraph()函数是easyx库里的一个函数,需要先去easyx官网下载该库,安装很简单.然后再#include头文件就可以使用了.该函数的作用是初始化一个图形界面,第一个参数表示的是横向(x),第二个参数表示的是纵向(y).它的大小根据存放蛇的数组大小而设置,数值上是10倍的关系.

接下来是start()函数,它的功能是初始化一个开始界面.

void start()
{
    setbkcolor(YELLOW); //设置窗口背景色;
    cleardevice(); //清除屏幕
    setbkmode(TRANSPARENT); //设置字体背景色为透明
    settextcolor(RED); //设置字体颜色为红色
    settextstyle(20, 0, _T("黑体")); //字体大小:20;字体类型:黑体
    outtextxy(200, 40, L"进入模式");
    outtextxy(190, 100, L"1.普通模式");
    outtextxy(190, 150, L"2.穿墙模式");
    outtextxy(190, 200, L"3.无敌模式");
    outtextxy(190, 250, L"4.自动模式");
    outtextxy(65, 300, L"数字键 1,2,3,4选择模式,Enter键进入游戏");
    outtextxy(65, 350, L"字母键 W,S,A,D 控制方向: 上 下 左 右 ");
    outtextxy(65, 400, L"空格键 暂停, Esc键 结束游戏");
}

每个函数后面都已经注释了该函数的功能,这里主要讲解一下outtextxy()函数.它的功能是指定到设置的(x,y)坐标下显示相应的文本内容.

好了,接下来chose()函数

void chose()
{
    while (1)
    {
        switch (_getch()) //接收字符
        {
        case '1':
            start();
            outtextxy(170, 100, L"->");
            mode = 0;
            break;
        case '2':
            start();
            outtextxy(170, 150, L"->");
            mode = 1;
            break;
        case '3':
            start();
            outtextxy(170, 200, L"->");
            mode = 2;
            break;
        case '4':
            start();
            outtextxy(170, 250, L"->");
            mode = 3;
            break;
        case 13: //回车键
            return; //返回
            break;
        }
    }
}

可以看到,chose()函数里面有一个while循环,因为该函数的主要作用是选择一种游戏模式.由变量mode来接收._getch()函数是从键盘接收一个字符,本文通过数字键1234选择四种模式,然后回车键确定.

选择好了游戏模式,接下来就是通过init()函数初始化游戏参数啦.

void init()
{
    srand((unsigned)time(NULL)); //随机数种子
    setbkcolor(BLACK); //设置背景颜色
    //初始化分数
    score = 0;
    //初始化map数组
    memset(map, SPACE, sizeof(map));
    // 每一行的 第一个 和 最后一个 是墙
    for (int i = 0; i < ROW; i++)
    {
        map[i][0] = map[i][COL - 1] = WALL;
    }
    //每一列的 第二个 和 倒数第二个是墙
    for (int j = 1; j < COL - 1; j++)
    {
        map[0][j] = map[ROW - 1][j] = WALL;
    }

    SnakeSize = 2; //蛇初始长度
    SnakeDir = 'D';
    //初始化蛇的位置
    map[1][2] = SNAKE; //头部
    map[1][1] = SNAKE; //身体
    snake[0].X = 1; snake[0].Y = 2; //头部位置
    snake[1].X = 1; snake[1].Y = 1; //身体位置
    path_mark[0][0] = path_mark[0][1] = path_mark[1][0] = path_mark[1][1] = true;
    //初始化食物位置
    for (int i = 0; i < number; i++)
    {
        addfood(i);
    }
    //初始化路径(ROW-2,COL-2)
    for (int i = 0; i < ROW - 2; i += 2)
    {
        for (int j = 0; j < COL - 2; j += 2)
        {
            path[i][j] = 'D';
            path[i][j + 1] = 'S';
            path[i + 1][j + 1] = 'A';
            path[i + 1][j] = 'W';
        }
    }
    //初始化路径标志(ROW-2,COL-2)
    for (int i = 0; i < ROW - 2; i++)
    {
        for (int j = 0; j < COL - 2; j++)
        {
            path_mark[i][j] = false;
        }
    }
    if (mode == 3)
    {
        autoseek();
    }
}

先看第一句,srand()函数,它是一个随机数种子,设置一系列随机数.因为我们需要随机食物的位置.
memset()函数将SPACE写入到map数组里,起的是一个初始化的作用.这里提一下,本文使用枚举类型定义了四个变量,SPACE表示空地,WALL表示墙,SNAKE表示蛇,FOOD表示食物.

enum game //枚举类型
{
    SPACE, WALL, SNAKE, FOOD //SPACE = 0, WALL = 1, SMAKE = 2, FOOD = 3
};

SnakeSize表示蛇的尺寸,SnakeDir表示蛇的方向,而snake数组存放的是蛇的位置.这里讲解一下COORD参数,它的作用是定义X,Y两个变量,我们用它来存储数组的行和列.
接下来是addfood()函数,它的作用是随机添加食物,food数组是用来存放食物位置的,number变量定义的是食物数量.

void addfood(int order)
{
    int row, col;
    int flag = 0;
    //判断是否有空位
    for (int i = 1; i <= ROW - 2; i++)
    {
        for (int j = 1; j <= COL - 2; j++)
        {
            if (map[i][j] == SPACE)
            {
                flag = 1;
                break;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
    //如果有空位,设置食物位置范围
    if (flag == 1)
    {
        do
        {
            row = rand() % (ROW - 2) + 1;
            col = rand() % (COL - 2) + 1;
        } while (map[row][col] != SPACE);
        map[row][col] = FOOD;
        food[order].X = row;
        food[order].Y = col;
    }
}

这里的flag变量是判断地图上是否还有空位存放食物,如果有空位的话我们就存放食物,没有的话就不生成食物了.

接下来我们主要讲解一下autoseek()函数,它在自动模式也就是mode=3的时候才生效.

void autoseek()
{
    int temp = 0; //最短距离的食物序号
    int d = pow((snake[0].X - food[0].X), 2) + pow((snake[0].Y - food[0].Y), 2);
    //寻找最短距离的食物
    if (number > 1)
    {
        for (int i = 1; i < number; i++)
        {
            if (d > pow((snake[0].X - food[i].X), 2) + pow((snake[0].Y - food[i].Y), 2))
            {
                d = pow((snake[0].X - food[i].X), 2) + pow((snake[0].Y - food[i].Y), 2);
                temp = i; //存储最短距离的食物序号
            }
        }
    }
    //遍历整个路径,删除无效路径
    for (int i = 0; i <= COL - 4; i += 2)
    {
        for (int j = 1; j <= ROW - 5; j += 2)
        {
            //纵向
            if ((map[j][i + 1] != SNAKE && map[j][i + 2] != SNAKE && map[j + 1][i + 1] != SNAKE && map[j + 1][i + 2] != SNAKE)
                || (map[j + 2][i + 1] != SNAKE && map[j + 2][i + 2] != SNAKE && map[j + 3][i + 1] != SNAKE && map[j + 3][i + 2] != SNAKE))
            {
                //设置独立环路
                path[j + 1][i] = 'D';
                path[j][i + 1] = 'A';
                //标记为0
                if (map[j][i + 1] != SNAKE && map[j][i + 2] != SNAKE && map[j + 1][i + 1] != SNAKE && map[j + 1][i + 2] != SNAKE)
                {
                    path_mark[j - 1][i] = false;
                    path_mark[j - 1][i + 1] = false;
                    path_mark[j][i] = false;
                    path_mark[j][i + 1] = false;
                }
                if (map[j + 2][i + 1] != SNAKE && map[j + 2][i + 2] != SNAKE && map[j + 3][i + 1] != SNAKE && map[j + 3][i + 2] != SNAKE)
                {
                    path_mark[j + 1][i] = false;
                    path_mark[j + 2][i] = false;
                    path_mark[j + 1][i + 1] = false;
                    path_mark[j + 2][i + 1] = false;
                }
            }
        }
    }
    for (int i = 0; i <= ROW - 4; i += 2)
    {
        for (int j = 1; j <= COL - 5; j += 2)
        {
            //横向
            if ((map[i + 1][j] != SNAKE && map[i + 1][j + 1] != SNAKE && map[i + 2][j] != SNAKE && map[i + 2][j + 1] != SNAKE)
                || (map[i + 1][j + 2] != SNAKE && map[i + 1][j + 3] != SNAKE && map[i + 2][j + 2] != SNAKE && map[i + 2][j + 3] != SNAKE))
            {
                //设置独立环路
                path[i][j] = 'S';
                path[i + 1][j + 1] = 'W';
                //标记为0
                if (map[i + 1][j] != SNAKE && map[i + 1][j + 1] != SNAKE && map[i + 2][j] != SNAKE && map[i + 2][j + 1] != SNAKE)
                {
                    path_mark[i][j - 1] = false;
                    path_mark[i + 1][j - 1] = false;
                    path_mark[i][j] = false;
                    path_mark[i + 1][j] = false;
                }
                if (map[i + 1][j + 2] != SNAKE && map[i + 1][j + 3] != SNAKE && map[i + 2][j + 2] != SNAKE && map[i + 2][j + 3] != SNAKE)
                {
                    path_mark[i][j + 1] = false;
                    path_mark[i][j + 2] = false;
                    path_mark[i + 1][j + 1] = false;
                    path_mark[i + 1][j + 2] = false;
                }
            }
        }
    }
    //生成路径
    COORD head; //头部位置
    head.X = snake[0].X - 1; head.Y = snake[0].Y - 1;
    head.X = head.X % 2 == 1 ? head.X - 1 : head.X; //头部位置回到左上角(奇数,-1;偶数,不变)
    head.Y = head.Y % 2 == 1 ? head.Y - 1 : head.Y;
    COORD end; //食物位置
    end.X = food[temp].X - 1; end.Y = food[temp].Y - 1;
    end.X = end.X % 2 == 1 ? end.X - 1 : end.X; //食物位置左上角(奇数,-1;偶数,不变)
    end.Y = end.Y % 2 == 1 ? end.Y - 1 : end.Y;

    int dx, dy; //头部与食物位置之间的相对关系
    dx = head.X > end.X ? -1 : head.X < end.X ? 1 : 0; //食物在下边,1;在上边,-1
    dy = head.Y > end.Y ? -1 : head.Y < end.Y ? 1 : 0; //食物在右边,1;在左边,-1

    while (head.Y != end.Y)
    {
        //横向
        head.Y += dy;
        if (head.Y % 2 == 1) //只在奇数时判断
        {
            if (!(path_mark[head.X][head.Y] && path_mark[head.X + 1][head.Y + 1]))
            {
                path[head.X][head.Y] = 'D';
                path[head.X + 1][head.Y + 1] = 'A';
                path_mark[head.X][head.Y - 1] = true; path_mark[head.X][head.Y] = true; path_mark[head.X + 1][head.Y - 1] = true; path_mark[head.X + 1][head.Y] = true;
                path_mark[head.X][head.Y + 1] = true; path_mark[head.X][head.Y + 2] = true; path_mark[head.X + 1][head.Y + 1] = true; path_mark[head.X + 1][head.Y + 2] = true;
            }
        }
    }
    while (head.X != end.X)
    {
        //纵向
        head.X += dx;
        if (head.X % 2 == 1) //只在奇数时判断
        {
            if (!(path_mark[head.X][head.Y] && path_mark[head.X + 1][head.Y + 1]))
            {
                path[head.X + 1][head.Y] = 'W';
                path[head.X][head.Y + 1] = 'S';
                path_mark[head.X - 1][head.Y] = path_mark[head.X - 1][head.Y + 1] = path_mark[head.X][head.Y] = path_mark[head.X][head.Y + 1] = true;
                path_mark[head.X + 1][head.Y] = path_mark[head.X + 1][head.Y + 1] = path_mark[head.X + 2][head.Y] = path_mark[head.X + 2][head.Y + 1] = true;
            }
        }
    }
    SnakeDir = path[snake[0].X - 1][snake[0].Y - 1];
}

可以看到前面的变量temp和d,是因为地图上的食物数量超过1时,需要判断哪个食物离蛇头的距离最短.因此需要计算头部到每个食物的距离d,使用temp记录距离最短的那个食物的编号.

接下来需要重点介绍两个数组,一个是path,另一个是path_mark.这两个数组的大小与map大小差不多,它的范围也就是蛇能够移动的范围.
首先来看path数组,它存放的是蛇的移动方向’D’,‘A’,‘S’,‘W’,也就是上下左右.这个数组的作用规划蛇的移动方向,让蛇在一个环路中移动,这样蛇头就不可能会撞到身体和墙等物体.
而path_mark数组的作用是标记路径,因为在规划路径的时候,如果是两个已存在的路径时,不能进行组合路径.组合路径的原则是存在未规划的路径.

del_path()函数和form_path()函数分别是删除路径和生成路径.
首先是del_path()函数,分别从横向和纵向遍历整个路径,当路径上不存在蛇的时候,需要删除该路径,并把标志置为0.
而form_path()函数,我们需要取出蛇头位置和食物位置,分别判断行号和列号是否为奇数,如果是奇数的话就减一,偶数不变.这样做的目的是让坐标回归到路径的左上角,方便计算蛇头和食物的相对位置关系(dx,dy).在生成路径过程中,利用path_mark数组判断是否是未添加过的路径,如果是的话,就需要把它添加到路径中;已经是路径的不做处理.
生成好路径之后,根据蛇头的位置检索它的移动方向,赋予给SnakeDir.

根据SnakeDir接收到的移动方向,在move()函数中,我们对蛇头位置进行相应的加减,用next变量接收,然后判断next位置上的值是什么,分别SNAKE,WALL,FOOD和SPACE四种情况,分别做相应的处理即可.score是吃到食物后的得分,得分越高,time1越小,蛇的移动速度越快,因为刷新速度越快.

接下来就很简单了,DrawMap()函数的功能是遍历整个map数组,根据map上的数值绘制相应的图形,这里我们是用正方形来模仿墙和蛇,用不同的颜色进行区分.

最后是整个源代码.

#include<stdio.h>
#include<easyx.h>
#include<graphics.h>
#include<conio.h>
#include<time.h>
#include<windows.h>
#include<math.h>
#pragma comment(lib, "Winmm.lib")
void start(); //开始界面
void chose(); //选择模式;0:普通模式 1:穿墙模式 2:无敌模式 3:自动模式
void init(); //初始化游戏数据
void DrawMap(); //绘制地图
void ChangeDir(); //接收命令,改变方向
void move(); //蛇的移动
void addfood(int order); //添加食物
void autoseek(); //自动寻找食物模式

enum game //枚举类型
{
    SPACE, WALL, SNAKE, FOOD //SPACE = 0, WALL = 1, SMAKE = 2, FOOD = 3
};
int mode; //游戏模式
int score; //分数

const int ROW = 50; //地图行数
const int COL = 50; //地图列数
const int number = 3; //食物数量
const int width = 500; //图形宽度(x)
const int high = 520; //图形高度(y)
int map[ROW][COL]; //地图大小
COORD food[number]; //食物的位置
COORD snake[ROW * COL]; //蛇的位置
int SnakeSize; //蛇的尺寸
char SnakeDir; //蛇的移动方向
char path[ROW - 2][COL - 2] = { 0 }; //路径
bool path_mark[ROW - 2][COL - 2]; //路径标志

int time1 = 50; //设定延时时间

int main()
{
    DWORD t1 = 0, t2 = 0; //延时时间(32位无符号整型)

    mciSendString(L"open D:\\22-音效\\ACG经典爆裂双吉他串烧.wav alias bgm", 0, 0, 0); //打开背景音乐
    mciSendString(L"play bgm", 0, 0, 0); //播放背景音乐

    initgraph(500, 520); //初始化图形界面: x=500, y=520
    start(); //开始界面
    outtextxy(170, 100, L"->"); //初始默认模式一

    chose(); //选择游戏模式;Enter键进入游戏

    init();  //初始化游戏参数

    while (1)
    {
        t2 = GetTickCount();

        DrawMap(); //绘制地图
        if (_kbhit()) //等待键盘输入命令
        {
            ChangeDir(); //接收命令
            move(); //移动

            t2 = GetTickCount();
            t1 = t2;
        }
        if (t2 - t1 > time1)
        {
            move(); //移动
            if (mode == 3)
            {
                autoseek();
            }
            t1 = t2; //更新时间
        }
    }
    return 0;
}
void start()
{
    setbkcolor(YELLOW); //设置窗口背景色;
    cleardevice(); //清除屏幕
    setbkmode(TRANSPARENT); //设置字体背景色为透明
    settextcolor(RED); //设置字体颜色为红色
    settextstyle(20, 0, _T("黑体")); //字体大小:20;字体类型:黑体
    outtextxy(200, 40, L"进入模式");
    outtextxy(190, 100, L"1.普通模式");
    outtextxy(190, 150, L"2.穿墙模式");
    outtextxy(190, 200, L"3.无敌模式");
    outtextxy(190, 250, L"4.自动模式");
    outtextxy(65, 300, L"数字键 1,2,3,4选择模式,Enter键进入游戏");
    outtextxy(65, 350, L"字母键 W,S,A,D 控制方向: 上 下 左 右 ");
    outtextxy(65, 400, L"空格键 暂停, Esc键 结束游戏");
}
void chose()
{
    while (1)
    {
        switch (_getch()) //接收字符
        {
        case '1':
            start();
            outtextxy(170, 100, L"->");
            mode = 0;
            break;
        case '2':
            start();
            outtextxy(170, 150, L"->");
            mode = 1;
            break;
        case '3':
            start();
            outtextxy(170, 200, L"->");
            mode = 2;
            break;
        case '4':
            start();
            outtextxy(170, 250, L"->");
            mode = 3;
            break;
        case 13: //回车键
            return; //返回
            break;
        }
    }
}
void init()
{
    srand((unsigned)time(NULL)); //随机数种子
    setbkcolor(BLACK); //设置背景颜色
    //初始化分数
    score = 0;
    //初始化map数组
    memset(map, SPACE, sizeof(map));
    // 每一行的 第一个 和 最后一个 是墙
    for (int i = 0; i < ROW; i++)
    {
        map[i][0] = map[i][COL - 1] = WALL;
    }
    //每一列的 第二个 和 倒数第二个是墙
    for (int j = 1; j < COL - 1; j++)
    {
        map[0][j] = map[ROW - 1][j] = WALL;
    }

    SnakeSize = 2; //蛇初始长度
    SnakeDir = 'D';
    //初始化蛇的位置
    map[1][2] = SNAKE; //头部
    map[1][1] = SNAKE; //身体
    snake[0].X = 1; snake[0].Y = 2; //头部位置
    snake[1].X = 1; snake[1].Y = 1; //身体位置
    path_mark[0][0] = path_mark[0][1] = path_mark[1][0] = path_mark[1][1] = true;
    //初始化食物位置
    for (int i = 0; i < number; i++)
    {
        addfood(i);
    }
    //初始化路径(ROW-2,COL-2)
    for (int i = 0; i < ROW - 2; i += 2)
    {
        for (int j = 0; j < COL - 2; j += 2)
        {
            path[i][j] = 'D';
            path[i][j + 1] = 'S';
            path[i + 1][j + 1] = 'A';
            path[i + 1][j] = 'W';
        }
    }
    //初始化路径标志(ROW-2,COL-2)
    for (int i = 0; i < ROW - 2; i++)
    {
        for (int j = 0; j < COL - 2; j++)
        {
            path_mark[i][j] = false;
        }
    }
    if (mode == 3)
    {
        autoseek();
    }  
}
void DrawMap()
{
    BeginBatchDraw(); //开始绘图
    setbkcolor(BLACK); //设置背景颜色
    settextcolor(YELLOW); //设置文本颜色
    cleardevice(); //清屏

    WCHAR arr[10]; //保存成绩
    wsprintf(arr, L"总分:%d", score); //将成绩格式化输出到字符串arr中 
    outtextxy(0, 0, arr); //显示成绩

    for (int y = 0; y < ROW; y++) //y轴方向向下(行)
    {
        for (int x = 0; x < COL; x++) //x轴方向向右(列)
        {
            switch (map[y][x])
            {
            case SPACE:
                break;
            case WALL: //墙
                setlinecolor(BLACK); //设置线条颜色为黑色
                setfillcolor(WHITE); //设置填充颜色为白色
                fillrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30); //画方块,边长10,左右对角的坐标
                break;
            case SNAKE: //蛇
                if (y == snake[0].X && x == snake[0].Y)
                {
                    setfillcolor(LIGHTBLUE);
                    solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
                }
                else
                {
                    setfillcolor(RGB(255, 255, 0)); //黄  255 255 0
                    solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
                }
                break;
            case FOOD:
                setfillcolor(LIGHTRED); //食物颜色
                solidrectangle(x * 10, y * 10 + 20, x * 10 + 10, y * 10 + 30);
                break;
            default:
                break;
            }
        }
    }
    EndBatchDraw(); //结束绘制
}
void autoseek()
{
    int temp = 0; //最短距离的食物序号
    int d = pow((snake[0].X - food[0].X), 2) + pow((snake[0].Y - food[0].Y), 2);
    //寻找最短距离的食物
    if (number > 1)
    {
        for (int i = 1; i < number; i++)
        {
            if (d > pow((snake[0].X - food[i].X), 2) + pow((snake[0].Y - food[i].Y), 2))
            {
                d = pow((snake[0].X - food[i].X), 2) + pow((snake[0].Y - food[i].Y), 2);
                temp = i; //存储最短距离的食物序号
            }
        }
    }
    //遍历整个路径,删除无效路径
    for (int i = 0; i <= COL - 4; i += 2)
    {
        for (int j = 1; j <= ROW - 5; j += 2)
        {
            //纵向
            if ((map[j][i + 1] != SNAKE && map[j][i + 2] != SNAKE && map[j + 1][i + 1] != SNAKE && map[j + 1][i + 2] != SNAKE)
                || (map[j + 2][i + 1] != SNAKE && map[j + 2][i + 2] != SNAKE && map[j + 3][i + 1] != SNAKE && map[j + 3][i + 2] != SNAKE))
            {
                //设置独立环路
                path[j + 1][i] = 'D';
                path[j][i + 1] = 'A';
                //标记为0
                if (map[j][i + 1] != SNAKE && map[j][i + 2] != SNAKE && map[j + 1][i + 1] != SNAKE && map[j + 1][i + 2] != SNAKE)
                {
                    path_mark[j - 1][i] = false;
                    path_mark[j - 1][i + 1] = false;
                    path_mark[j][i] = false;
                    path_mark[j][i + 1] = false;
                }
                if (map[j + 2][i + 1] != SNAKE && map[j + 2][i + 2] != SNAKE && map[j + 3][i + 1] != SNAKE && map[j + 3][i + 2] != SNAKE)
                {
                    path_mark[j + 1][i] = false;
                    path_mark[j + 2][i] = false;
                    path_mark[j + 1][i + 1] = false;
                    path_mark[j + 2][i + 1] = false;
                }
            }
        }
    }
    for (int i = 0; i <= ROW - 4; i += 2)
    {
        for (int j = 1; j <= COL - 5; j += 2)
        {
            //横向
            if ((map[i + 1][j] != SNAKE && map[i + 1][j + 1] != SNAKE && map[i + 2][j] != SNAKE && map[i + 2][j + 1] != SNAKE)
                || (map[i + 1][j + 2] != SNAKE && map[i + 1][j + 3] != SNAKE && map[i + 2][j + 2] != SNAKE && map[i + 2][j + 3] != SNAKE))
            {
                //设置独立环路
                path[i][j] = 'S';
                path[i + 1][j + 1] = 'W';
                //标记为0
                if (map[i + 1][j] != SNAKE && map[i + 1][j + 1] != SNAKE && map[i + 2][j] != SNAKE && map[i + 2][j + 1] != SNAKE)
                {
                    path_mark[i][j - 1] = false;
                    path_mark[i + 1][j - 1] = false;
                    path_mark[i][j] = false;
                    path_mark[i + 1][j] = false;
                }
                if (map[i + 1][j + 2] != SNAKE && map[i + 1][j + 3] != SNAKE && map[i + 2][j + 2] != SNAKE && map[i + 2][j + 3] != SNAKE)
                {
                    path_mark[i][j + 1] = false;
                    path_mark[i][j + 2] = false;
                    path_mark[i + 1][j + 1] = false;
                    path_mark[i + 1][j + 2] = false;
                }
            }
        }
    }
    //生成路径
    COORD head; //头部位置
    head.X = snake[0].X - 1; head.Y = snake[0].Y - 1;
    head.X = head.X % 2 == 1 ? head.X - 1 : head.X; //头部位置回到左上角(奇数,-1;偶数,不变)
    head.Y = head.Y % 2 == 1 ? head.Y - 1 : head.Y;
    COORD end; //食物位置
    end.X = food[temp].X - 1; end.Y = food[temp].Y - 1;
    end.X = end.X % 2 == 1 ? end.X - 1 : end.X; //食物位置左上角(奇数,-1;偶数,不变)
    end.Y = end.Y % 2 == 1 ? end.Y - 1 : end.Y;

    int dx, dy; //头部与食物位置之间的相对关系
    dx = head.X > end.X ? -1 : head.X < end.X ? 1 : 0; //食物在下边,1;在上边,-1
    dy = head.Y > end.Y ? -1 : head.Y < end.Y ? 1 : 0; //食物在右边,1;在左边,-1

    while (head.Y != end.Y)
    {
        //横向
        head.Y += dy;
        if (head.Y % 2 == 1) //只在奇数时判断
        {
            if (!(path_mark[head.X][head.Y] && path_mark[head.X + 1][head.Y + 1]))
            {
                path[head.X][head.Y] = 'D';
                path[head.X + 1][head.Y + 1] = 'A';
                path_mark[head.X][head.Y - 1] = true; path_mark[head.X][head.Y] = true; path_mark[head.X + 1][head.Y - 1] = true; path_mark[head.X + 1][head.Y] = true;
                path_mark[head.X][head.Y + 1] = true; path_mark[head.X][head.Y + 2] = true; path_mark[head.X + 1][head.Y + 1] = true; path_mark[head.X + 1][head.Y + 2] = true;
            }
        }
    }
    while (head.X != end.X)
    {
        //纵向
        head.X += dx;
        if (head.X % 2 == 1) //只在奇数时判断
        {
            if (!(path_mark[head.X][head.Y] && path_mark[head.X + 1][head.Y + 1]))
            {
                path[head.X + 1][head.Y] = 'W';
                path[head.X][head.Y + 1] = 'S';
                path_mark[head.X - 1][head.Y] = path_mark[head.X - 1][head.Y + 1] = path_mark[head.X][head.Y] = path_mark[head.X][head.Y + 1] = true;
                path_mark[head.X + 1][head.Y] = path_mark[head.X + 1][head.Y + 1] = path_mark[head.X + 2][head.Y] = path_mark[head.X + 2][head.Y + 1] = true;
            }
        }
    }
    SnakeDir = path[snake[0].X - 1][snake[0].Y - 1];
}
void ChangeDir()
{
    switch (_getch())
    {
    case'A':
    case'a':
    case 75:
        if (SnakeDir != 'D') SnakeDir = 'A';
        break;
    case'D':
    case'd':
    case 77:
        if (SnakeDir != 'A') SnakeDir = 'D';
        break;
    case'W':
    case'w':
    case 72:
        if (SnakeDir != 'S') SnakeDir = 'W';
        break;
    case'S':
    case's':
    case 80:
        if (SnakeDir != 'W') SnakeDir = 'S';
        break;
    case 32:
        _getch();
        break;
    case 27:
        MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
        start(); //开始界面
        outtextxy(200, 100, L"->"); //初始默认模式一
        
        chose(); //选择模式;Enter键进入游戏
        
        init();  //初始化游戏界面
    default:
        break;
    }
}
void move()
{
    COORD next; //蛇头的下一个位置
    static int times = 1; //游戏难度
    //选择移动方向
    switch (SnakeDir)
    {
    case'A':
        next.X = snake[0].X;
        next.Y = snake[0].Y - 1;
        break;
    case'W':
        next.X = snake[0].X - 1;
        next.Y = snake[0].Y;
        break;
    case'D':
        next.X = snake[0].X;
        next.Y = snake[0].Y + 1;
        break;
    case'S':
        next.X = snake[0].X + 1;
        next.Y = snake[0].Y;
        break;
    default:
        break;
    }
    //判断下一个位置是什么
    switch (map[next.X][next.Y])
    {
    case SPACE:
        map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE; //地图蛇尾所在地置空
        for (int i = SnakeSize - 1; i > 0; i--) //蛇尾到蛇头整体移动一位
        {
            snake[i] = snake[i - 1];
            map[snake[i].X][snake[i].Y] = SNAKE; //蛇身位置必须赋予SNAKE
        }
        snake[0] = next; //将下一个位置赋予给头部
        map[snake[0].X][snake[0].Y] = SNAKE; //设置头部
        break;
    case WALL:
        if (mode == 1 || mode == 2) //模式一模式二可穿墙
        {
            map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE; //蛇尾置空
            for (int i = SnakeSize - 1; i > 0; i--) //蛇尾到蛇头整体移动一位
            {
                snake[i] = snake[i - 1];
                map[snake[i].X][snake[i].Y] = SNAKE;
            }
            switch (SnakeDir) //穿墙
            {
            case 'A':next.Y = COL - 2; break;
            case 'D':next.Y = 1; break;
            case 'W':next.X = ROW - 2; break;
            case 'S':next.X = 1; break;
            default:
                break;
            }
            snake[0] = next; //蛇头移动到新位置
            map[snake[0].X][snake[0].Y] = SNAKE; //新的蛇头所在的位置
        }
        else {
            MessageBox(GetHWnd(), L"游戏结束", L"SORRY", MB_OK);
            start(); //开始界面
            outtextxy(200, 100, L"->"); //初始默认模式一
            chose(); //选择模式;Enter键进入游戏
            init();  //初始化游戏界面
        }
        break;
    case SNAKE:
        map[snake[SnakeSize - 1].X][snake[SnakeSize - 1].Y] = SPACE; //地图蛇尾所在地置空
        for (int i = SnakeSize - 1; i > 0; i--) //蛇尾到蛇头整体移动一位
        {
            snake[i] = snake[i - 1];
            map[snake[i].X][snake[i].Y] = SNAKE;
        }
        snake[0] = next; //将下一个位置赋予给头部
        map[snake[0].X][snake[0].Y] = SNAKE; //设置头部
        break;
    case FOOD: //食物
        for (int i = SnakeSize; i > 0; i--) //蛇尾到蛇头整体移动一位
        {
            snake[i] = snake[i - 1];
        }
        snake[0] = next; //设置蛇头位置
        map[next.X][next.Y] = SNAKE; //设置头部
        SnakeSize++; //蛇尺度加一
        PlaySound(TEXT("D:\\22-音效\\1枪声.wav"), NULL, SND_FILENAME | SND_ASYNC); //播放音效//SND_ASYNC:播放音乐的同时结束程序;SND_SYNC:播放完音乐后才结束程序;
        score++; //分数加一
        //加快移动速度
        if (score == 3 * times)
        {
            time1 -= 10;
            times++;
            if (time1 == 0)
            {
                time1 = 10;
            }
        }
        yu//添加新的食物
        for (int order = 0; order < number; order++)
        {
            if ((snake[0].X == food[order].X) && (snake[0].Y == food[order].Y))
            {
                addfood(order);
            }
        }
        break;
    default:
        break;
    }
}
void addfood(int order)
{
    int row, col;
    int flag = 0;
    //判断是否有空位
    for (int i = 1; i <= ROW - 2; i++)
    {
        for (int j = 1; j <= COL - 2; j++)
        {
            if (map[i][j] == SPACE)
            {
                flag = 1;
                break;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
    //如果有空位,设置食物位置范围
    if (flag == 1)
    {
        do
        {
            row = rand() % (ROW - 2) + 1;
            col = rand() % (COL - 2) + 1;
        } while (map[row][col] != SPACE);
        map[row][col] = FOOD;
        food[order].X = row;
        food[order].Y = col;
    }
}

代码运行效果如下:
在这里插入图片描述

  • 122
    点赞
  • 408
    收藏
    觉得还不错? 一键收藏
  • 34
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值