C++实现带简易菜单的贪吃蛇

C++ 贪吃蛇 小游戏

文章目录

  • C++ 贪吃蛇 小游戏
    • 思路
    • 具体实现
      • 一、工具函数
      • 二、地图
      • 三、食物
      • 四、蛇
      • 五、菜单
      • 六、组装
      • 七、效果图
    • 参考文章

思路

无论什么样的贪吃蛇总会有三个必不可缺的元素

  1. 地图
  2. 食物

再加上一个简易的的菜单,共四个部分。前三个可以各写一个类,菜单里面我们就直接写函数。

具体实现

尝试由简入繁,先完成简单的部分,再完成难的,再组装起来。
每一个类写一个头文件一个源文件,头文件只给定义,源文件里再具体实现。

一、工具函数

先给出两个常用工具函数,一个用于定位光标方便在我们想要的位置输出,另一个用于隐藏光标,
调用两者需要<windows.h>这个头文件。

//控制输出光标
void Pos(int x, int y)   
{
	COORD pos;    //结构体
	pos.X = x; 	  //控制列
	pos.Y = y;    //控制行
	//读取标准输出句柄来控制光标为pos
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
//隐藏光标
void HideCursor()           
{
	CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

二、地图

地图应该有大小,即横纵坐标范围,应该能画出来
Map.h

#pragma once
#include 
#include 
#include 
#pragma comment(lib,"winmm.lib")
using namespace std;
class Map
{
private:
	int ROW_Map = 32;			//地图的行大小;
	int COL_Map = 82;			//地图的列大小;
public:
	void DrawMap();				//画地图;
};

Map.cpp

#include"Map.h"
void Pos(int x, int y);
void HideCursor();

void Map::DrawMap()
{
	system("color 3F");                      //设置颜色
	int i = 0;
	for (i = 20; i < COL_Map + 20; i += 2)   //打印上下边框(每个■占用两列)
	{
		Pos(i, 5);							 //如果不加20就是打印在控制台边上,下面的5也不用加
		printf("■");
		Pos(i, 5 + ROW_Map - 1);
		printf("■");
	}
	for (i = 5; i < ROW_Map + 5; i++)		 //打印左右边框
	{
		Pos(20, i);
		printf("■");
		Pos(20 + COL_Map - 2, i);
		printf("■");
	}
}

写一个测试函数,记得在main函数外加上那两个工具函数,这里为了节省篇幅就不贴出来了

#include"Map.h"
int main()
{
	Map A;
	A.DrawMap();
	system("pause");
	return 0;
}

颜色可以自己改,system("color ")里改后面数字就可,具体啥颜色CSDN里可以搜到很多介绍

三、食物

食物应该是随机产生的,但是应该在地图范围里,而且不能与蛇重合,所以要知道具体的坐标。
随机函数用 rand() ;根据时间给它加一个随机数种子,需要 <ctime> 头文件
Food.h

#pragma once
#include 
#include "Map.h"
class Food
{
private:
    int pos_x;
    int pos_y;
public:
    void randfood();          //随机产生一个食物
    int getFood_x();          //返回食物的x坐标
    int getFood_y();          //返回食物的y坐标
};

Food.cpp

#include"Food.h"

void Pos(int x, int y);
void HideCursor();

void Food::randfood()
{
    srand((int)time(NULL));//利用时间添加随机数种子,需要ctime头文件
Loop:
    pos_x = rand() % (76) + 24;
    pos_y = rand() % (29) + 7;
    if (pos_x % 2)         //如果食物的x坐标不是偶数则重新确定食物的坐标
        goto Loop;
    Pos(pos_x, pos_y);     //在确认好的位置输出食物
    cout << "★";
}
int Food::getFood_x()
{
    return pos_x;
}
int Food::getFood_y()
{
    return pos_y;
}

四、蛇

用向量容器来描述蛇,直接上代码比较明了
Snake.h

#pragma once
#include
#include 
#include"Food.h"
#include 
#pragma comment(lib, "Psapi.lib")

void Pos(int x, int y);
void HideCursor();

class Snake
{
private:
    struct Snakepoint
    {
        int x;  //第几列
        int y;  //第几列
    };
    vector<Snakepoint> snakepoint;

    char Direction;                                         //蛇行走的方向
    void Show_information(int& score, int& sleeptime);      //打印信息
    void overout(const int score);                          //游戏结束显示
    void Gameover(int& score,int &x);                       //游戏结束
    void Control(Snakepoint& nexthead);                     //控制方向

public:
    Snake();                                                //构造函数
    void move(Food& food, int& score, int& sleeptime,int& x);//蛇移动并打印出来
    void sleep(int& score, int& sleeptime);                 //根据分数调整游戏速度
};

Snake.cpp

#pragma once
#include"Snake.h"

void Snake::Show_information(int& score,int& sleeptime)
{
    Pos(44, 4);
    cout << "得分: " << score<<"                      	//空格调整相对位置
    cout << "速度: ";
    printf("%d", sleeptime);
}

void Snake::overout(const int score)                    //游戏结束显示
{
    Pos(44, 14);
    cout << "游戏结束";
    Pos(44, 18);
    cout << "得分:" << score;
}

void Snake::Gameover(int& score,int &x)                 //游戏结束原因及得分
{
    //撞墙
    if (snakepoint[0].x >= 100 || snakepoint[0].x < 22 || snakepoint[0].y >= 36 || snakepoint[0].y < 6)
    {
        system("cls");      //清屏
        Pos(44, 10);
        cout << "撞墙啦,头太铁";
        overout(score);
        score = 0;
        Sleep(3000);
        exit(0);            //退出
    }
    //撞到自身
    for (int i = 1; i < snakepoint.size(); i++)
        if (snakepoint[0].x == snakepoint[i].x && snakepoint[0].y == snakepoint[i].y)
        {
            system("cls");  //清屏
            Pos(44, 10);
            cout << "撞到自身啦,肯定很痛";
            overout(score);
            score = 0;
            Sleep(3000);
            exit(0);       //退出
        }
}

void Snake::Control(Snakepoint& nexthead)
{
    static char Direction = 'R';                //静态变量防止改变移动方向后重新改回来,,将蛇设置为初始向右走

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

        if ((temp == 'd') && Direction != 'L')
        {
            Direction = 'R';
        }
        else if ((temp == 'a') && Direction != 'R')
        {
            Direction = 'L';
        }
        else if ((temp == 'w') && Direction != 'D')
        {
            Direction = 'U';
        }
        else if ((temp == 's') && Direction != 'U')
        {
            Direction = 'D';
        }
    }

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

void Snake::sleep(int& score, int& sleeptime)
{
    if (score < 10)
    {
        sleeptime = 120;
    }
    else if (score <20)
    {
        sleeptime = 80 ;
    }
    else if (score <30)
    {
        sleeptime = 50 ;
    }
    else
    {
        sleeptime = 30 ;
    }
    Sleep(sleeptime);                                   //速度随分数加快
}

void Snake::move(Food& food, int& score,int& sleeptime,int& x)
{
    Snakepoint nexthead;
    Control(nexthead);
    snakepoint.insert(snakepoint.begin(), nexthead);    //将蛇头插入容器的头部

    Show_information(score ,sleeptime);
    Gameover(score,x);

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

        score++;                                        //吃到食物得分+1
        food.randfood();                                //重新产生一个食物
        return ;                                        //直接结束本次移动
    }

    //蛇每移动一次,容器向前动一次,打印出来的部分除蛇尾意外其余都被覆盖,所以最后清除蛇尾
    for (int i = 0; i < snakepoint.size(); i++)         //遍历容器,判断食物与蛇身是否重合并输出整条蛇
    {                                            
        Pos(snakepoint[i].x, snakepoint[i].y);
        if (!i)                                         //蛇头
            cout << "¤";
        else
            cout << "●";

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

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

Snake::Snake()
{
    Direction = 'R';
    Snakepoint temp;                                    //临时结构变量用于创建蛇
    for (int i = 5; i >= 0; i--)                        //反向创建初始蛇身,初始蛇头朝右
    {
        temp.x = 22 + (i << 1);                         //偶数 在蛇头左移生成蛇身
        temp.y = 7;
        snakepoint.push_back(temp);                     //在蛇尾尾插入临时变量
    }
}

五、菜单

菜单有一个表示选项的光标,用于提示选择
Menu.h

#pragma once
#include 
#include 
#include 
#include
#include 
#pragma comment(lib,"winmm.lib")
using namespace std;

void Pos(int x, int y);
void HideCursor();

int Menu_Examine = 0;			//用来控制打印菜单的时候只打印一次,不然会在循环的时候一直打印
void DrawMenu()					//打印菜单
{
	void HideCursor();
	if(Menu_Examine==0)
	{ 
	    Menu_Examine = 1;
		for (int i = 5; i > 0; i--)
		{
			printf("\n");
		}
		printf("\t\t\t\t☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |    贪吃蛇之无敌香蕉蛇皮怪之盘古开天地之最原始版    |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                      开始游戏                      |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                      退出游戏                      |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t |                                                    |\n");
		printf("\t\t\t\t☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆-☆\n");
		printf("\t\t\t\t                                    wasd控制,space选择\n");
		printf("\t\t\t\t                                         制作人:琛小獭\n");
		}
}

void Chioce(int &x)
{
	void HideCursor();
	static char n = '1';
	if (_kbhit())
	{
		char temp = _getch();	//临时变量储存键盘输入的值
		if ((n == '1') && (temp == 'w'))
		{
			n = '1';			//选择‘1’的时候不能在往上翻
		}	
		else if ((n == '1') && (temp == 's'))
		{
			n = '2';
		}
		else if ((n == '2') && (temp == 'w'))
		{
			n = '1';
		}
		else if ((n == '2') && (temp == 's'))
		{
			n = '2';			//选择‘2’的时候不能在往下翻
		}
		else if ((temp == ' ') && (n == '1'))
		{
			x = 2;				//进入游戏
			return;
		}
		else if ((temp == ' ') && (n == '2'))
		{
			exit(0);			//退出
		}
	}
	switch (n)
	{
	case '1':					//选择标识在选择‘1’
		Pos(52, 10);
		cout << "●";
		Pos(52, 13);
		cout << "  ";
		break;
	case '2':					//选择标识在选择‘2’
		Pos(52, 10);
		cout << "  ";
		Pos(52, 13);
		cout << "●";
		break;
	}
	Sleep(10);				//设置间隔以免看出闪烁
}

由于编辑器问题复制过来可能没对齐大家自行调整

六、组装

BeginGame.h

#pragma once
#include"Map.h"
#include"Food.h"
#include"Snake.h"
Food food;              //食物对象
Map map;				//地图对象
Snake snake;            //蛇对象
int score = 0;          //得分变量
int sleeptime = 120;	//游戏速度
int Init_Examine;

void BeginGame(int &x)
{
	if (Init_Examine == 0)
	{
		Init_Examine = 1;
		system("cls");							//清除之前打印的菜单
		HideCursor();
		map.DrawMap();							//画地图
		food.randfood();						//随机产生一个食物
		snake.move(food, score, sleeptime, x);	//蛇移动
		snake.sleep(score, sleeptime);
	}
	else if (Init_Examine == 1)
	{
		snake.move(food, score, sleeptime, x);	//蛇移动
		snake.sleep(score, sleeptime);			//游戏速度
	}
}

Main.cpp

#include"Menu.h"
#include"BeginGame.h"

//两个全局函数
//控制输出光标
void Pos(int x, int y)   
{
	COORD pos;  //pos为结构体
	pos.X = x;  //控制列
	pos.Y = y;  //控制行
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);//读取输出句柄来控制光标
}
//隐藏光标
void HideCursor()           
{
	CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

int main()
{
	system("title 贪吃蛇");	//设置窗口名字
	system("color 3F");		//设置颜色
	static int x = 1;		//初始打印菜单
	void HideCursor();		//隐藏光标
	mciSendString("play bgm.mp3 repeat", NULL, 0, NULL);//BGM大家可以自己设置
	while (true)										//把一个mp3格式文件拷到工程目录下改名为bgm.mp3即可
	{
		switch (x)
		{
		case 1:
			DrawMenu();
			Chioce(x);
			Init_Examine = 0;
			break;
		case 2:
			BeginGame(x);
			break;
		}
	}
	system("pause");
	return 0;
}

七、效果图


参考文章

大部分地方我没有写提示,代码段里面有注释,各位大佬看明白肯定不在话下
过程中主要参考了下面这两篇文章,感谢这两位大佬,大家有兴趣也看下有助于理解
参考文章1
参考文章2

你可能感兴趣的:(C++小游戏)