软件开发教程 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试
游戏开发 网络协议 系统运维 HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程
C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
 
   -> C++知识库 -> C++编写贪吃蛇(使用类与对象) -> 正文阅读

[C++知识库]C++编写贪吃蛇(使用类与对象)

C++编写贪吃蛇(使用类与对象)

完成了一个学期的C++学习后我决定用C++编写一个贪吃蛇。本以为是一个十分基础的问题,网上应该有很多代码教程,但找了好久都没有合适的。要么没有使用类和对象来编写,要么连编译都通不过。无奈只能从头开始自己摸索着写。本人水平有限,代码算法可能存在着一些问题,欢迎读到这篇文章有其他想法的人在评论区理性讨论,大家互相学习。

思路

贪吃蛇游戏主要有这样三个部分:地图食物。地图是蛇运动和食物生成的地方且在地图周围存在着墙作为边界。游戏运行的过程大致如下:

1.游戏开始,生成地图并生成墙;
2.生成蛇,包括初始化蛇位置、长度和初始运动方向;
3.生成食物;
4.蛇朝着当前方向移动,直到接收到玩家改变运动方向的指令或蛇撞到墙或自身使游戏结束;
5.当蛇头下一步就要吃到食物时,蛇的长度加一,同时生成新的食物;
6.当蛇的长度达到最大值或死亡时游戏结束。

代码实现

点类

在贪吃蛇中,不论是蛇本身还是食物都是由组成的,在代码编写过程中,需要进行大量的对的操作。
在编写游戏本体之前,我首先声明定义了点(Point)类,并编写了一些函数以便后面使用。

class Point
{
private:
	COORD pos;
public:
	Point () { pos.X = 0; pos.Y = 0; }

	Point (UINT x , UINT y)
	{
		pos.X = x;
		pos.Y = y;
	}

	Point (const Point &p)
		:pos (p.pos)
	{
	}

	void setPos (UINT x , UINT y)
	{
		pos.X = x;
		pos.Y = y;
	}

	void setPosPoint (const Point &p) { pos = p.pos; }

	void setPosRandom (int isHead = 0)
	//生成随机位置的点,主要用于蛇的初始化和食物的生成
	//和食物不同,蛇头具有特殊性,它不能过于靠近地图的边缘,所以这里设计了isHead参数
    //当isHead不填时默认为零,此时用于生成食物的位置
    //当isHead传入参数为1时生成蛇头位置
	{
		if (isHead == 1)
		{
			srand (time (NULL));
			pos.X = rand () % 18 + 1;
			pos.Y = rand () % 10 + 7;
		}
		else
		{
			srand (time (NULL));
			pos.X = rand () % 18 + 1;
			pos.Y = rand () % 18 + 1;
		}
	}

	void movePoint (int d)
	{
		if (d == 1)
		{
			pos.Y--;
		}
		else if (d == 2)
		{
			pos.Y++;
		}
		else if (d == 3)
		{
			pos.X--;
		}
		else if (d == 4)
		{
			pos.X++;
		}
	}

	COORD getPos (void)
	{
		return pos;
	}

	void printPoint (char c)
	{
		HANDLE hOutput = GetStdHandle (STD_OUTPUT_HANDLE);
		SetConsoleCursorPosition (hOutput , pos);
		cout << c;
	}
};

这个类实际上十分简单,它只包含自身位置这一个成员变量。成员函数的功能也大多为对点进行初始化或再赋值。这里面涉及到一些控制台的操作,不明白的可以去百度一下。这里只是简单涉及到,还是很好理解的。
有了这个工具后就可以开始游戏本体的编写了。

class Snake
{
private:
	Point p[400];
	int dirction;//蛇目前的运动方向 1上2下3左4右
	int lenth;//蛇的长度
	friend class Game;
public:
	Snake ()
	{
		lenth = 3;
		p[0].setPosRandom (1);
		p[1].setPos (p[0].getPos ().X , p[0].getPos ().Y + 1);
		p[2].setPos (p[1].getPos ().X , p[1].getPos ().Y + 1);
		dirction = 1;
	}
};

蛇这个类本身十分简单,成员函数只有一个无参构造函数。设计这个类的目的主要是储存与蛇本身有关的一些变量,并对蛇进行初始化。为了方便后面的操作,我将另一个Game类声明为Snake类的友元,这样Game中的函数也可以对Snake类中的变量直接进行操作了。

游戏功能

class Game
{
private:
	Snake s;
	int map[20][20] = {0};//0为空地 1为蛇身 2为食物 -1为墙
	Point food;
	int score;
public:
	Game ()
	{
		for (int i = 0; i < 20; i++)//这里初始化墙并在控制台打印出来
		{
			Point p1 (UINT (i) , UINT (0));
			map[i][0] = -1;
			p1.printPoint ('=');
			Point p2 (UINT (i) , UINT (19));
			map[i][19] = -1;
			p2.printPoint ('=');
			Point p3 (UINT (0) , UINT (i));
			map[0][i] = -1;
			p3.printPoint ('=');
			Point p4 (UINT (19) , UINT (i));
			map[19][i] = -1;
			p4.printPoint ('=');
			Sleep (50);//这里是用来做出一种动画的效果
		}
		for (int i = 0; i < s.lenth; i++)//这里在map中标记好蛇
		{
			map[s.p[i].getPos ().X][s.p[i].getPos ().Y] = 1;
		}
		for (int i = 0; i < 3; i++)//在控制台打印出蛇
		{
			s.p[i].printPoint ('O');
		}
		score = 0;
	}

	void creatFood (void)
	{
		Point food;
		while (map[food.getPos ().X][food.getPos ().Y] != 0)//只有当随机生成的位置在map中为0,也就是为空地时才进行下一步操作
		{
			food.setPosRandom ();
		}
		map[food.getPos ().X][food.getPos ().Y] = 2;
		food.printPoint ('$');
	}

	void eatFood (void)
	{
		score = score + 100;
		Point temph (s.p[0]);
		Point tempt (s.p[s.lenth - 1]);
		s.p[0].movePoint (s.dirction);
		map[s.p[0].getPos ().X][s.p[0].getPos ().Y] = 1;
		s.p[0].printPoint ('O');
		for (int i = s.lenth - 1; i > 1; i--)
		{
			s.p[i].setPosPoint (s.p[i - 1]);
		}
		s.p[1].setPosPoint (temph);
		s.lenth++;
		s.p[s.lenth - 1].setPosPoint (tempt);
		Sleep (500);
	}

	void snakeGo (void)
	{
		Point temp (s.p[0]);
		s.p[0].movePoint (s.dirction);
		map[s.p[0].getPos ().X][s.p[0].getPos ().Y] = 1;
		s.p[0].printPoint ('O');
		map[s.p[s.lenth - 1].getPos ().X][s.p[s.lenth - 1].getPos ().Y] = 0;
		s.p[s.lenth - 1].printPoint (' ');
		for (int i = s.lenth - 1; i > 1; i--)
		{
			s.p[i].setPosPoint (s.p[i - 1]);
		}
		s.p[1].setPosPoint (temp);
		Sleep (500);
	}

	int isEatorEnd (Point q , int dirc)
	//传入蛇头坐标和此时蛇行进的方向,然后判断蛇下一次运动后是否吃到食物或撞到墙壁或自身,这里利用map进行判断
	{
		q.movePoint (dirc);
		if (map[q.getPos ().X][q.getPos ().Y] == -1 || map[q.getPos ().X][q.getPos ().Y] == 1)
		{
			return -1;
		}
		else if (map[q.getPos ().X][q.getPos ().Y] == 2)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}

	void setDirection (char d)
	//注意到蛇在运动过程中不能直接向反方向移动
	//比如当蛇向上走时只能向左右转而不能直接向后转
	{
		if (d == 'w' && s.dirction != 2)
		{
			s.dirction = 1;
		}
		else if (d == 's' && s.dirction != 1)
		{
			s.dirction = 2;
		}
		else if (d == 'a' && s.dirction != 4)
		{
			s.dirction = 3;
		}
		else if (d == 'd' && s.dirction != 3)
		{
			s.dirction = 4;
		}
		else
		{
			return;
		}
	}

	Point &getHead (void) { return s.p[0]; }

	int getDirc (void) { return s.dirction; }

	int getPoint (void) { return score; }

	int getLenth (void) { return s.lenth; }
};

贪吃蛇游戏的主要功能都是在Game类中定义的。Game类中有一个int型的二维数组,里面储存着整个地图中各个位置目前的状态,方便后面条件判断使用。
一些简单的函数的说明都以注释的方式写到代码里了,这里主要说一下snakeGo和eatFood这两个函数。

snakeGo函数

snakeGo函数可以说是整个游戏的核心所在。这里简单示意一下蛇运动一次需要经历的步骤。
这里首先要说明一下,游戏中一共有三个地方储存蛇的位置:蛇类中Point类里储存的控制台坐标,变量map中相应位置储存的“1”和控制台窗口上实际打印出的蛇。蛇要完成一次移动,就要将这三个都进行改变。
第一步要先将表示蛇头点的控制台坐标暂时储存下来;第二步要将储存蛇头位置的三个地方进行相应的改动;第三步将蛇尾从map和控制台窗口中抹去;最后一步将中间蛇身的所有点的坐标变为其前一个点的坐标。
这里可以注意到在移动过程中控制台窗口只需关注蛇头和蛇尾的变化情况即可。
函数最后的Sleep函数实现了蛇移动后的短暂停顿。

eatFood函数

eatFood函数用来控制当蛇吃到食物时的运动状况。当判断到蛇在下一次运动时会吃到食物时(通过主函数调用isEatorEnd函数实现),主函数会调用eatFood函数进行蛇的运动而不是snakeGo并在执行完成后直接进入下一循环。
eatFood函数与snakeGo基本相同,不同点在于要在snake类中p数组中增加蛇尾位置的信息,lenth变量中增加蛇的长度。

主循环

接下来就是程序的执行部分,欢迎界面后便是主循环,游戏结束前的部分都将在这里完成。主循环内的二层循环以kbhit函数作为判断条件,函数的功能可以自行搜索,在用户改变蛇前进方向之前的部分都在这里进行。在每次循环进行时,会首先判断游戏是否结束或蛇是否将要吃到食物,然后再进行蛇的移动操作。

int main ()
{
	cout << "            您好,欢迎来到贪吃蛇           " << endl;
	cout << "您可以通过'w''a''s''d'来控制蛇的上下左右移动" << endl;
	system ("pause");
	system ("cls");
	Game g;
	g.creatFood ();
	while (1)
	{
		while (!_kbhit ())
		{
			int i = g.isEatorEnd (g.getHead () , g.getDirc ());
			if (i == 1)
			{
				g.eatFood ();
				if (g.getLenth () == 361)
				{
					goto L;
				}
				g.creatFood ();
				continue;
			}
			else if (i == -1)
			{
				goto L;
			}
			g.snakeGo ();
		}
		char d = _getch ();
		g.setDirection (d);
	}
L:system ("cls");
	cout << "游戏结束!您的得分为" << g.getPoint () << endl;
	return 0;
}
  C++知识库 最新文章
c++ 从键盘输入三个数比较大小
C++ 友元函数
剑指offer 栈与队列 C++
[PyQt5-Node-Editor][进阶篇]使用Pyqt5制作
QWQ氏计算器English(v1.1.1)c++
GCC详解-Binutils工具之c++filt
c++进阶练习题--基础篇(1)
C++ Builder xe 关于FolderDialog1的自定义
第五天C语言数组
C语言for循环
上一篇文章      下一篇文章      查看所有文章
加:2021-07-17 11:43:20  更:2021-07-17 11:45:28 
 
360图书馆 购物 三丰科技 阅读网 日历 万年历 2021年7日历 -2021/7/27 5:21:08-
图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件开发教程