每天一個(gè)編程小項(xiàng)目,提升你的編程能力!
游戲介紹
下圍棋的程序,實(shí)現(xiàn)了界面切換,選擇路數(shù),和圍棋規(guī)則,也實(shí)現(xiàn)了點(diǎn)目功能,不過(guò)只有當(dāng)所有死子都被提走才能點(diǎn)目,不然不準(zhǔn)確
操作方法
鼠標(biāo)操作
游戲截圖
編譯環(huán)境
VisualStudio2019,EasyX_20211109
文件描述
用廣度尋路尋找周?chē)邢嗤遄樱钡剿奶幣霰诹耍玫桨鼑∽约旱乃悬c(diǎn),看看這些點(diǎn)是空地的數(shù)量,空地的數(shù)量就是氣的數(shù)量,氣為 0這些子全部提掉,設(shè)為空地。每下一步棋記錄下這步棋的位置,悔棋時(shí)把這些點(diǎn)提掉。打劫時(shí)在存悔棋的點(diǎn)的地方找到劫爭(zhēng)的地方,只吃了一顆子并且落在劫爭(zhēng)的地方就不能下。
點(diǎn)目就是找到一塊空地,看看圍住它的是不是都是同一個(gè)顏色的子,是的話這塊空地有多大這個(gè)顏色的子的目數(shù)就加多大。
如果圍住空地的是不同顏色的子,那么這塊空地多大,這兩個(gè)子的氣就加這塊空地的目數(shù)的一半。
其他功能就是設(shè)計(jì)模式的問(wèn)題了,我借鑒了 cocos2d 的設(shè)計(jì)模式,在 director 里運(yùn)行整個(gè)程序,在 scene 里寫(xiě)這個(gè)層運(yùn)行的功能,director 的runwithscene 寫(xiě)切換場(chǎng)景。
詳解:
1、公共工具
MyTool
這個(gè)文件里包含了廣度尋路和圍棋地圖的類(lèi),其中圍棋地圖通過(guò)廣度尋路實(shí)現(xiàn)了吃子,提子,點(diǎn)目的功能。還有一些之前做七巧板的項(xiàng)目時(shí)保存下來(lái)的類(lèi),暫且用不到,但是也許能方便未來(lái)開(kāi)發(fā),所以放到一起。
DealArray處理數(shù)組的頭文件,目前有三個(gè)函數(shù),作用分別是:將 vector 首尾顛倒、判斷一個(gè)元素是否在 vector 里面,判斷兩個(gè) vector 是否相等(每個(gè)元素都相等就是兩個(gè) vector 相等),函數(shù)實(shí)現(xiàn)為
// 這個(gè)實(shí)現(xiàn) vector 首尾顛倒 template// 這個(gè)是函數(shù)模板 void Reserve_Vector(vector & arr) { for (int i = 0;i < (arr.size() >> 1);i++) { Vector_Reverse temp = arr[i]; arr[i] = arr[arr.size() - i-1]; arr[arr.size() - i - 1] = temp; } } // 這個(gè)實(shí)現(xiàn)判斷一個(gè)元素是否在 vector 里面 template bool ifNotInVector(vector arr, VectorInclude num) { for (VectorInclude i : arr) { if (i == num)return false; } return true; } // 這個(gè)實(shí)現(xiàn)判斷兩個(gè) vector 是否相等 template bool ifTwoVectorEqual(vector arr, vector ano) { if (arr.size() != ano.size())return false; for (int i = 0;i < arr.size();i++) { if (arr[i] != ano[i])return false; } return true; }
MapPoint地圖點(diǎn)的類(lèi),由 indexX 存放列數(shù),indexY 存放行數(shù),有 PathDir 枚舉類(lèi)型枚舉四個(gè)方向,能通過(guò) MapPoint getDirPoint(PathDir turn) 這個(gè)函數(shù)獲得四個(gè)方向的點(diǎn),這個(gè)函數(shù)長(zhǎng)這樣
MapPoint getDirPoint(PathDir turn) { switch (turn) { case path_up: return MapPoint(this->indexX,this->indexY-1); break; case path_down: return MapPoint(this->indexX, this->indexY + 1); break; case path_left: return MapPoint(this->indexX-1, this->indexY); break; case path_right: return MapPoint(this->indexX+1, this->indexY); break; default: break; } }
同時(shí)這個(gè)類(lèi)也用于保存 BoundingBox 類(lèi)的坐標(biāo),因?yàn)?easyx 里的每個(gè)點(diǎn)都是整型,所以保存的坐標(biāo)也是整型。
PathNode廣度尋路的節(jié)點(diǎn)類(lèi),也就是樹(shù)的數(shù)據(jù)結(jié)構(gòu),一個(gè)父節(jié)點(diǎn),多個(gè)子節(jié)點(diǎn)由
MapPoint pos; PathNode* parent; vectorchild;
這三個(gè)數(shù)據(jù)組成,pos 是這個(gè)節(jié)點(diǎn)所在的位置,parent 是這個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn),child 是這個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)們
為方便清理 PathNode 節(jié)點(diǎn),這個(gè)類(lèi)里還提供了靜態(tài)函數(shù)
static void clearNode(PathNode* p_head) { if (p_head->child.empty()) { if (p_head->parent != nullptr) { PathNode* temp = p_head->parent; for (int i = 0;i < temp->child.size();i++) { if (temp->child[i] == p_head) { temp->child.erase(temp->child.begin() + i); break; } } } delete p_head; } else { // vector 的遍歷不夠好,直接這么清除子節(jié)點(diǎn) while (!p_head->child.empty()) { clearNode(p_head->child[0]); } } }
要清理掉一整個(gè) PathNode* phead 樹(shù)只需
PathNode::clearNode(phead);
BFS廣度尋路的類(lèi),包含
functionifCanThrough;// 判斷是否可以通行的函數(shù)指針 unsigned int size_Width, size_Height;// 地圖尺寸,width 為寬度,height 為高度 bool* AuxiliaryMaps;// 輔助地圖,判斷某個(gè)點(diǎn)是否走過(guò)
四個(gè)數(shù)據(jù),用
void BFS::setMap(unsigned int size_Width, unsigned int size_Height, functionCallBack) { this->size_Height = size_Height; this->size_Width = size_Width; ifCanThrough = CallBack; }
設(shè)置地圖尺寸和判斷是否可以通行的函數(shù)指針,setMap 用法如下
int map[3][3] = { 0,1,0, 0,1,0, 0,0,0 }; BFS bfs; bfs.setMap(3, 3, [&](MapPoint num) { if (map[num.indexY][num.indexX] == 0)return true; return false; });// 這是用 lambda 表達(dá)式寫(xiě)的 // 也可以用函數(shù)指針如 bool ifCanThrough(MapPoint num) { if (map[num.indexY][num.indexX] == 0)return true; return false; } bfs.setMap(3, 3, ifCanThrough); // 或者 bool (*p)(MapPoint)=ifCanThrough; bfs.setMap(3,3,p);
初始化 AuxiliaryMap 用
void initAuxiliaryMaps() { AuxiliaryMaps = new bool[size_Height * size_Width]; memset(AuxiliaryMaps, false, sizeof(bool)*size_Height*size_Width); }
清理 AuxiliaryMap 用
void clearAuxiliaryMaps() { if (AuxiliaryMaps != nullptr)delete AuxiliaryMaps; AuxiliaryMaps = nullptr; }
AuxiliaryMap(輔助地圖)的作用是每次遍歷一個(gè)廣度尋路的節(jié)點(diǎn)就把該節(jié)點(diǎn)的位置的 bool 值設(shè)為 true 表示這個(gè)點(diǎn)尋找過(guò)了,避免重復(fù)尋找同一個(gè)位置,尋路完就把輔助地圖清理掉。
由于不知道 ifCanThrough 是否判斷點(diǎn)是否在地圖內(nèi),所以要多寫(xiě)一個(gè)判斷點(diǎn)是否在地圖內(nèi)的函數(shù),避免訪問(wèn) AuxiliaryMap 時(shí)數(shù)組越界,這個(gè)函數(shù)為
bool ifInMap(MapPoint num) { if (num.indexX >= 0 && num.indexX < size_Width && num.indexY >= 0 && num.indexY
現(xiàn)在輔助地圖有了,廣度尋路的節(jié)點(diǎn)有了,是否可以通行的判斷也有了,可以根據(jù)廣度尋路的算法用起點(diǎn)和終點(diǎn)的值找到可以通行的路徑了,尋找路徑的函數(shù)為
vectorgetThroughPath(MapPoint star, MapPoint end);
函數(shù)過(guò)長(zhǎng),就不貼出來(lái)了,廣度尋路的步驟是
1、將起點(diǎn)放進(jìn) PathNode* phead
2、將 phead->pos 在AuxiliaryMap 對(duì)應(yīng)的點(diǎn)的 bool 設(shè)為 true,即 AuxiliaryMap[phead->pos.indexY*size_Width+phead->pos.indexX]=true;
3、判斷 phead->pos 上下左右四個(gè)方向的點(diǎn)是否找尋過(guò),是否可以通行,未找尋過(guò)可以通行則把這個(gè)點(diǎn)放入 phead 的子節(jié)點(diǎn),phead->addchild(new PathNode(MapPoint(phead->pos.getDirPoint(path_up /* 或者 path_down path_left path_rght */)))); 并且放進(jìn) vector
child; 里 4、遍歷 child,看看有沒(méi)有點(diǎn)到達(dá)終點(diǎn),沒(méi)有進(jìn)入步驟 5,有進(jìn)入步驟 8
5、令 vector
parent=child;child.clear(); 遍歷 parnet 里的每個(gè) PathNode,對(duì)每個(gè) PathNode* 單獨(dú)執(zhí)行步驟 3 6、如果 child 為空,進(jìn)入步驟 7,如果 child 不為空,進(jìn)入步驟 4
7、返回空的 vector
result; 8、把找到的 PathNode 節(jié)點(diǎn)保存下來(lái),不停找 pathNode 的父節(jié)點(diǎn),把每個(gè)父節(jié)點(diǎn)的 pos 值push_back 進(jìn)vector
result; 里面返回 result. 具體函數(shù)實(shí)現(xiàn)看 BFS 里的 vector
getThroughPath(MapPointstar, MapPointend); 實(shí)現(xiàn)這個(gè)功能其實(shí)對(duì)圍棋這個(gè)項(xiàng)目沒(méi)有幫助,但是都封裝出了這個(gè)類(lèi),不實(shí)現(xiàn)一下這個(gè)功能總歸有點(diǎn)缺憾,圍棋要判斷所有能走的點(diǎn),只需要在廣度尋路的八個(gè)步驟中去掉對(duì)是否到達(dá)終點(diǎn)的判斷就行了,得到包圍這塊區(qū)域的點(diǎn)只需要在尋找所有能走的點(diǎn)時(shí)遇到 ifCanThrough 為false 的點(diǎn)時(shí)把該點(diǎn)所在 AuxiliaryMap 的bool 值設(shè)為 true 并存進(jìn) vector
result; 里就行,最終返回的就是遇到的所有不能走的點(diǎn),在 BFS 的函數(shù)實(shí)現(xiàn)為
vectorgetAllCanThrough(MapPoint star); vector getEnclosedPoint(MapPoint star);
BFS 中還實(shí)現(xiàn)了單步尋路的功能
vectorSingleSearch(PathNode* begin);
這個(gè)的用法是
int map[3][3] = { 0,1,0, 0,1,0, 0,0,0 }; bool ifCanThrough(MapPoint num) { if (map[num.indexY][num.indexX] == 0)return true; return false; } BFS bfs; PathNode* begin = new PathNode(MapPoint(0, 0)); bfs.setMap(3, 3, ifCanThrough); bfs.initAuxiliaryMaps(); vectorreslt=bfs.SingleSearch(begin); while(!reslt.empty()) { // .....這里寫(xiě)每步尋路后的操作 reslt = bfs.SingleSearch(begin); } bfs.clearAuxiliaryMaps(); PathNode::clearNode(begin);
MapNode地圖節(jié)點(diǎn),我試圖用圖的數(shù)據(jù)結(jié)構(gòu)來(lái)寫(xiě)圍棋的地圖,這樣地圖上的每個(gè)點(diǎn)都是指針,加上 Map 是個(gè)單例模式,得到的每個(gè)點(diǎn),點(diǎn)每個(gè)點(diǎn)的處理都會(huì)反應(yīng)到真實(shí)的地圖上,不用重復(fù)傳參。
這個(gè)頭文件有 Piece 枚舉類(lèi)型
enum Piece {Black,White,Space};
表示圍棋的黑子,白子,空地三種類(lèi)型
這個(gè)類(lèi)有
// 上下左右四個(gè)節(jié)點(diǎn) MapNode* Node_Left; MapNode* Node_Right; MapNode* Node_Up; MapNode* Node_Down; // 這個(gè)點(diǎn)的棋子 Piece Node_Piece; // 這個(gè)點(diǎn)原來(lái)的棋子 Piece original_Piece; // 這個(gè)點(diǎn)的坐標(biāo) int indexX, indexY; // 這個(gè)點(diǎn)棋子被改變的次數(shù) unsigned int changeTimes;
9 個(gè)數(shù)據(jù)
要清理整個(gè)地圖調(diào)用
// 在圖中的任何一個(gè)點(diǎn)都可以用于清除整個(gè)圖 void MapNode::DeleteChild() { // 從父節(jié)點(diǎn)到子節(jié)點(diǎn)瘋狂擴(kuò)散來(lái)清理子節(jié)點(diǎn) vectorparent; vector child; if (this->Node_Down) { child.push_back(this->Node_Down); this->Node_Down->Node_Up = nullptr; this->Node_Down = nullptr; } if (this->Node_Up) { child.push_back(this->Node_Up); this->Node_Up->Node_Down = nullptr; this->Node_Up = nullptr; } if (this->Node_Left) { child.push_back(this->Node_Left); this->Node_Left->Node_Right = nullptr; this->Node_Left = nullptr; } if (this->Node_Right) { child.push_back(this->Node_Right); this->Node_Right->Node_Left = nullptr; this->Node_Right = nullptr; } while (!child.empty()) { parent = child; child.clear(); for (MapNode* parent_Node : parent) { if (parent_Node->Node_Down) { if(ifNotInVector(child, parent_Node->Node_Down)) child.push_back(parent_Node->Node_Down); parent_Node->Node_Down->Node_Up = nullptr; parent_Node->Node_Down = nullptr; } if (parent_Node->Node_Up) { if(ifNotInVector(child, parent_Node->Node_Up)) child.push_back(parent_Node->Node_Up); parent_Node->Node_Up->Node_Down = nullptr; parent_Node->Node_Up = nullptr; } if (parent_Node->Node_Left) { if(ifNotInVector(child, parent_Node->Node_Left)) child.push_back(parent_Node->Node_Left); parent_Node->Node_Left->Node_Right = nullptr; parent_Node->Node_Left = nullptr; } if (parent_Node->Node_Right) { if(ifNotInVector(child, parent_Node->Node_Right)) child.push_back(parent_Node->Node_Right); parent_Node->Node_Right->Node_Left = nullptr; parent_Node->Node_Right = nullptr; } delete parent_Node; } } }
這個(gè)函數(shù)。這個(gè)函數(shù)不會(huì)把自己清理掉,只會(huì)把自己周?chē)乃泄?jié)點(diǎn)設(shè)為 nullptr,所以可以放心在析構(gòu)函數(shù)里用它。
悔棋時(shí)把這個(gè)點(diǎn)設(shè)為某個(gè)棋子用
void MapNode::UndoSetPiece(Piece num) { changeTimes--; if (changeTimes == 1)original_Piece = Space; else if (num == original_Piece) { switch (num) { case White:original_Piece = Black;break; case Black:original_Piece = White;break; default: break; } } Node_Piece =num; }
悔棋時(shí)這個(gè)點(diǎn)如果棋子改變次數(shù)大于 2,設(shè)為與原先相同的子時(shí)原先的子就要設(shè)為的這個(gè)子的相反面,這點(diǎn)有一點(diǎn)小邏輯在里面,當(dāng)然如果改變次數(shù)為 2,要設(shè)為任何子,原來(lái)的子都會(huì)是空地。有閑心的可以自己推一下。
不悔了和落子時(shí)把這個(gè)點(diǎn)設(shè)為某個(gè)棋子時(shí)用
void MapNode::setPiece(Piece num) { if(num==Space&&Node_Piece!=Space)original_Piece = Node_Piece; Node_Piece = num; changeTimes++; }
StepPoint每一步的點(diǎn),用于存每一步落子的地方和每一步悔棋的地方,還有每一步劫爭(zhēng)的 MapNode,用于實(shí)現(xiàn)悔棋和不悔了的功能,共有
int indexX, indexY; // 下的位置 bool ifUpBeEated; // 上邊有沒(méi)有被吃 bool ifDownBeEated; // 下邊有沒(méi)有被吃 bool ifLeftBeEated; // 左邊有沒(méi)有被吃 bool ifRightBeEated; // 右邊有沒(méi)有被吃 Piece Step_Piece; // 這一步是什么棋子 MapNode* kozai; // 這一步劫爭(zhēng)的地方
八個(gè)數(shù)據(jù),如果上邊有被吃,就把上邊的所有空地找到,設(shè)為與這一步棋子相反的棋子,下,左,右亦然,四個(gè)方向判斷完后再把這顆子提掉,這就是悔棋的邏輯,不用存下被吃掉的所有點(diǎn),用四個(gè) bool 值就省去了很多內(nèi)存。
Map,地圖的所有數(shù)據(jù)及數(shù)據(jù)的處理都在 Map 這個(gè)類(lèi)里。
這是個(gè)單例模式的類(lèi),單例模式就是任何人不能 new 出一個(gè)對(duì)象,只有這個(gè)類(lèi)自己才能給出自己的模樣,具體寫(xiě)法為
class A { public: ~A() {}// 析構(gòu)函數(shù)一定要是公有的 static A* getInstance()// getInstance 一定要是靜態(tài)的 { if (p_Ins == nullptr)p_Ins = new A; return p_Ins; } private: A() {};// 構(gòu)造函數(shù)一定要是私有的 static A* p_Ins;// 這個(gè)不能在構(gòu)造函數(shù)里初始化 }; A* A::p_Ins = nullptr;// 這個(gè)不能漏
具體用法你得多多實(shí)踐才能理解透徹,例如寫(xiě)一個(gè)回合制對(duì)戰(zhàn)游戲,一個(gè)英雄一個(gè)怪物,一回合輪一個(gè)人發(fā)動(dòng)攻擊或者防御什么的,調(diào)整每個(gè)人的攻擊力,防御力,暴擊率,看看最后是誰(shuí)贏了這個(gè)小項(xiàng)目,你用單例模式試著做一下差不多就能理解了。之后要說(shuō)的模擬 cocos 就用到了一個(gè)單例模式,也是至關(guān)重要的單例模式。
Map 共有
MapNode* Entity;// 實(shí)體 int sizeX, sizeY; stackeveryStep; stack everyUndoStep; function drawPiece;
這六個(gè)數(shù)據(jù),且這六個(gè)數(shù)據(jù)都是私有的
drawPiece 是個(gè)函數(shù)指針,由于地圖的不同,drawPiece 函數(shù)也會(huì)不同,所以具體情況具體賦值,這個(gè) drawPiece 相當(dāng)于一個(gè)虛函數(shù)。
為 drawPiece 賦值的接口為
void setDrawPiece(functionnum) { drawPiece = num; }
Entity 是地圖數(shù)據(jù)的實(shí)體,通過(guò)不斷地訪問(wèn)
MapNode* Node_Left; MapNode* Node_Right; MapNode* Node_Up; MapNode* Node_Down;
這四個(gè)節(jié)點(diǎn)來(lái)到達(dá)地圖上的任何一個(gè)地方。具體函數(shù)為
MapNode* Map::getMapNode(int indexX, int indexY) { if (!ifInMap(indexX, indexY))return nullptr; MapNode* result=Entity; for (int xx = 0;xx < indexX;xx++)result = result->Node_Right; for (int yy = 0;yy < indexY;yy++)result = result->Node_Down; return result; }
sizeX,sizeY 是地圖尺寸,用于廣度尋路。
everyStep 儲(chǔ)存每一步子落在的地方,everyUndoStep 儲(chǔ)存每一步悔棋提掉的子所在的地方,都是 stack 結(jié)構(gòu)來(lái)存的。
一開(kāi)始棋盤(pán)是空的,所以通過(guò)
void Map::setBlankMap(int width, int height) { sizeX = width; sizeY = height; if (Entity != nullptr) { Entity->DeleteChild(); delete Entity; Entity = nullptr; } Entity = new MapNode; Entity->indexX = 0; Entity->indexY = 0; MapNode* currentY = Entity; MapNode* currentX = Entity; for (int indexY = 0;indexY < height;indexY++) { currentX = currentY; if (indexY != height - 1) { currentY->Node_Down = new MapNode; currentY->Node_Down->Node_Up = currentY; currentY = currentY->Node_Down; currentY->indexX = 0; currentY->indexY = indexY + 1; } for (int indexX = 0;indexX < width-1;indexX++) { currentX->Node_Right = new MapNode; currentX->Node_Right->Node_Left = currentX; if (currentX->Node_Up && currentX->Node_Up->Node_Right) { currentX->Node_Right->Node_Up = currentX->Node_Up->Node_Right; currentX->Node_Up->Node_Right->Node_Down = currentX->Node_Right; } currentX = currentX->Node_Right; currentX->indexX = indexX + 1; currentX->indexY = indexY; } } while (!everyStep.empty())everyStep.pop(); while (!everyUndoStep.empty())everyUndoStep.pop(); }
來(lái)初始化 Entity,sizeX,sizeY。
圍棋的流程為一個(gè)人下一顆子,判斷這顆子吃了幾顆子,把吃掉的子提掉,判斷能不能下在這里(提掉的子大于一或提掉的子為一且不在 everyStep.top().kozai 的地方,沒(méi)有提掉的子且自身的氣不為 0),能下在這里就下在這里,不能下在這里就重新下,下完輪到另一個(gè)人。吃掉子,判斷在不在劫爭(zhēng)的位置,判斷自身的氣是否為 0都要判斷氣,所以首先要實(shí)現(xiàn)判斷一個(gè)區(qū)域的氣的功能。
在 Map 里判斷一個(gè)區(qū)域氣的功能我寫(xiě)為兩個(gè)函數(shù)
vectorMap::getEnclosedPiece(int indexX, int indexY) { vector result; MapNode* num = getMapNode(indexX, indexY); BFS calc; calc.setMap(sizeX, sizeY, [&](MapPoint val) { if (getMapNode(val.indexX, val.indexY)->Node_Piece != num->Node_Piece)return false; return true; }); vector enclose_point=calc.getEnclosedPoint(MapPoint(indexX, indexY)); for (MapPoint i : enclose_point) { result.push_back(getMapNode(i.indexX, i.indexY)); } return result; } int Map::getZoneQi(int indexX, int indexY) { int result = 0; vector enclose_point = getEnclosedPiece(indexX, indexY); for (MapNode* i : enclose_point) { if (i->Node_Piece == Space)result++; } return result; }
getZoneQi 就是判斷一個(gè)區(qū)域氣的函數(shù)。
判斷一個(gè)區(qū)域的氣為 0,那就要把這塊區(qū)域設(shè)為空地,這個(gè)需要得到這塊區(qū)域所有的點(diǎn),然后把這塊區(qū)域所有點(diǎn)設(shè)為空地,實(shí)現(xiàn)這個(gè)功能需要兩個(gè)函數(shù)
vectorMap::getAllSimplePiece(int indexX, int indexY) { vector result; MapNode* num = getMapNode(indexX, indexY); BFS calc; calc.setMap(sizeX, sizeY, [&](MapPoint val) { if (getMapNode(val.indexX, val.indexY)->Node_Piece != num->Node_Piece)return false; return true; }); vector next_point = calc.getAllCanThrough(MapPoint(indexX, indexY)); for (MapPoint i : next_point) { result.push_back(getMapNode(i.indexX, i.indexY)); } return result; } void Map::setZoneSpace(int indexX, int indexY) { vector next_point = getAllSimplePiece(indexX, indexY); for (MapNode* i : next_point) { i->setPiece(Space); } }
能吃子,能提子,然后才能落子,落子的功能比較復(fù)雜,函數(shù)也比較長(zhǎng),總的來(lái)說(shuō)就是
boolputOnePiece(intindexX,intindexY,Piecenum);
這個(gè)函數(shù),如果這個(gè)點(diǎn)能落子返回 true,不能落子返回 false。具體實(shí)現(xiàn)看 gitee 上的源碼
悔棋功能寫(xiě)在
bool Undo();
不悔了的功能寫(xiě)在
bool UnUnDo();
之所以有返回值是因?yàn)橛锌赡軟](méi)落子就有人按悔棋,或者沒(méi)悔過(guò)棋就有人按不悔了,返回的 bool 值是悔棋和不悔了是否成功。
代碼沒(méi)什么好說(shuō)的,看源碼就是了,有點(diǎn)長(zhǎng)。
點(diǎn)目功能寫(xiě)在
double getMesh(Piece num);
里,有點(diǎn)長(zhǎng),看源碼去。
至此圍棋這個(gè)游戲的邏輯已經(jīng)全部實(shí)現(xiàn)了,接著就是界面的切換
2、SimulationCocos(模擬 Cocos)
模擬 Cocos 有三個(gè)模塊,Menu,Scene,Director
Menu 菜單,用于保存每個(gè)按鈕的類(lèi),每個(gè)場(chǎng)景里只有一個(gè)菜單,菜單里有 MenuItem (菜單項(xiàng))
MenuItem菜單項(xiàng),是一個(gè)雙向鏈表,每個(gè)菜單里只有一個(gè) MenuItem 鏈表,每個(gè) MenuItem 里包含一個(gè) Button
Button包含三個(gè)函數(shù)指針
functionResponseFunction; // 響應(yīng) function Call_Back; // 回調(diào) function Restore; // 恢復(fù)
和一個(gè) BoundingBox 類(lèi)。
BoundingBox邊框,包含
int size_width, size_height; // 尺寸 MapPoint Place; // 左上角位置
三個(gè)數(shù)據(jù),判斷某個(gè)點(diǎn)是否在 BoundingBox 里面調(diào)用
bool BoundingBox::ifInBoundingBox(MapPoint num) { int heightest, lowest, leftest, rightest; heightest = Place.indexY; lowest = Place.indexY + size_height; leftest = Place.indexX; rightest = Place.indexX + size_width; if (num.indexX >= leftest && num.indexX <= rightest && num.indexY >= heightest && num.indexY <= lowest)return true; return false; }
當(dāng)一個(gè)場(chǎng)景里發(fā)生了點(diǎn)擊反應(yīng),只需在場(chǎng)景的 Menu 里調(diào)用
MenuItem* Menu::IfHappendEvent(int xx, int yy) { MenuItem* current = head; bool ifFind = false; while (current != nullptr) { if (current->ifThisIsCalling(xx, yy)) { ifFind = true; break; } current = current->child; } if(ifFind) return current; return nullptr; }
就能判斷是否按到了某個(gè)按鈕以及得到那個(gè)按鈕的 MenuItem 值,然后調(diào)用 MenuItem 的按鈕的 ResponseFunc
當(dāng)點(diǎn)擊反應(yīng)結(jié)束時(shí)調(diào)用響應(yīng)中的按鈕的 Restore 然后判斷鼠標(biāo)所在的位置還在不在按鈕里面,在的話調(diào)用按鈕的 Call_Back 函數(shù),函數(shù)里面?zhèn)鞯膮⑹前粹o的邊框,用于繪制按鈕。
Scene場(chǎng)景,繼承自 GameNode 類(lèi),
GameNode是一個(gè)雙向鏈表,有
virtual bool initMySelf() { return true; } virtual bool operation() { return true; } virtual void EndOperation(){}
三個(gè)虛函數(shù),operation 是場(chǎng)景運(yùn)行時(shí)的函數(shù),EndOperation 是令場(chǎng)景結(jié)束運(yùn)行的函數(shù),initMySelf 是初始化場(chǎng)景的函數(shù)
同時(shí)還有
bool GameNode::ifInRace(GameNode* num) { GameNode* current = this; while (current!=nullptr) { if (current == num)return true; current = current->child; } current = this; while (current!=nullptr) { if (current == num)return true; current = current->parent; } return false; }
判斷某個(gè)場(chǎng)景是否和自己有血緣關(guān)系。有血緣關(guān)系返回 true,無(wú)血緣關(guān)系返回 false
在 Scene 里有
functionOperat_Func;
這個(gè)函數(shù)指針,也算是個(gè)虛函數(shù),交由子類(lèi)實(shí)現(xiàn),子類(lèi)必須實(shí)現(xiàn)這個(gè)函數(shù)指針,不然一定會(huì)報(bào)錯(cuò),所以也可以稱(chēng)作不會(huì)報(bào)錯(cuò)的純虛函數(shù)吧。
還有
bool ifExit;
是否退出場(chǎng)景的判斷
在 Scene 里實(shí)現(xiàn)了
bool Scene::operation() { ifExit = false; while (true) { Operat_Func(); if (ifExit)break; } return true; }void EndOperation() { ifExit = true; };
這兩個(gè)函數(shù),operation 里面真正的精華是 Operat_Func(); 這個(gè)函數(shù),這個(gè)函數(shù)交由 Scene 的子類(lèi)實(shí)現(xiàn)。Scene 的子類(lèi)可以通過(guò)調(diào)用 this->EndOperation(); 這個(gè)函數(shù)退出場(chǎng)景。
Director,單例模式,程序運(yùn)行的核心,每個(gè) Scene 都在 Director 里運(yùn)行。只有兩個(gè)數(shù)據(jù)
bool ifExit; // 是否退出的判斷 GameNode* IsRunning; // 當(dāng)前運(yùn)行的場(chǎng)景
Director 里主要通過(guò)兩個(gè)函數(shù)來(lái)實(shí)現(xiàn) Scene 的運(yùn)行和場(chǎng)景的切換
void Director::RunWithScene(GameNode* scene) { if (IsRunning != nullptr) { IsRunning->EndOperation(); } IsRunning = scene; }void Director::Operation() { ifExit = false; GameNode* temp = IsRunning; while (true) { if (temp == nullptr)break; temp->initMySelf(); if (temp->operation())// 場(chǎng)景一律在這個(gè)判斷里運(yùn)行,退出場(chǎng)景時(shí)進(jìn)入判斷 { if(!IsRunning->ifInRace(temp))// 此時(shí) IsRunning 已經(jīng)通過(guò) Director::getInstance()->RunWithScene(...); 改變了自己 delete temp; temp = IsRunning; } if (ifExit)break; } }
IsRunning 變了,temp 不變,原來(lái)的場(chǎng)景能運(yùn)行至結(jié)束然后才跳出,釋放掉原來(lái)場(chǎng)景的內(nèi)存接著才運(yùn)行新的場(chǎng)景,這就是 Director 的核心邏輯,Director 需要和 Scene 互相引用,Scene 通過(guò)訪問(wèn) Director 類(lèi)直接訪問(wèn)當(dāng)前正在運(yùn)行的程序,如果 Director 不是單例模式,那么 Scene 就不能通過(guò)直接訪問(wèn)類(lèi)的方式訪問(wèn)到當(dāng)前的 Director,Director 還得傳參給 Scene,這就造成了 Scene 和Director 互相引用,也就是未定義類(lèi)型的問(wèn)題。所以 Director 用單例模式會(huì)很方便。
當(dāng)然,這只是我使用 Cocos2d-x 根據(jù) Cocos 的特性推測(cè)著寫(xiě)的,Cocos2d-x 里有自動(dòng)釋放池,寫(xiě)起來(lái)估計(jì)比我這種山寨版的要好,但是我這個(gè)在 Scene 里引用了 graphics.h 頭文件,也就是可以在 Scene 里重新定義圖形界面的大小,某種意義上會(huì)比 Cocos2d 方便。
3、GameScene,LoadScene
這兩個(gè)類(lèi)都繼承自 Scene,都需要實(shí)現(xiàn) initMySelf 函數(shù),不過(guò)如果要實(shí)現(xiàn)兩個(gè)場(chǎng)景之間的切換不能通過(guò)互相引用的方法或者分成兩個(gè)文件,一個(gè)頭文件,一個(gè) .cpp 文件來(lái)實(shí)現(xiàn),頭一種會(huì)造成發(fā)現(xiàn)一個(gè)多次重定義的標(biāo)識(shí)符,和未定義標(biāo)識(shí)符的報(bào)錯(cuò),后一種會(huì)多出 140 個(gè)報(bào)錯(cuò)說(shuō)是什么什么字符已經(jīng)定義了??傊畠蓚€(gè)文件不能互相引用,那么就是一個(gè)知道另一個(gè),一個(gè)不知道另一個(gè),在這種情況下要實(shí)現(xiàn)場(chǎng)景的切換就用到了 GameNode 的特性雙向鏈表,比如我是讓 LoadScene 文件里引用了 GameScene 的頭文件,然后在 LoadScene 的類(lèi)里包含了 GameScene* scene; 在構(gòu)造函數(shù)的時(shí)候
scene = new GameScene; scene->addChild(this);
把自己設(shè)為 scene 的子節(jié)點(diǎn),開(kāi)始游戲時(shí)
Director::getInstance()->RunWithScene(scene);
進(jìn)入 GameScene
在 GameScene 里要變回 LoadScene 只需
Director::getInstance()->RunWithScene(this->getChild());
就行了。Director 里要是 IsRunning 和temp 有血緣關(guān)系它是不會(huì) delete 掉temp 的。所以切換場(chǎng)景時(shí)這兩個(gè)場(chǎng)景都不會(huì)被清理掉。
完整的 VC 項(xiàng)目在 gitee 上:https://gitee.com/ProtagonistMan/weiqi
以上就是圍棋的所有邏輯了,至于代碼部分,很長(zhǎng),邏輯都有了就剩搬磚把大樓蓋起來(lái),看不下去我的源碼也可以根據(jù)我的描述寫(xiě)一份自己的了,我相信我描述的夠清楚了。
審核編輯:湯梓紅
-
游戲
+關(guān)注
關(guān)注
2文章
763瀏覽量
26603 -
編程
+關(guān)注
關(guān)注
88文章
3674瀏覽量
94716 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4365瀏覽量
63911 -
C++
+關(guān)注
關(guān)注
22文章
2116瀏覽量
74605
原文標(biāo)題:【項(xiàng)目實(shí)戰(zhàn)】C/C++語(yǔ)言帶你實(shí)現(xiàn):圍棋游戲!詳細(xì)邏輯+核心源碼
文章出處:【微信號(hào):cyuyanxuexi,微信公眾號(hào):C語(yǔ)言編程學(xué)習(xí)基地】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
C語(yǔ)言實(shí)現(xiàn)面向?qū)ο蟮姆绞?C++中的class的運(yùn)行原理
C語(yǔ)言實(shí)現(xiàn):見(jiàn)縫插針游戲!代碼思路+源碼分享
C++程序設(shè)計(jì)語(yǔ)言實(shí)驗(yàn)指導(dǎo)書(shū)
如何使用C語(yǔ)言實(shí)現(xiàn)一個(gè)比較簡(jiǎn)單的猜數(shù)游戲的程序免費(fèi)下載

使用C++語(yǔ)言實(shí)現(xiàn)的解題的實(shí)例說(shuō)明

評(píng)論