利用C++写一个简单的贪吃蛇小游戏

别说谁变了你拦得住时间么 提交于 2020-02-28 05:22:54

首先,非常感谢白家名大佬参考提供的思路与方法,非常感谢!
 
 

首先写一个给光标定位的函数

void gotoxy(int x, int y)   //光标定位
{
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

这个函数需要用windows.h头文件,能把光标移动到指定的坐标位置,参考:光标定位
有了这个函数,就可以在指定位置生成蛇头,蛇身以及食物了。
 
 

贪吃蛇主要由蛇,食物与墙壁组成。

这里将控制台窗口作为墙壁,通过设置控制台窗口来形成贪吃蛇的墙。

通过系统命令可以:

  
system("mode con cols=a  lines=b ");   //设置控制台窗口大小
    system("title xxxxxx");            //设置控制台的标题

这里设置窗口大小为88*28,并将标题改为贪吃蛇。
 
 

然后设计一个食物的类

利用刚才定义好的gotoxy函数即可在指定位置生成食物

     
gotoxy(x, y);   //在确认好的位置输出食物
        cout << "★";

但是为了让食物能够随机生成,所以需要利用rand()函数函数介绍
因为我们设置的食物★在控制台中所占位置为2*2的,所以88*28的控制台食物的x最大为86,y最大为24.
所以食物类:

//食物类
class Food
{
private:
    int m_x;
    int m_y;
public:
    void randfood()         //随机产生一个食物
    {
        srand((int)time(NULL));//利用时间添加随机数种子,需要ctime头文件
    L1:
        m_x = rand() % (85) + 2;//2~86
        m_y = rand() % (25) + 2;//2~26

     if (m_x % 2)        //如果食物的x坐标不是偶数则重新确定食物的坐标
        goto L1;

    gotoxy(m_x, m_y);   //在确认好的位置输出食物
    cout << "★";
}
int getFoodm_x()        //返回食物的x坐标
{
    return m_x;
}
int getFoodm_y()        //返回食物的y坐标
{
    return m_y;
}


食物生成时还要注意,如果生成的食物与蛇身重合,那么要重新生成一次,这点在蛇类里解决。

为什么食物坐标需要偶数:
本来不明白为什么,后来通过询问原作者明白了原因
我们创建的蛇是这样的:在这里插入图片描述
在图片中我们可以看出,蛇各部位的长和宽是等长的,而实际上控制台中x和y是这样子的:
在这里插入图片描述这里可以看出,控制台两个x坐标才相当于一个y坐标
后面设计的蛇头初始位置x坐标为偶数,如果食物是奇数的,那么蛇头将碰不到食物
 
 

设计蛇类

在控制台中用■■■■■■●来表示蛇🐍
这里需要用到两个函数
conio.h 头文件里包含 _getch() 和 _kbhit()两个函数
_getch()函数作用:
getch()是编程中所用的函数,这个函数是一个不回显函数,当用户按下某个字符时,函数自动读取,无需按回车,有的C语言命令行程序会用到此函数做游戏,但是这个函数并非标准函数,要注意移植性!百度百科   这个函数用来读取从键盘输入的字符来进行操作
_kbhit()函数作用:
kbhit()是一个C和C++函数,用于非阻塞地响应键盘输入事件。其中文可译为“键盘敲击”(keyboard hit)。百度百科,它的的功能是: 检查当前是否有键盘输入,若有则返回一个非0值,否则返回0,因为它无论有没有都会返回,所以称为非阻塞函数,而getch() 在执行时,检测按下什么键,如果不按键就不返回所以称为阻塞函数  这里用kbhit()来检测键盘是否有输入
 
构建一个游戏结束时的界面:

    //游戏结束时设计一个界面输出“游戏结束”以及分数
    void finmatt(const int score)
    {
        system("cls");//清屏然后输出
        gotoxy(40, 14);
        cout << "游戏结束";
        gotoxy(40, 16);
        cout << "得分:" << score;
        gotoxy(0, 26);
        exit(0);//exit为C++的退出函数  exit(0)表示程序正常退出,非0表示非正常退出
    }

 

关于游戏结束如何判定

贪吃蛇结束有两种可能:1是蛇头触碰到墙,即边界,2是蛇头碰触到蛇身
撞墙:蛇头的x坐标或者y坐标超出边界的范围即是撞墙
撞到自己:遍历自身,如果蛇头的xy坐标都与蛇身相同则是撞到自身。

    //游戏结束
    void finishgame(const int score)
    {
        //撞墙
        if (snakecoor[0].x >= 88 || snakecoor[0].x < 0 || snakecoor[0].y >= 28 || snakecoor[0].y < 0)
            finmatt(score);

        //撞到自身
        for (int i = 1; i < snakecoor.size(); i++)
            if (snakecoor[0].x == snakecoor[i].x && snakecoor[0].y == snakecoor[i].y)
                finmatt(score);
    }

同时,还需要将蛇的位置初始化并且构造一个蛇运动的函数,将它们都封装进蛇类里。
蛇类:

//蛇类
class Snake
{
private:
    struct Snakecoor//定义一个蛇的坐标机构
    {
        int x;
        int y;

    };
    vector snakecoor;//将坐标存入vector容器中

    //判断并改变前进方向的函数
    void degdir(Snakecoor& nexthead)   //定义新的蛇头变量
    {
        static char key = 'd';  //静态变量防止改变移动方向后重新改回来

        if (_kbhit())
        {
            char temp = _getch();//定义一个临时变量储存键盘输入的值

            switch (temp)     //如果临时变量的值为wasd中的一个,则赋值给key
            {
            default:    //default是缺省情况,只有任何条件都不匹配的情况下才会执行 必须写在前面!不然蛇无法转向
                break;
            case'w':
            case'a':
            case's':
            case'd':

                //如果temp的方向和key的方向不相反则赋值  因为两次移动方向不能相反  将蛇设置为初始向右走
                if ((key == 'w' && temp != 's') || (key == 's' && temp != 'w') || \
                    (key == 'a' && temp != 'd') || (key == 'd' && temp != 'a'))
                    key = temp;
            }

        }

        switch (key)   //根据key的值来确定蛇的移动方向
        {
        case'd':
            nexthead.x = snakecoor.front().x + 2;  //新的蛇头的头部等于容器内第一个数据(旧蛇头)x坐标+2 因为蛇头占两个坐标,移动一次加2
            nexthead.y = snakecoor.front().y;
            break;
        case 'a':
            nexthead.x = snakecoor.front().x - 2;
            nexthead.y = snakecoor.front().y;
            break;
        case 'w':
            nexthead.x = snakecoor.front().x;
            nexthead.y = snakecoor.front().y - 1;  //因为控制台的x长度是y的一半,所以用两个x做蛇头,需要的坐标是二倍
            break;
        case 's':
            nexthead.x = snakecoor.front().x;
            nexthead.y = snakecoor.front().y + 1;
        }

        }
    //游戏结束时设计一个界面输出“游戏结束”以及分数
    void finmatt(const int score)
    {
        system("cls");//清屏然后输出
        gotoxy(40, 14);
        cout << "游戏结束";
        gotoxy(40, 16);
        cout << "得分:" << score;
        gotoxy(0, 26);
        exit(0);//exit为C++的退出函数  exit(0)表示程序正常退出,非0表示非正常退出
    }
    //游戏结束
    void finishgame(const int score)
    {
        //撞墙
        if (snakecoor[0].x >= 88 || snakecoor[0].x < 0 || snakecoor[0].y >= 28 || snakecoor[0].y < 0)
            finmatt(score);

        //撞到自身
        for (int i = 1; i < snakecoor.size(); i++)
            if (snakecoor[0].x == snakecoor[i].x && snakecoor[0].y == snakecoor[i].y)
                finmatt(score);
    }

public:
    //构造初始化蛇的位置
    Snake()
    {
        Snakecoor temp; //临时结构变量用于创建蛇

        for (int i = 5; i >= 0; i--)    //反向创建初始蛇身,初始蛇头朝右
        {
            temp.x = 16 + (i << 1);     //偶数 在蛇头左移生成蛇身
            temp.y = 8;
            snakecoor.push_back(temp);//在蛇尾尾插入临时变量
        }

    }
    //蛇运动的函数
    void move(Food&food, int& score)
    {
        Snakecoor nexthead;         //新蛇头变量

        degdir(nexthead);               //判断和改变蛇前进的方向

        snakecoor.insert(snakecoor.begin(), nexthead);  //将蛇头插入容器的头部

        gotoxy(0, 0);
        cout << "得分:" << score; //每次移动都在左上角刷新得分
       // gotoxy(0, 2);
       // cout << "蛇的长度为:" << snakecoor.size();//长度用来测试

        finishgame(score);              //判断游戏结束,输出分数

        //吃到食物蛇的变化
        if (snakecoor[0].x == food.getFoodm_x() && snakecoor[0].y == food.getFoodm_y()) //蛇头与食物重合
        {
            gotoxy(snakecoor[0].x, snakecoor[0].y);     //吃到食物时这次蛇没有移动,所以蛇会卡顿一下
            cout << "●";                                //重新输出一下蛇头和第一节蛇身让蛇不卡顿
            gotoxy(snakecoor[1].x, snakecoor[1].y);
            cout << "■";


            score++;             //吃到食物得分+1

            food.randfood();     //如果蛇头坐标和食物坐标重合则重新产生一个食物
            return;              //直接结束本次移动
        }
        for (int i = 0; i < snakecoor.size(); i++)  //遍历容器,判断食物与蛇身是否重合并输出整条蛇
        {
            gotoxy(snakecoor[i].x, snakecoor[i].y);

            if (!i)                             //头部输出圆形否则输出方块
                cout << "●";
            else
                cout << "■";

            //如果食物刷新到了蛇身上,则重新产生一个食物
            if (snakecoor[i].x == food.getFoodm_x() && snakecoor[i].y == food.getFoodm_y())
                food.randfood();
        }


        //清除蛇尾  数据与画面是分开的
        gotoxy(snakecoor.back().x, snakecoor.back().y); //在容器尾部的地方输出空格 清除画面上的蛇尾
        cout << "  ";
        snakecoor.pop_back();                       //删除容器中最后一个数据  清除数据上的蛇尾
    }

};

关于蛇吃到食物蛇身变长如何实现的:蛇每次移动添加一个蛇头,删除一个蛇尾来实现移动,当蛇吃到食物时,直接return回到循环没有删除蛇尾,以此达到蛇身变长的目的。
 
通过sleep函数控制蛇移动的速度。sleep函数是在<windows.h>头文件下的函数,作用是告诉系统,延迟多少毫秒来执行后面的代码。
 
此时运行函数发现有光标
在这里插入图片描述
因此添加一个去除光标的函数

void HideCursor()           //隐藏光标
{
    CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

 
最后完成:

#include <iostream>
#include<ctime>
#include<windows.h>
#include<vector>
#include <conio.h>
using namespace std;


void gotoxy(int x, int y)   //光标定位
{
    COORD pos = { x,y };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}


//食物类
class Food
{
private:
    int m_x;
    int m_y;
public:
    void randfood()         //随机产生一个食物
    {
        srand((int)time(NULL));//利用时间添加随机数种子,需要ctime头文件
    L1:
        m_x = rand() % (85) + 2;//2~86
        m_y = rand() % (25) + 2;//2~26

        if (m_x % 2)        //如果食物的x坐标不是偶数则重新确定食物的坐标
            goto L1;

        gotoxy(m_x, m_y);   //在确认好的位置输出食物
        cout << "★";
    }
    int getFoodm_x()        //返回食物的x坐标
    {
        return m_x;
    }
    int getFoodm_y()        //返回食物的y坐标
    {
        return m_y;
    }
};

class Snake
{
private:
    struct Snakecoor//定义一个蛇的坐标机构
    {
        int x;
        int y;

    };
    vector<Snakecoor> snakecoor;//将坐标存入vector容器中

    //判断并改变前进方向的函数
    void degdir(Snakecoor& nexthead)   //定义新的蛇头变量
    {
        static char key = 'd';  //静态变量防止改变移动方向后重新改回来

        if (_kbhit())
        {
            char temp = _getch();//定义一个临时变量储存键盘输入的值

            switch (temp)     //如果临时变量的值为wasd中的一个,则赋值给key
            {
            default:    //default是缺省情况,只有任何条件都不匹配的情况下才会执行 必须写在前面!不然蛇无法转向
                break;
            case'w':
            case'a':
            case's':
            case'd':

                //如果temp的方向和key的方向不相反则赋值  因为两次移动方向不能相反  将蛇设置为初始向右走
                if ((key == 'w' && temp != 's') || (key == 's' && temp != 'w') || \
                    (key == 'a' && temp != 'd') || (key == 'd' && temp != 'a'))
                    key = temp;
            }

        }

        switch (key)   //根据key的值来确定蛇的移动方向
        {
        case'd':
            nexthead.x = snakecoor.front().x + 2;  //新的蛇头的头部等于容器内第一个数据(旧蛇头)x坐标+2 因为蛇头占两个坐标,移动一次加2
            nexthead.y = snakecoor.front().y;
            break;
        case 'a':
            nexthead.x = snakecoor.front().x - 2;
            nexthead.y = snakecoor.front().y;
            break;
        case 'w':
            nexthead.x = snakecoor.front().x;
            nexthead.y = snakecoor.front().y - 1;  //因为控制台的x长度是y的一半,所以用两个x做蛇头,需要的坐标是二倍
            break;
        case 's':
            nexthead.x = snakecoor.front().x;
            nexthead.y = snakecoor.front().y + 1;
        }

        }
    //游戏结束时设计一个界面输出“游戏结束”以及分数
    void finmatt(const int score)
    {
        system("cls");//清屏然后输出
        gotoxy(40, 14);
        cout << "游戏结束";
        gotoxy(40, 16);
        cout << "得分:" << score;
        gotoxy(0, 26);
        exit(0);//exit为C++的退出函数  exit(0)表示程序正常退出,非0表示非正常退出
    }
    //游戏结束
    void finishgame(const int score)
    {
        //撞墙
        if (snakecoor[0].x >= 88 || snakecoor[0].x < 0 || snakecoor[0].y >= 28 || snakecoor[0].y < 0)
            finmatt(score);

        //撞到自身
        for (int i = 1; i < snakecoor.size(); i++)
            if (snakecoor[0].x == snakecoor[i].x && snakecoor[0].y == snakecoor[i].y)
                finmatt(score);
    }

public:
    //构造初始化蛇的位置
    Snake()
    {
        Snakecoor temp; //临时结构变量用于创建蛇

        for (int i = 5; i >= 0; i--)    //反向创建初始蛇身,初始蛇头朝右
        {
            temp.x = 16 + (i << 1);     //偶数 在蛇头左移生成蛇身
            temp.y = 8;
            snakecoor.push_back(temp);//在蛇尾尾插入临时变量
        }

    }
    //蛇运动的函数
    void move(Food&food, int& score)
    {
        Snakecoor nexthead;         //新蛇头变量

        degdir(nexthead);               //判断和改变蛇前进的方向

        snakecoor.insert(snakecoor.begin(), nexthead);  //将蛇头插入容器的头部

        gotoxy(0, 0);
        cout << "得分:" << score; //每次移动都在左上角刷新得分
        gotoxy(0, 2);
        cout << "蛇的长度为:" << snakecoor.size();//长度用来测试

        finishgame(score);              //判断游戏结束,输出分数

        //吃到食物蛇的变化
        if (snakecoor[0].x == food.getFoodm_x() && snakecoor[0].y == food.getFoodm_y()) //蛇头与食物重合
        {
            gotoxy(snakecoor[0].x, snakecoor[0].y);     //吃到食物时这次蛇没有移动,所以蛇会卡顿一下
            cout << "●";                                //重新输出一下蛇头和第一节蛇身让蛇不卡顿
            gotoxy(snakecoor[1].x, snakecoor[1].y);
            cout << "■";


            score++;             //吃到食物得分+1

            food.randfood();     //如果蛇头坐标和食物坐标重合则重新产生一个食物
            return;              //直接结束本次移动
        }
        for (int i = 0; i < snakecoor.size(); i++)  //遍历容器,判断食物与蛇身是否重合并输出整条蛇
        {
            gotoxy(snakecoor[i].x, snakecoor[i].y);

            if (!i)                             //头部输出圆形否则输出方块
                cout << "●";
            else
                cout << "■";

            //如果食物刷新到了蛇身上,则重新产生一个食物
            if (snakecoor[i].x == food.getFoodm_x() && snakecoor[i].y == food.getFoodm_y())
                food.randfood();
        }


        //清除蛇尾  数据与画面是分开的
        gotoxy(snakecoor.back().x, snakecoor.back().y); //在容器尾部的地方输出空格 清除画面上的蛇尾
        cout << "  ";
        snakecoor.pop_back();                       //删除容器中最后一个数据  清除数据上的蛇尾
    }

};


void HideCursor()           //隐藏光标
{
    CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}


int main()
{
    system("mode con cols=88 lines=28");   //设置控制台窗口大小
    system("title 贪吃蛇");            //设置标题
    HideCursor();                          //光标隐藏

    int score = 0;              //得分变量

    Food food;                  //食物对象
    food.randfood();            //开局随机产生一个食物

    Snake snake;                //蛇对象


    while (true)
    {
        snake.move(food, score);//蛇移动

        Sleep(150);     //游戏速度
    }

    return 0;
}


在这里插入图片描述   最后,再次感谢白家名大佬对我的帮助,谢谢!

 

欢迎大家访问我的个人博客青蛙听禅的博客

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!