面向對象方法編一個簡易的控制檯版貪喫蛇(一)

今天,我們開始用面向對象的方法編寫一個簡易的控制檯版的貪喫蛇。本人能力有限,若有紕漏還請及時指出,多多包涵。

在編寫程序之前,我們得有一個較爲清晰的思路,即:如何才能編出這個遊戲。我想,可以遵循以下幾條來明確以下思路。

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");
}

運行結果:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章