大家好,如果您还对gitee手机网站源码分享不太了解,没有关系,今天就由本站为大家分享gitee手机网站源码分享的知识,包括github网站源码的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!
每天一个编程小项目,提升你的编程能力!
游戏介绍
下围棋的程序,实现了界面切换,选择路数,和围棋规则,也实现了点目功能,不过只有当所有棋子都被提走才能点目,不然不准确
操作方法
鼠标操作
游戏截图
编译环境
VisualStudio2019,EasyX_20211109
文件描述
用广度寻路寻找周围所有相同棋子,直到四处碰壁了,得到包围住自己的所有点,看看这些点是空地的数量,空地的数量就是气的数量,气为0这些子全部提掉,设为空地。每下一步棋记录下这步棋的位置,悔棋时把这些点提掉。打劫时在存悔棋的点的地方找到劫争的地方,只吃了一颗子并且落在劫争的地方就不能下。
点目就是找到一块空地,看看围住它的是不是都是同一个颜色的子,是的话这块空地有多大这个颜色的子的目数就加多大。
如果围住空地的是不同颜色的子,那么这块空地多大,这两个子的气就加这块空地的目数的一半。
其他功能就是设计模式的问题了,我借鉴了cocos2d的设计模式,在director里运行整个程序,在scene里写这个层运行的功能,director的runwithscene写切换场景。
详解:
1、公共工具
MyTool
这个文件里包含了广度寻路和围棋地图的类,其中围棋地图通过广度寻路实现了吃子,提子,点目的功能。还有一些之前做七巧板的项目时保存下来的类,暂且用不到,但是也许能方便未来开发,所以放到一起。
DealArray处理数组的头文件,目前有三个函数,作用分别是:将vector首尾颠倒、判断一个元素是否在vector里面,判断两个vector是否相等(每个元素都相等就是两个vector相等),函数实现为
//这个实现vector首尾颠倒\ntemplate<typenameVector_Reverse>//这个是函数模板\nvoidReserve_Vector(vector<Vector_Reverse>&arr)\n{\n\tfor(inti=0;i<(arr.size()>>1);i++)\n\t{\n\t\tVector_Reversetemp=arr[i];\n\t\tarr[i]=arr[arr.size()-i-1];\n\t\tarr[arr.size()-i-1]=temp;\n\t}\n}\n//这个实现判断一个元素是否在vector里面\ntemplate<typenameVectorInclude>\nboolifNotInVector(vector<VectorInclude>arr,VectorIncludenum)\n{\n\tfor(VectorIncludei:arr)\n\t{\n\t\tif(i==num)returnfalse;\n\t}\n\treturntrue;\n}\n//这个实现判断两个vector是否相等\ntemplate<typenameVectorEqual>\nboolifTwoVectorEqual(vector<VectorEqual>arr,vector<VectorEqual>ano)\n{\n\tif(arr.size()!=ano.size())returnfalse;\n\tfor(inti=0;i<arr.size();i++)\n\t{\n\t\tif(arr[i]!=ano[i])returnfalse;\n\t}\n\treturntrue;\n}
MapPoint地图点的类,由indexX存放列数,indexY存放行数,有PathDir枚举类型枚举四个方向,能通过MapPointgetDirPoint(PathDirturn)这个函数获得四个方向的点,这个函数长这样
MapPointgetDirPoint(PathDirturn)\n{\n\n\tswitch(turn)\n\t{\n\tcasepath_up:\n\t\treturnMapPoint(this->indexX,this->indexY-1);\n\t\tbreak;\n\tcasepath_down:\n\t\treturnMapPoint(this->indexX,this->indexY+1);\n\t\tbreak;\n\tcasepath_left:\n\t\treturnMapPoint(this->indexX-1,this->indexY);\n\t\tbreak;\n\tcasepath_right:\n\t\treturnMapPoint(this->indexX+1,this->indexY);\n\t\tbreak;\n\tdefault:\n\tbreak;\n\t}\n}
同时这个类也用于保存BoundingBox类的坐标,因为easyx里的每个点都是整型,所以保存的坐标也是整型。
PathNode广度寻路的节点类,也就是树的数据结构,一个父节点,多个子节点由
MapPointpos;\nPathNode*parent;\nvector<PathNode*>child;
这三个数据组成,pos是这个节点所在的位置,parent是这个节点的父节点,child是这个节点的子节点们
为方便清理PathNode节点,这个类里还提供了静态函数
staticvoidclearNode(PathNode*p_head)\n{\n\tif(p_head->child.empty())\n\t{\n\t\tif(p_head->parent!=nullptr)\n\t\t{\t\n\t\t\tPathNode*temp=p_head->parent;\n\t\t\tfor(inti=0;i<temp->child.size();i++)\n\t\t\t{\n\t\t\t\tif(temp->child[i]==p_head)\n\t\t\t\t{\n\t\t\t\t\ttemp->child.erase(temp->child.begin()+i);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdeletep_head;\n\t}\n\telse\n\t{\n\t\t//vector的遍历不够好,直接这么清除子节点\n\t\twhile(!p_head->child.empty())\n\t\t{\n\t\t\tclearNode(p_head->child[0]);\n\t\t}\n\t}\n}
要清理掉一整个PathNode*phead树只需
PathNode::clearNode(phead);
BFS广度寻路的类,包含
function<bool(MapPoint)>ifCanThrough;//判断是否可以通行的函数指针\nunsignedintsize_Width,size_Height;//地图尺寸,width为宽度,height为高度\nbool*AuxiliaryMaps;//辅助地图,判断某个点是否走过
四个数据,用
voidBFS::setMap(unsignedintsize_Width,unsignedintsize_Height,function<bool(MapPoint)>CallBack)\n{\n\tthis->size_Height=size_Height;\n\tthis->size_Width=size_Width;\n\tifCanThrough=CallBack;\n}
设置地图尺寸和判断是否可以通行的函数指针,setMap用法如下
intmap[3][3]=\n{\n\t0,1,0,\n\t0,1,0,\n\t0,0,0\n};\nBFSbfs;\nbfs.setMap(3,3,[&](MapPointnum)\n{\n\tif(map[num.indexY][num.indexX]==0)returntrue;\n\treturnfalse;\n});//这是用lambda表达式写的\n//也可以用函数指针如\nboolifCanThrough(MapPointnum)\n{\n\tif(map[num.indexY][num.indexX]==0)returntrue;\n\treturnfalse;\n}\nbfs.setMap(3,3,ifCanThrough);\n//或者\nbool(*p)(MapPoint)=ifCanThrough;\nbfs.setMap(3,3,p);
初始化AuxiliaryMap用
voidinitAuxiliaryMaps()\n{\n\tAuxiliaryMaps=newbool[size_Height*size_Width];\n\tmemset(AuxiliaryMaps,false,sizeof(bool)*size_Height*size_Width);\n}
清理AuxiliaryMap用
voidclearAuxiliaryMaps()\n{\n\tif(AuxiliaryMaps!=nullptr)deleteAuxiliaryMaps;\n\tAuxiliaryMaps=nullptr;\n}
AuxiliaryMap(辅助地图)的作用是每次遍历一个广度寻路的节点就把该节点的位置的bool值设为true表示这个点寻找过了,避免重复寻找同一个位置,寻路完就把辅助地图清理掉。
由于不知道ifCanThrough是否判断点是否在地图内,所以要多写一个判断点是否在地图内的函数,避免访问AuxiliaryMap时数组越界,这个函数为
boolifInMap(MapPointnum)\n{\n\tif(num.indexX>=0&&num.indexX<size_Width&&num.indexY>=0&&num.indexY<size_Height)returntrue;\n\treturnfalse;\n}
现在辅助地图有了,广度寻路的节点有了,是否可以通行的判断也有了,可以根据广度寻路的算法用起点和终点的值找到可以通行的路径了,寻找路径的函数为
vector<MapPoint>getThroughPath(MapPointstar,MapPointend);
函数过长,就不贴出来了,广度寻路的步骤是
1、将起点放进PathNode*phead
2、将phead->pos在AuxiliaryMap对应的点的bool设为true,即AuxiliaryMap[phead->pos.indexY*size_Width+phead->pos.indexX]=true;
3、判断phead->pos上下左右四个方向的点是否找寻过,是否可以通行,未找寻过可以通行则把这个点放入phead的子节点,phead->addchild(newPathNode(MapPoint(phead->pos.getDirPoint(path_up/*或者path_downpath_leftpath_rght*/))));并且放进vector<PathNode*>child;里
4、遍历child,看看有没有点到达终点,没有进入步骤5,有进入步骤8
5、令vector<PathNode*>parent=child;child.clear();遍历parnet里的每个PathNode,对每个PathNode*单独执行步骤3
6、如果child为空,进入步骤7,如果child不为空,进入步骤4
7、返回空的vector<MapPoint>result;
8、把找到的PathNode节点保存下来,不停找pathNode的父节点,把每个父节点的pos值push_back进vector<MapPoint>result;里面返回result.
具体函数实现看BFS里的vector<MapPoint>getThroughPath(MapPointstar,MapPointend);
实现这个功能其实对围棋这个项目没有帮助,但是都封装出了这个类,不实现一下这个功能总归有点缺憾,围棋要判断所有能走的点,只需要在广度寻路的八个步骤中去掉对是否到达终点的判断就行了,得到包围这块区域的点只需要在寻找所有能走的点时遇到ifCanThrough为false的点时把该点所在AuxiliaryMap的bool值设为true并存进vector<MapPoint>result;里就行,最终返回的就是遇到的所有不能走的点,在BFS的函数实现为
vector<MapPoint>getAllCanThrough(MapPointstar);\nvector<MapPoint>getEnclosedPoint(MapPointstar);
BFS中还实现了单步寻路的功能
vector<MapPoint>SingleSearch(PathNode*begin);
这个的用法是
intmap[3][3]=\n{\n\t0,1,0,\n\t0,1,0,\n\t0,0,0\n};\nboolifCanThrough(MapPointnum)\n{\n\tif(map[num.indexY][num.indexX]==0)returntrue;\n\treturnfalse;\n}\nBFSbfs;\nPathNode*begin=newPathNode(MapPoint(0,0));\nbfs.setMap(3,3,ifCanThrough);\nbfs.initAuxiliaryMaps();\nvector<MapPoint>reslt=bfs.SingleSearch(begin);\nwhile(!reslt.empty())\n{\n\t//…..这里写每步寻路后的操作\n\treslt=bfs.SingleSearch(begin);\n}\nbfs.clearAuxiliaryMaps();\nPathNode::clearNode(begin);
MapNode地图节点,我试图用图的数据结构来写围棋的地图,这样地图上的每个点都是指针,加上Map是个单例模式,得到的每个点,点每个点的处理都会反应到真实的地图上,不用重复传参。
这个头文件有Piece枚举类型
enumPiece{Black,White,Space};
表示围棋的黑子,白子,空地三种类型
这个类有
//上下左右四个节点\nMapNode*Node_Left;\nMapNode*Node_Right;\nMapNode*Node_Up;\nMapNode*Node_Down;\n//这个点的棋子\nPieceNode_Piece;\n//这个点原来的棋子\nPieceoriginal_Piece;\n//这个点的坐标\nintindexX,indexY;\n//这个点棋子被改变的次数\nunsignedintchangeTimes;
9个数据
要清理整个地图调用
//在图中的任何一个点都可以用于清除整个图\nvoidMapNode::DeleteChild()\n{\n\t//从父节点到子节点疯狂扩散来清理子节点\n\tvector<MapNode*>parent;\n\tvector<MapNode*>child;\n\tif(this->Node_Down)\n\t{\n\tchild.push_back(this->Node_Down);\n\tthis->Node_Down->Node_Up=nullptr;\n\tthis->Node_Down=nullptr;\n\t}\n\tif(this->Node_Up)\n\t{\n\t\tchild.push_back(this->Node_Up);\n\t\tthis->Node_Up->Node_Down=nullptr;\n\t\tthis->Node_Up=nullptr;\n\t}\n\tif(this->Node_Left)\n\t{\n\t\tchild.push_back(this->Node_Left);\n\t\tthis->Node_Left->Node_Right=nullptr;\n\t\tthis->Node_Left=nullptr;\n\t}\n\tif(this->Node_Right)\n\t{\n\t\tchild.push_back(this->Node_Right);\n\t\tthis->Node_Right->Node_Left=nullptr;\n\t\tthis->Node_Right=nullptr;\n\t}\n\twhile(!child.empty())\n\t{\n\t\tparent=child;\n\t\tchild.clear();\n\t\tfor(MapNode*parent_Node:parent)\n\t\t{\n\t\t\tif(parent_Node->Node_Down)\n\t\t\t{\n\t\t\t\tif(ifNotInVector(child,parent_Node->Node_Down))\n\t\t\t\tchild.push_back(parent_Node->Node_Down);\n\t\t\t\tparent_Node->Node_Down->Node_Up=nullptr;\n\t\t\t\tparent_Node->Node_Down=nullptr;\n\t\t\t}\n\t\t\tif(parent_Node->Node_Up)\n\t\t\t{\n\t\t\t\tif(ifNotInVector(child,parent_Node->Node_Up))\n\t\t\t\tchild.push_back(parent_Node->Node_Up);\n\t\t\t\tparent_Node->Node_Up->Node_Down=nullptr;\n\t\t\t\tparent_Node->Node_Up=nullptr;\n\t\t\t}\n\t\t\tif(parent_Node->Node_Left)\n\t\t\t{\n\t\t\t\tif(ifNotInVector(child,parent_Node->Node_Left))\n\t\t\t\tchild.push_back(parent_Node->Node_Left);\n\t\t\t\tparent_Node->Node_Left->Node_Right=nullptr;\n\t\t\t\tparent_Node->Node_Left=nullptr;\n\t\t\t}\t\n\t\t\tif(parent_Node->Node_Right)\n\t\t\t{\n\t\t\t\tif(ifNotInVector(child,parent_Node->Node_Right))\n\t\t\t\tchild.push_back(parent_Node->Node_Right);\n\t\t\t\tparent_Node->Node_Right->Node_Left=nullptr;\n\t\t\t\tparent_Node->Node_Right=nullptr;\n\t\t\t}\n\t\t\tdeleteparent_Node;\n\t\t}\n\t}\n}
这个函数。这个函数不会把自己清理掉,只会把自己周围的所有节点设为nullptr,所以可以放心在析构函数里用它。
悔棋时把这个点设为某个棋子用
voidMapNode::UndoSetPiece(Piecenum)\n{\n\tchangeTimes–;\n\tif(changeTimes==1)original_Piece=Space;\n\telseif(num==original_Piece)\n\t{\n\t\tswitch(num)\n\t\t{\n\t\tcaseWhite:original_Piece=Black;break;\n\t\tcaseBlack:original_Piece=White;break;\n\t\tdefault:\n\t\tbreak;\n\t\t}\n\t}\n\tNode_Piece=num;\n}
悔棋时这个点如果棋子改变次数大于2,设为与原先相同的子时原先的子就要设为的这个子的相反面,这点有一点小逻辑在里面,当然如果改变次数为2,要设为任何子,原来的子都会是空地。有闲心的可以自己推一下。
不悔了和落子时把这个点设为某个棋子时用
voidMapNode::setPiece(Piecenum)\n{\n\tif(num==Space&&Node_Piece!=Space)original_Piece=Node_Piece;\n\tNode_Piece=num;\n\tchangeTimes++;\n}
StepPoint每一步的点,用于存每一步落子的地方和每一步悔棋的地方,还有每一步劫争的MapNode,用于实现悔棋和不悔了的功能,共有
intindexX,indexY;\t\t//下的位置\nboolifUpBeEated;\t\t//上边有没有被吃\nboolifDownBeEated;\t\t//下边有没有被吃\nboolifLeftBeEated;\t\t//左边有没有被吃\nboolifRightBeEated;\t//右边有没有被吃\nPieceStep_Piece;\t\t//这一步是什么棋子\nMapNode*kozai;\t\t\t//这一步劫争的地方
八个数据,如果上边有被吃,就把上边的所有空地找到,设为与这一步棋子相反的棋子,下,左,右亦然,四个方向判断完后再把这颗子提掉,这就是悔棋的逻辑,不用存下被吃掉的所有点,用四个bool值就省去了很多内存。
Map,地图的所有数据及数据的处理都在Map这个类里。
这是个单例模式的类,单例模式就是任何人不能new出一个对象,只有这个类自己才能给出自己的模样,具体写法为
classA\n{\npublic:\n\t~A(){}//析构函数一定要是公有的\n\tstaticA*getInstance()//getInstance一定要是静态的\n\t{\n\t\tif(p_Ins==nullptr)p_Ins=newA;\n\t\treturnp_Ins;\n\t}\nprivate:\n\tA(){};//构造函数一定要是私有的\n\tstaticA*p_Ins;//这个不能在构造函数里初始化\n};\nA*A::p_Ins=nullptr;//这个不能漏
具体用法你得多多实践才能理解透彻,例如写一个回合制对战游戏,一个英雄一个怪物,一回合轮一个人发动攻击或者防御什么的,调整每个人的攻击力,防御力,暴击率,看看最后是谁赢了这个小项目,你用单例模式试着做一下差不多就能理解了。之后要说的模拟cocos就用到了一个单例模式,也是至关重要的单例模式。
Map共有
MapNode*Entity;//实体\nintsizeX,sizeY;\nstack<StepPoint>everyStep;\nstack<StepPoint>everyUndoStep;\nfunction<void(intindexX,intindexY,Piecenum)>drawPiece;
这六个数据,且这六个数据都是私有的
drawPiece是个函数指针,由于地图的不同,drawPiece函数也会不同,所以具体情况具体赋值,这个drawPiece相当于一个虚函数。
为drawPiece赋值的接口为
voidsetDrawPiece(function<void(intindexX,intindexY,Piecenum)>num)\n{\n\tdrawPiece=num;\n}
Entity是地图数据的实体,通过不断地访问
MapNode*Node_Left;\nMapNode*Node_Right;\nMapNode*Node_Up;\nMapNode*Node_Down;
这四个节点来到达地图上的任何一个地方。具体函数为
MapNode*Map::getMapNode(intindexX,intindexY)\n{\n\tif(!ifInMap(indexX,indexY))returnnullptr;\n\tMapNode*result=Entity;\n\tfor(intxx=0;xx<indexX;xx++)result=result->Node_Right;\n\tfor(intyy=0;yy<indexY;yy++)result=result->Node_Down;\n\treturnresult;\n}
sizeX,sizeY是地图尺寸,用于广度寻路。
everyStep储存每一步子落在的地方,everyUndoStep储存每一步悔棋提掉的子所在的地方,都是stack结构来存的。
一开始棋盘是空的,所以通过
voidMap::setBlankMap(intwidth,intheight)\n{\n\tsizeX=width;\n\tsizeY=height;\n\tif(Entity!=nullptr)\n\t{\n\t\tEntity->DeleteChild();\n\t\tdeleteEntity;\n\t\tEntity=nullptr;\n\t}\n\tEntity=newMapNode;\n\tEntity->indexX=0;\n\tEntity->indexY=0;\n\tMapNode*currentY=Entity;\n\tMapNode*currentX=Entity;\n\tfor(intindexY=0;indexY<height;indexY++)\n\t{\n\t\tcurrentX=currentY;\n\t\tif(indexY!=height-1)\n\t\t{\n\t\t\tcurrentY->Node_Down=newMapNode;\n\t\t\tcurrentY->Node_Down->Node_Up=currentY;\n\t\t\tcurrentY=currentY->Node_Down;\n\t\t\tcurrentY->indexX=0;\n\t\t\tcurrentY->indexY=indexY+1;\n\t\t}\n\t\tfor(intindexX=0;indexX<width-1;indexX++)\n\t\t{\n\t\t\tcurrentX->Node_Right=newMapNode;\n\t\t\tcurrentX->Node_Right->Node_Left=currentX;\n\t\t\tif(currentX->Node_Up&¤tX->Node_Up->Node_Right)\n\t\t\t{\n\t\t\t\tcurrentX->Node_Right->Node_Up=currentX->Node_Up->Node_Right;\n\t\t\t\tcurrentX->Node_Up->Node_Right->Node_Down=currentX->Node_Right;\n\t\t\t}\n\t\t\tcurrentX=currentX->Node_Right;\n\t\t\tcurrentX->indexX=indexX+1;\n\t\t\tcurrentX->indexY=indexY;\n\t\t}\n\t}\n\twhile(!everyStep.empty())everyStep.pop();\n\twhile(!everyUndoStep.empty())everyUndoStep.pop();\n}
来初始化Entity,sizeX,sizeY。
围棋的流程为一个人下一颗子,判断这颗子吃了几颗子,把吃掉的子提掉,判断能不能下在这里(提掉的子大于一或提掉的子为一且不在everyStep.top().kozai的地方,没有提掉的子且自身的气不为0),能下在这里就下在这里,不能下在这里就重新下,下完轮到另一个人。吃掉子,判断在不在劫争的位置,判断自身的气是否为0都要判断气,所以首先要实现判断一个区域的气的功能。
在Map里判断一个区域气的功能我写为两个函数
vector<MapNode*>Map::getEnclosedPiece(intindexX,intindexY)\n{\n\tvector<MapNode*>result;\n\tMapNode*num=getMapNode(indexX,indexY);\n\tBFScalc;\n\tcalc.setMap(sizeX,sizeY,[&](MapPointval)\n\t{\n\t\tif(getMapNode(val.indexX,val.indexY)->Node_Piece!=num->Node_Piece)returnfalse;\n\t\treturntrue;\n\t});\n\tvector<MapPoint>enclose_point=calc.getEnclosedPoint(MapPoint(indexX,indexY));\n\tfor(MapPointi:enclose_point)\n\t{\n\t\tresult.push_back(getMapNode(i.indexX,i.indexY));\n\t}\n\treturnresult;\n}\nintMap::getZoneQi(intindexX,intindexY)\n{\n\tintresult=0;\n\tvector<MapNode*>enclose_point=getEnclosedPiece(indexX,indexY);\n\tfor(MapNode*i:enclose_point)\n\t{\n\t\tif(i->Node_Piece==Space)result++;\n\t}\n\treturnresult;\n}
getZoneQi就是判断一个区域气的函数。
判断一个区域的气为0,那就要把这块区域设为空地,这个需要得到这块区域所有的点,然后把这块区域所有点设为空地,实现这个功能需要两个函数
vector<MapNode*>Map::getAllSimplePiece(intindexX,intindexY)\n{\n\tvector<MapNode*>result;\n\tMapNode*num=getMapNode(indexX,indexY);\n\tBFScalc;\n\tcalc.setMap(sizeX,sizeY,[&](MapPointval)\n\t{\n\t\tif(getMapNode(val.indexX,val.indexY)->Node_Piece!=num->Node_Piece)returnfalse;\n\t\treturntrue;\n\t});\n\tvector<MapPoint>next_point=calc.getAllCanThrough(MapPoint(indexX,indexY));\n\tfor(MapPointi:next_point)\n\t{\n\t\tresult.push_back(getMapNode(i.indexX,i.indexY));\n\t}\n\treturnresult;\n}\n\nvoidMap::setZoneSpace(intindexX,intindexY)\n{\n\tvector<MapNode*>next_point=getAllSimplePiece(indexX,indexY);\n\tfor(MapNode*i:next_point)\n\t{\n\t\ti->setPiece(Space);\n\t}\n}
能吃子,能提子,然后才能落子,落子的功能比较复杂,函数也比较长,总的来说就是
boolputOnePiece(intindexX,intindexY,Piecenum);
这个函数,如果这个点能落子返回true,不能落子返回false。具体实现看gitee上的源码
悔棋功能写在
boolUndo();
不悔了的功能写在
boolUnUnDo();
之所以有返回值是因为有可能没落子就有人按悔棋,或者没悔过棋就有人按不悔了,返回的bool值是悔棋和不悔了是否成功。
代码没什么好说的,看源码就是了,有点长。
点目功能写在
doublegetMesh(Piecenum);
里,有点长,看源码去。
至此围棋这个游戏的逻辑已经全部实现了,接着就是界面的切换
2、SimulationCocos(模拟Cocos)
模拟Cocos有三个模块,Menu,Scene,Director
Menu菜单,用于保存每个按钮的类,每个场景里只有一个菜单,菜单里有MenuItem(菜单项)
MenuItem菜单项,是一个双向链表,每个菜单里只有一个MenuItem链表,每个MenuItem里包含一个Button
Button包含三个函数指针
function<void(BoundingBoxnum)>ResponseFunction;\t//响应\nfunction<bool(BoundingBoxnum)>Call_Back;\t\t\t//回调\nfunction<void(BoundingBoxnum)>Restore;\t\t\t//恢复
和一个BoundingBox类。
BoundingBox边框,包含
intsize_width,size_height;\t//尺寸\nMapPointPlace;\t\t\t\t\t//左上角位置
三个数据,判断某个点是否在BoundingBox里面调用
boolBoundingBox::ifInBoundingBox(MapPointnum)\n{\n\tintheightest,lowest,leftest,rightest;\n\theightest=Place.indexY;\n\tlowest=Place.indexY+size_height;\n\tleftest=Place.indexX;\n\trightest=Place.indexX+size_width;\n\tif(num.indexX>=leftest&&num.indexX<=rightest&&num.indexY>=heightest&&num.indexY<=lowest)returntrue;\n\treturnfalse;\n}
当一个场景里发生了点击反应,只需在场景的Menu里调用
MenuItem*Menu::IfHappendEvent(intxx,intyy)\n{\n\tMenuItem*current=head;\n\tboolifFind=false;\n\twhile(current!=nullptr)\n\t{\n\t\tif(current->ifThisIsCalling(xx,yy))\n\t\t{\n\t\t\tifFind=true;\n\t\t\tbreak;\n\t\t}\n\t\tcurrent=current->child;\n\t}\n\tif(ifFind)\n\treturncurrent;\n\treturnnullptr;\n}
就能判断是否按到了某个按钮以及得到那个按钮的MenuItem值,然后调用MenuItem的按钮的ResponseFunc
当点击反应结束时调用响应中的按钮的Restore然后判断鼠标所在的位置还在不在按钮里面,在的话调用按钮的Call_Back函数,函数里面传的参是按钮的边框,用于绘制按钮。
Scene场景,继承自GameNode类,
GameNode是一个双向链表,有
virtualboolinitMySelf(){returntrue;}\nvirtualbooloperation(){returntrue;}\nvirtualvoidEndOperation(){}
三个虚函数,operation是场景运行时的函数,EndOperation是令场景结束运行的函数,initMySelf是初始化场景的函数
同时还有
boolGameNode::ifInRace(GameNode*num)\n{\n\tGameNode*current=this;\n\twhile(current!=nullptr)\n\t{\n\t\tif(current==num)returntrue;\n\t\tcurrent=current->child;\n\t}\n\tcurrent=this;\n\twhile(current!=nullptr)\n\t{\n\t\tif(current==num)returntrue;\n\t\tcurrent=current->parent;\n\t}\n\treturnfalse;\n}
判断某个场景是否和自己有血缘关系。有血缘关系返回true,无血缘关系返回false
在Scene里有
function<void()>Operat_Func;
这个函数指针,也算是个虚函数,交由子类实现,子类必须实现这个函数指针,不然一定会报错,所以也可以称作不会报错的纯虚函数吧。
还有
boolifExit;
是否退出场景的判断
在Scene里实现了
boolScene::operation()\n{\n\tifExit=false;\n\twhile(true)\n\t{\n\t\tOperat_Func();\n\t\tif(ifExit)break;\n\t}\n\treturntrue;\n}
voidEndOperation(){ifExit=true;};
这两个函数,operation里面真正的精华是Operat_Func();这个函数,这个函数交由Scene的子类实现。Scene的子类可以通过调用this->EndOperation();这个函数退出场景。
Director,单例模式,程序运行的核心,每个Scene都在Director里运行。只有两个数据
boolifExit;\t\t\t//是否退出的判断\nGameNode*IsRunning;\t//当前运行的场景
Director里主要通过两个函数来实现Scene的运行和场景的切换
voidDirector::RunWithScene(GameNode*scene)\n{\n\tif(IsRunning!=nullptr)\n\t{\n\t\tIsRunning->EndOperation();\n\t}\n\tIsRunning=scene;\n}
voidDirector::Operation()\n{\n\tifExit=false;\n\tGameNode*temp=IsRunning;\n\twhile(true)\n\t{\n\t\tif(temp==nullptr)break;\n\t\ttemp->initMySelf();\n\t\tif(temp->operation())//场景一律在这个判断里运行,退出场景时进入判断\n\t\t{\n\t\t\tif(!IsRunning->ifInRace(temp))//此时IsRunning已经通过Director::getInstance()->RunWithScene(…);改变了自己\n\t\t\tdeletetemp;\n\t\t\ttemp=IsRunning;\n\t\t}\n\t\tif(ifExit)break;\n\t}\n}
IsRunning变了,temp不变,原来的场景能运行至结束然后才跳出,释放掉原来场景的内存接着才运行新的场景,这就是Director的核心逻辑,Director需要和Scene互相引用,Scene通过访问Director类直接访问当前正在运行的程序,如果Director不是单例模式,那么Scene就不能通过直接访问类的方式访问到当前的Director,Director还得传参给Scene,这就造成了Scene和Director互相引用,也就是未定义类型的问题。所以Director用单例模式会很方便。
当然,这只是我使用Cocos2d-x根据Cocos的特性推测着写的,Cocos2d-x里有自动释放池,写起来估计比我这种山寨版的要好,但是我这个在Scene里引用了graphics.h头文件,也就是可以在Scene里重新定义图形界面的大小,某种意义上会比Cocos2d方便。
3、GameScene,LoadScene
这两个类都继承自Scene,都需要实现initMySelf函数,不过如果要实现两个场景之间的切换不能通过互相引用的方法或者分成两个文件,一个头文件,一个.cpp文件来实现,头一种会造成发现一个多次重定义的标识符,和未定义标识符的报错,后一种会多出140个报错说是什么什么字符已经定义了。总之两个文件不能互相引用,那么就是一个知道另一个,一个不知道另一个,在这种情况下要实现场景的切换就用到了GameNode的特性双向链表,比如我是让LoadScene文件里引用了GameScene的头文件,然后在LoadScene的类里包含了GameScene*scene;在构造函数的时候
scene=newGameScene;\nscene->addChild(this);
把自己设为scene的子节点,开始游戏时
Director::getInstance()->RunWithScene(scene);
进入GameScene
在GameScene里要变回LoadScene只需
Director::getInstance()->RunWithScene(this->getChild());
就行了。Director里要是IsRunning和temp有血缘关系它是不会delete掉temp的。所以切换场景时这两个场景都不会被清理掉。
完整的VC项目在gitee上:https://gitee.com/ProtagonistMan/weiqi
以上就是围棋的所有逻辑了,至于代码部分,很长,逻辑都有了就剩搬砖把大楼盖起来,看不下去我的源码也可以根据我的描述写一份自己的了,我相信我描述的够清楚了。
此外,我也给大家分享我收集的其他资源,从最零基础开始的C语言C++教程,帮助大家在学习C语言的道路上披荆斩棘!
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!
OK,本文到此结束,希望对大家有所帮助。