面向对象方法编一个简易的控制台版贪吃蛇(一)

本文介绍了一种使用C++面向对象的方式实现控制台版贪吃蛇游戏的方法。通过定义结点类、控制类以及蛇、地图和食物类,实现了游戏的基本框架。文章详细讲解了每个类的设计和实现细节。

今天,我们开始用面向对象的方法编写一个简易的控制台版的贪吃蛇。本人能力有限,若有纰漏还请及时指出,多多包涵。

在编写程序之前,我们得有一个较为清晰的思路,即:如何才能编出这个游戏。我想,可以遵循以下几条来明确以下思路。

1.想思路:首先你得知道贪吃蛇是怎么个玩法。毕竟,若游戏规则都不知道的话,想编出来也不切合实际。

2.找出技术点

(1)通过什么技术能将地图、蛇什么的显示出来

(2)蛇需要人来控制,那么控制蛇需要什么方式来实现?其实就是不断地绘制和检测状态(捕获按键)的过程。

3.构建关系(功能):蛇吃食物会变长,碰到墙壁会死去。

1)由此可以想到应该构建这么几个类(class)——食物类、地图类、蛇类。同时,你需要一个loop循环,如果不符合条件了(比如说:蛇死了),就跳出循环。

2)具体用什么方式来存储食物、地图还有蛇呢?你可以用vector去存储每一块。把食物,地图还有蛇统统压入vector容器当中,同时可以想办法将这三种不同的东西打上标记(TYPE_)区分开来。然后遍历整个容器,通过不同的标记绘制不同的图案,用这种方法即可实现在东西再控制台上的显示。

3Loop循环:每一次循环,你都需要遍历容器,检测食物、蛇还有地图的坐标。为什么这样做?因为你需要用这种方式检测蛇头和另外两个东西是否重合,坐标相同了,就重合了。若蛇头的坐标和墙的坐标相同了,说明撞墙了,此时Game Over;若蛇头的坐标和食物的坐标重合了,说明吃到食物了,此时再额外生成食物,同时蛇身增加。

4)如何控制蛇的移动?你可以把这个类写进蛇类里面,但是这样工作量更大,你可以单独写一个控制类,专门控制蛇的移动操作。

4.逐个实现。

5.关联调试。


我们已经有了以上的这些思路,那么我们就开始一一实现吧。但是一一实现的过程也需要一些具体的逻辑,这些逻辑主要是体现在:先做什么,后做什么,该怎么解决这些问题!

一、首先得把框架搭建出来,最起码能够在控制台上显示出来蛇、地图和食物。

根据以上的思路,我们需要蛇类(Snake)、地图类(Map),食物类(Food)。我们采取这样的一种思路:用C++vector容器来存储各个“结点”,如果这个结点存储的是地图类的点,我们就用“*”表示;如果是蛇类的点,我们就用“o”表示;如果是食物类,我们就用“$”表示。

不过,这个“结点”并不是一维的,毕竟控制台上显示的东西都是二维的,有x值和y值。所以,我们还得额外定义一个结点类(baseNode,专门用来定义“结点”。

为了方便显示以及其它的一些操作,我们专门定义一个控制类(Controller),这个类所起的作用是对整个游戏控制的一个“调配”。

 

所以,具体实现代码的时候,你最先应该实现的是哪个类呢?显然是结点类,因为如果没有具体的结点的话,不管地图、蛇还是食物的话你都无从定义(毕竟蛇、地图和食物是在二维空间里)。其次是控制类,没有控制类的话,你就无法实现对蛇、地图等进行显示等操作。这两个类写好之后,蛇类、地图类和食物类将会比较容易实现。

最开始实现的是结点类:

/*
	baseNode.h
*/

#pragma once

enum nodeType  //定义了结点的类型
{
	TYPE_SNAKE,   //蛇类
	TYPE_MAP,	  //地图类
	TYPE_FOOD     //食物类
};

class BaseNode
{
public:
	BaseNode();
	BaseNode(int x, int y, nodeType type);
	~BaseNode();


	int x;
	int y;
	nodeType type;
};
相应的,baseNode.h里具体函数的实现过程如下:

//baseNode.cpp
#include "baseNode.h"

BaseNode::BaseNode()
{
}

BaseNode::BaseNode(int x, int y, nodeType type)
{
	this->x = x;
	this->y = y;
	this->type = type;
}

BaseNode::~BaseNode()
{
}

baseNode类实现之后,开始写控制类,目前的情况下,我们只需只需实现两个功能:能够将结点显示在控制台上,以及捕获控制台上的坐标。

/*
	控制类:从某些方面讲,这个类是对整个游戏的“调配”
*/

#pragma once
#include<vector>
#include"baseNode.h"
using namespace std;
class Controller
{
public:
	static void moveXY(int x, int y);  //移动光标的位置
	static void showBaseNode(vector<BaseNode*> v); //把地图,蛇,食物什么的在控制台上显示出来
};
说明:在Controller.cpp文件中我定义的是:如果是map类型的结点(TYPE_MAP),就画“*”;如果是蛇类结点(TYPE_SNAKE),就画“o”;如果是食物类结点(TYPE_FOOD),就画“$”。
//Controller.cpp

#include "Controller.h"
#include"baseNode.h"
#include<iostream>
#include<Windows.h>
#include<conio.h>
using namespace std;

/*
	moveXY这个函数通过SetConsoleCursorPosition这个函数可以定位到(x,y)在控制台上的位置
	这样,我在showBaseNode函数里面调用这个函数,就可以在控制台上相应的位置上输出我想要的样式。
*/

void Controller::moveXY(int x, int y)
{
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void Controller::showBaseNode(vector<BaseNode*> v)
{
	for (int i = 0; i < v.size(); i++)
	{
		BaseNode* node = v.at(i);
		Controller::moveXY(node->x, node->y);  //moveXY方法的作用在这里体现了出来,若去掉这个语句的话
		                                       //在控制台上显示的图像是不管是啥都是按控制上的行依次输出的。
		if (node->type == TYPE_SNAKE)
		{
			cout << "o";
		}
		else if (node->type == TYPE_MAP)
		{
			cout << "*";
		}
		else if (node->type == TYPE_FOOD)
		{
			cout << "#";
		}
	}
}
以上这两个类实现完毕后,我们依次实现蛇类,地图类和食物类。比较容易了。

蛇类:

//Snake.h

#pragma once
#include<vector>
#include"Controller.h"
#include"baseNode.h"
using namespace std;

class Snake
{
public:
	Snake(int x, int y);  //构造函数是用来初始化蛇的位置的
	~Snake();

	void showSnake();

private:
	vector<BaseNode*> m_snake;
};
//Snake.cpp
#include "Snake.h"
#include"Controller.h"

Snake::Snake(int x, int y)
{
	m_snake.push_back(new BaseNode(x, y, TYPE_SNAKE));//创建一个蛇,就是将相应的结点压入vector,地图和食物同理。
}

Snake::~Snake()
{
	while (m_snake.size())
	{
		delete m_snake.back();
		m_snake.pop_back();
	}
}

void Snake::showSnake()
{
	Controller::showBaseNode(m_snake);
}



食物类,实现道理和蛇类基本一样:

//Food.h
#pragma once
#include"Controller.h"
#include"baseNode.h"
class Food
{
public:
	Food(int x,int y);
	~Food();

	void showFood();
private:
	vector<BaseNode*> m_food;
};

//Food.cpp
#include "Food.h"

Food::Food(int x,int y)
{
	m_food.push_back(new BaseNode(x, y, TYPE_FOOD));
}

Food::~Food()
{
	while (m_food.size())
	{
		delete m_food.back();
		m_food.pop_back();
	}
}

void Food::showFood()
{
	Controller::showBaseNode(m_food);
}

最后是地图类:

//Map.h
#pragma once
#include"baseNode.h"
#include"Controller.h"
using namespace std;
class Map
{
public:
	Map();
	~Map();

	void showMap();

private: 
	vector<BaseNode*> m_map;
};

//Map.cpp
#include "Map.h"

Map::Map()  //最早初始化地图的时候,显然是周围的四面墙
{
	for (int i = 0; i < 60; i++)
	{
		m_map.push_back(new BaseNode(i, 0, TYPE_MAP));
		m_map.push_back(new BaseNode(i, 19, TYPE_MAP));
	}

	for (int i = 0; i < 20; i++)
	{
		m_map.push_back(new BaseNode(0, i, TYPE_MAP));
		m_map.push_back(new BaseNode(59, i, TYPE_MAP));
	}
}

Map::~Map()
{
	while (m_map.size())
	{
		delete m_map.back();
		m_map.pop_back();
	}
}

void Map::showMap()
{
	Controller::showBaseNode(m_map);
}

最后我们写一个主函数检验一下,在现在这个阶段,我们暂时不管随机生成食物等功能。所以我们先把坐标写成定值。只是看看能否实现第一阶段的目标——在控制台中能够显示出地图、蛇和食物。

Main.cpp

#include<iostream>
#include"Controller.h"
#include"baseNode.h"
#include"Map.h";
#include"Snake.h"
#include"Food.h"

void main()
{
	{
		Map* map = new Map();
		Snake* snake = new Snake(5, 6);
		Food* food = new Food(30, 15);

		snake->showSnake();
		food->showFood();
		map->showMap();
	}
	system("pause");
}

运行结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值