吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于C/C++語言實現圍棋游戲

C語言編程學習基地 ? 來源:C語言編程學習基地 ? 2023-03-08 17:07 ? 次閱讀

每天一個編程小項目,提升你的編程能力!

39c69744-ade6-11ed-bfe3-dac502259ad0.png

游戲介紹

下圍棋的程序,實現了界面切換,選擇路數,和圍棋規則,也實現了點目功能,不過只有當所有死子都被提走才能點目,不然不準確

操作方法

鼠標操作

游戲截圖

39e9572a-ade6-11ed-bfe3-dac502259ad0.png

39f915fc-ade6-11ed-bfe3-dac502259ad0.png

編譯環境

VisualStudio2019,EasyX_20211109

文件描述

用廣度尋路尋找周圍所有相同棋子,直到四處碰壁了,得到包圍住自己的所有點,看看這些點是空地的數量,空地的數量就是氣的數量,氣為 0這些子全部提掉,設為空地。每下一步棋記錄下這步棋的位置,悔棋時把這些點提掉。打劫時在存悔棋的點的地方找到劫爭的地方,只吃了一顆子并且落在劫爭的地方就不能下。

點目就是找到一塊空地,看看圍住它的是不是都是同一個顏色的子,是的話這塊空地有多大這個顏色的子的目數就加多大。

如果圍住空地的是不同顏色的子,那么這塊空地多大,這兩個子的氣就加這塊空地的目數的一半。

其他功能就是設計模式的問題了,我借鑒了 cocos2d 的設計模式,在 director 里運行整個程序,在 scene 里寫這個層運行的功能,director 的runwithscene 寫切換場景。

詳解:

1、公共工具

MyTool

這個文件里包含了廣度尋路和圍棋地圖的類,其中圍棋地圖通過廣度尋路實現了吃子,提子,點目的功能。還有一些之前做七巧板的項目時保存下來的類,暫且用不到,但是也許能方便未來開發,所以放到一起。

DealArray處理數組的頭文件,目前有三個函數,作用分別是:將 vector 首尾顛倒、判斷一個元素是否在 vector 里面,判斷兩個 vector 是否相等(每個元素都相等就是兩個 vector 相等),函數實現為

// 這個實現 vector 首尾顛倒
template// 這個是函數模板
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;
  }
}
// 這個實現判斷一個元素是否在 vector 里面
template
bool ifNotInVector(vectorarr, VectorInclude num)
{
  for (VectorInclude i : arr)
  {
    if (i == num)return false;
  }
  return true;
}
// 這個實現判斷兩個 vector 是否相等
template
bool ifTwoVectorEqual(vector arr, vectorano)
{
  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地圖點的類,由 indexX 存放列數,indexY 存放行數,有 PathDir 枚舉類型枚舉四個方向,能通過 MapPoint getDirPoint(PathDir turn) 這個函數獲得四個方向的點,這個函數長這樣

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

同時這個類也用于保存 BoundingBox 類的坐標,因為 easyx 里的每個點都是整型,所以保存的坐標也是整型。

PathNode廣度尋路的節點類,也就是樹的數據結構,一個父節點,多個子節點由

MapPoint pos;
PathNode* parent;
vector child;

這三個數據組成,pos 是這個節點所在的位置,parent 是這個節點的父節點,child 是這個節點的子節點們

為方便清理 PathNode 節點,這個類里還提供了靜態函數

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 的遍歷不夠好,直接這么清除子節點
    while (!p_head->child.empty())
    {
      clearNode(p_head->child[0]);
    }
  }
}

要清理掉一整個 PathNode* phead 樹只需

PathNode::clearNode(phead);

BFS廣度尋路的類,包含

function ifCanThrough;// 判斷是否可以通行的函數指針
unsigned int size_Width, size_Height;// 地圖尺寸,width 為寬度,height 為高度
bool* AuxiliaryMaps;// 輔助地圖,判斷某個點是否走過

四個數據,用

void BFS::setMap(unsigned int size_Width, unsigned int size_Height, function CallBack)
{
  this->size_Height = size_Height;
  this->size_Width = size_Width;
  ifCanThrough = CallBack;
}

設置地圖尺寸和判斷是否可以通行的函數指針,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 表達式寫的
// 也可以用函數指針如
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(輔助地圖)的作用是每次遍歷一個廣度尋路的節點就把該節點的位置的 bool 值設為 true 表示這個點尋找過了,避免重復尋找同一個位置,尋路完就把輔助地圖清理掉。

由于不知道 ifCanThrough 是否判斷點是否在地圖內,所以要多寫一個判斷點是否在地圖內的函數,避免訪問 AuxiliaryMap 時數組越界,這個函數為

bool ifInMap(MapPoint num)
{
  if (num.indexX >= 0 && num.indexX < size_Width && num.indexY >= 0 && num.indexY

現在輔助地圖有了,廣度尋路的節點有了,是否可以通行的判斷也有了,可以根據廣度尋路的算法用起點和終點的值找到可以通行的路徑了,尋找路徑的函數為

vector getThroughPath(MapPoint star, MapPoint end);

函數過長,就不貼出來了,廣度尋路的步驟是

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(new PathNode(MapPoint(phead->pos.getDirPoint(path_up /* 或者 path_down path_left path_rght */)))); 并且放進 vectorchild; 里

4、遍歷 child,看看有沒有點到達終點,沒有進入步驟 5,有進入步驟 8

5、令 vectorparent=child;child.clear(); 遍歷 parnet 里的每個 PathNode,對每個 PathNode* 單獨執行步驟 3

6、如果 child 為空,進入步驟 7,如果 child 不為空,進入步驟 4

7、返回空的 vectorresult;

8、把找到的 PathNode 節點保存下來,不停找 pathNode 的父節點,把每個父節點的 pos 值push_back 進vector result; 里面返回 result.

具體函數實現看 BFS 里的 vector getThroughPath(MapPointstar, MapPointend);

實現這個功能其實對圍棋這個項目沒有幫助,但是都封裝出了這個類,不實現一下這個功能總歸有點缺憾,圍棋要判斷所有能走的點,只需要在廣度尋路的八個步驟中去掉對是否到達終點的判斷就行了,得到包圍這塊區域的點只需要在尋找所有能走的點時遇到 ifCanThrough 為false 的點時把該點所在 AuxiliaryMap 的bool 值設為 true 并存進 vector result; 里就行,最終返回的就是遇到的所有不能走的點,在 BFS 的函數實現為

vector getAllCanThrough(MapPoint star);
vector getEnclosedPoint(MapPoint star);

BFS 中還實現了單步尋路的功能

vector SingleSearch(PathNode* begin);

這個的用法是

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();
vector reslt=bfs.SingleSearch(begin);
while(!reslt.empty())
{
  // .....這里寫每步尋路后的操作
  reslt = bfs.SingleSearch(begin);
}
bfs.clearAuxiliaryMaps();
PathNode::clearNode(begin);

MapNode地圖節點,我試圖用圖的數據結構來寫圍棋的地圖,這樣地圖上的每個點都是指針,加上 Map 是個單例模式,得到的每個點,點每個點的處理都會反應到真實的地圖上,不用重復傳參。

這個頭文件有 Piece 枚舉類型

enum Piece {Black,White,Space};

表示圍棋的黑子,白子,空地三種類型

這個類有

// 上下左右四個節點
MapNode* Node_Left;
MapNode* Node_Right;
MapNode* Node_Up;
MapNode* Node_Down;
// 這個點的棋子
Piece Node_Piece;
// 這個點原來的棋子
Piece original_Piece;
// 這個點的坐標
int indexX, indexY;
// 這個點棋子被改變的次數
unsigned int changeTimes;

9 個數據

要清理整個地圖調用

// 在圖中的任何一個點都可以用于清除整個圖
void MapNode::DeleteChild()
{
  // 從父節點到子節點瘋狂擴散來清理子節點
  vector parent;
  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;
    }
  }
}

這個函數。這個函數不會把自己清理掉,只會把自己周圍的所有節點設為 nullptr,所以可以放心在析構函數里用它。

悔棋時把這個點設為某個棋子用

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

悔棋時這個點如果棋子改變次數大于 2,設為與原先相同的子時原先的子就要設為的這個子的相反面,這點有一點小邏輯在里面,當然如果改變次數為 2,要設為任何子,原來的子都會是空地。有閑心的可以自己推一下。

不悔了和落子時把這個點設為某個棋子時用

void MapNode::setPiece(Piece num)
{
  if(num==Space&&Node_Piece!=Space)original_Piece = Node_Piece;
  Node_Piece = num;
  changeTimes++;
}

StepPoint每一步的點,用于存每一步落子的地方和每一步悔棋的地方,還有每一步劫爭的 MapNode,用于實現悔棋和不悔了的功能,共有

int indexX, indexY;    // 下的位置
bool ifUpBeEated;    // 上邊有沒有被吃
bool ifDownBeEated;    // 下邊有沒有被吃
bool ifLeftBeEated;    // 左邊有沒有被吃
bool ifRightBeEated;  // 右邊有沒有被吃
Piece Step_Piece;    // 這一步是什么棋子
MapNode* kozai;      // 這一步劫爭的地方

八個數據,如果上邊有被吃,就把上邊的所有空地找到,設為與這一步棋子相反的棋子,下,左,右亦然,四個方向判斷完后再把這顆子提掉,這就是悔棋的邏輯,不用存下被吃掉的所有點,用四個 bool 值就省去了很多內存。

Map,地圖的所有數據及數據的處理都在 Map 這個類里。

這是個單例模式的類,單例模式就是任何人不能 new 出一個對象,只有這個類自己才能給出自己的模樣,具體寫法為

class A
{
public:
  ~A() {}// 析構函數一定要是公有的
  static A* getInstance()// getInstance 一定要是靜態的
{
    if (p_Ins == nullptr)p_Ins = new A;
    return p_Ins;
  }
private:
  A() {};// 構造函數一定要是私有的
  static A* p_Ins;// 這個不能在構造函數里初始化
};
A* A::p_Ins = nullptr;// 這個不能漏

具體用法你得多多實踐才能理解透徹,例如寫一個回合制對戰游戲,一個英雄一個怪物,一回合輪一個人發動攻擊或者防御什么的,調整每個人的攻擊力,防御力,暴擊率,看看最后是誰贏了這個小項目,你用單例模式試著做一下差不多就能理解了。之后要說的模擬 cocos 就用到了一個單例模式,也是至關重要的單例模式。

Map 共有

MapNode* Entity;// 實體
int sizeX, sizeY;
stack everyStep;
stack everyUndoStep;
function drawPiece;

這六個數據,且這六個數據都是私有的

drawPiece 是個函數指針,由于地圖的不同,drawPiece 函數也會不同,所以具體情況具體賦值,這個 drawPiece 相當于一個虛函數。

為 drawPiece 賦值的接口

void setDrawPiece(function num)
{
  drawPiece = num;
}

Entity 是地圖數據的實體,通過不斷地訪問

MapNode* Node_Left;
MapNode* Node_Right;
MapNode* Node_Up;
MapNode* Node_Down;

這四個節點來到達地圖上的任何一個地方。具體函數為

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 儲存每一步子落在的地方,everyUndoStep 儲存每一步悔棋提掉的子所在的地方,都是 stack 結構來存的。

一開始棋盤是空的,所以通過

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

來初始化 Entity,sizeX,sizeY。

圍棋的流程為一個人下一顆子,判斷這顆子吃了幾顆子,把吃掉的子提掉,判斷能不能下在這里(提掉的子大于一或提掉的子為一且不在 everyStep.top().kozai 的地方,沒有提掉的子且自身的氣不為 0),能下在這里就下在這里,不能下在這里就重新下,下完輪到另一個人。吃掉子,判斷在不在劫爭的位置,判斷自身的氣是否為 0都要判斷氣,所以首先要實現判斷一個區域的氣的功能。

在 Map 里判斷一個區域氣的功能我寫為兩個函數

vector Map::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;
  });
  vectorenclose_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 就是判斷一個區域氣的函數。

判斷一個區域的氣為 0,那就要把這塊區域設為空地,這個需要得到這塊區域所有的點,然后把這塊區域所有點設為空地,實現這個功能需要兩個函數

vector Map::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;
  });
  vectornext_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);
  }
}

能吃子,能提子,然后才能落子,落子的功能比較復雜,函數也比較長,總的來說就是

boolputOnePiece(intindexX,intindexY,Piecenum);

這個函數,如果這個點能落子返回 true,不能落子返回 false。具體實現看 gitee 上的源碼

悔棋功能寫在

bool Undo();

不悔了的功能寫在

bool UnUnDo();

之所以有返回值是因為有可能沒落子就有人按悔棋,或者沒悔過棋就有人按不悔了,返回的 bool 值是悔棋和不悔了是否成功。

代碼沒什么好說的,看源碼就是了,有點長。

點目功能寫在

double getMesh(Piece num);

里,有點長,看源碼去。

至此圍棋這個游戲的邏輯已經全部實現了,接著就是界面的切換

2、SimulationCocos(模擬 Cocos)

模擬 Cocos 有三個模塊,Menu,Scene,Director

Menu 菜單,用于保存每個按鈕的類,每個場景里只有一個菜單,菜單里有 MenuItem (菜單項)

MenuItem菜單項,是一個雙向鏈表,每個菜單里只有一個 MenuItem 鏈表,每個 MenuItem 里包含一個 Button

Button包含三個函數指針

function ResponseFunction;  // 響應
function Call_Back;      // 回調
function Restore;      // 恢復

和一個 BoundingBox 類。

BoundingBox邊框,包含

int size_width, size_height;  // 尺寸
MapPoint Place;          // 左上角位置

三個數據,判斷某個點是否在 BoundingBox 里面調用

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

當一個場景里發生了點擊反應,只需在場景的 Menu 里調用

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

就能判斷是否按到了某個按鈕以及得到那個按鈕的 MenuItem 值,然后調用 MenuItem 的按鈕的 ResponseFunc

當點擊反應結束時調用響應中的按鈕的 Restore 然后判斷鼠標所在的位置還在不在按鈕里面,在的話調用按鈕的 Call_Back 函數,函數里面傳的參是按鈕的邊框,用于繪制按鈕。

Scene場景,繼承自 GameNode 類,

GameNode是一個雙向鏈表,有

virtual bool initMySelf() { return true; }
virtual bool operation() { return true; }
virtual void EndOperation(){}

三個虛函數,operation 是場景運行時的函數,EndOperation 是令場景結束運行的函數,initMySelf 是初始化場景的函數

同時還有

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

判斷某個場景是否和自己有血緣關系。有血緣關系返回 true,無血緣關系返回 false

在 Scene 里有

function Operat_Func;

這個函數指針,也算是個虛函數,交由子類實現,子類必須實現這個函數指針,不然一定會報錯,所以也可以稱作不會報錯的純虛函數吧。

還有

bool ifExit;

是否退出場景的判斷

在 Scene 里實現了

bool Scene::operation()
{
  ifExit = false;
  while (true)
  {
    Operat_Func();
    if (ifExit)break;
  }
  return true;
}
void EndOperation() { ifExit = true; };

這兩個函數,operation 里面真正的精華是 Operat_Func(); 這個函數,這個函數交由 Scene 的子類實現。Scene 的子類可以通過調用 this->EndOperation(); 這個函數退出場景。

Director,單例模式,程序運行的核心,每個 Scene 都在 Director 里運行。只有兩個數據

bool ifExit;       // 是否退出的判斷
GameNode* IsRunning;  // 當前運行的場景

Director 里主要通過兩個函數來實現 Scene 的運行和場景的切換

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())// 場景一律在這個判斷里運行,退出場景時進入判斷
    {
      if(!IsRunning->ifInRace(temp))// 此時 IsRunning 已經通過 Director::getInstance()->RunWithScene(...); 改變了自己
      delete temp;
      temp = IsRunning;
    }
    if (ifExit)break;
  }
}

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 = new GameScene;
scene->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

以上就是圍棋的所有邏輯了,至于代碼部分,很長,邏輯都有了就剩搬磚把大樓蓋起來,看不下去我的源碼也可以根據我的描述寫一份自己的了,我相信我描述的夠清楚了。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 游戲
    +關注

    關注

    2

    文章

    750

    瀏覽量

    26364
  • 編程
    +關注

    關注

    88

    文章

    3637

    瀏覽量

    93986
  • 函數
    +關注

    關注

    3

    文章

    4346

    瀏覽量

    62977
  • C++
    C++
    +關注

    關注

    22

    文章

    2114

    瀏覽量

    73858

原文標題:【項目實戰】C/C++語言帶你實現:圍棋游戲!詳細邏輯+核心源碼

文章出處:【微信號:cyuyanxuexi,微信公眾號:C語言編程學習基地】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    C語言實現面向對象的方式 C++中的class的運行原理

    這里主要介紹下在C語言中是如何實現的面向對象。知道了C語言實現面向對象的方式,再聯想下,C++
    發表于 10-21 09:00 ?1172次閱讀

    C語言實現:見縫插針游戲!代碼思路+源碼分享

    見縫插圓我們昨天已經用C語言實現了,今天將實現一個見縫插針的游戲
    發表于 12-05 11:02 ?792次閱讀

    C++語言實現火車排序功能.doc

    C++語言實現火車排序功能.doc
    發表于 08-05 22:01

    如何用C語言實現一款猜數字游戲

    如何用C語言實現一款猜數字游戲
    發表于 01-06 07:10

    C++程序設計語言實驗指導書

    C++程序設計語言實驗指導書書說明:第一章 C++實驗基礎知識...61.1 C++語言實驗環境配置.61.2 Visual
    發表于 09-20 22:20 ?45次下載

    DSP算法的c語言實現

    DSP算法的c語言實現,又需要的朋友下來看看。
    發表于 05-09 10:59 ?0次下載

    PID控制算法的C語言實現(完整版)

    PID控制算法的C語言實現一 PID算法原理
    發表于 11-05 15:45 ?0次下載

    C++語言實現火車排序功能

    C++語言實現火車排序功能
    發表于 01-05 11:27 ?2次下載

    4個重要算法C語言實現源代碼

    4個重要算法C語言實現源代碼
    發表于 06-10 08:00 ?12次下載

    如何使用C語言實現一個比較簡單的猜數游戲的程序免費下載

    本文檔的主要內容詳細介紹的是如何使用C語言實現一個比較簡單的猜數游戲的程序免費下載
    發表于 04-24 18:31 ?0次下載
    如何使用<b class='flag-5'>C</b><b class='flag-5'>語言實現</b>一個比較簡單的猜數<b class='flag-5'>游戲</b>的程序免費下載

    使用C++語言實現的解題的實例說明

    本文檔的主要內容詳細介紹的是使用C++語言實現的解題的實例說明。
    發表于 04-21 11:50 ?6次下載
    使用<b class='flag-5'>C++</b><b class='flag-5'>語言實現</b>的解題的實例說明

    累加校驗和C語言實現

    累加校驗和C語言實現
    發表于 11-29 18:06 ?10次下載
    累加校驗和<b class='flag-5'>C</b><b class='flag-5'>語言實現</b>

    怎么用C語言實現多態

    這里我想主要介紹下在C語言中是如何實現的面向對象。知道了C語言實現面向對象的方式,我們再聯想下,C++
    的頭像 發表于 10-12 09:12 ?2104次閱讀

    C語言實現《別碰白塊》小游戲!全部代碼+思路注釋

    今天我們將用C語言實現一個小球跳躍躲避方塊的游戲
    的頭像 發表于 12-08 09:06 ?1411次閱讀

    使用C語言實現的CRC計算單元的例子

    使用C語言實現的CRC計算單元的例子
    的頭像 發表于 05-16 16:16 ?1076次閱讀
    大发888娱乐城高手| 太阳城百家乐官网坡解| 缅甸百家乐官网视频| 六合彩150期| 汉百家乐官网春| 百家乐分析| 海立方百家乐客户端| 忻城县| 百盛百家乐软件| 海立方百家乐官网海立方| 互联网百家乐的玩法技巧和规则| 七乐娱乐城| 玩百家乐秘诀| 百家乐官网优博娱乐城| 免费百家乐计划软件| 百家乐官网路单走势图| 大发888游戏平台df888| 杨公24山择日| 永利百家乐官网游戏| 真人百家乐软件云南景| 百家乐官网赌场破解方法| 真人娱乐场注册送现金| 百家乐百家乐视频| 玩百家乐官网如何看路| 威尼斯人娱乐| 百家乐官网平注法到6568| 大发888易付168 充值| 属鼠跟属虎做生意| 百家乐官网风云人物| 威尼斯人娱乐城首选大丰收| 速博百家乐官网的玩法技巧和规则 | 百家乐筹码真伪| 百家乐官网国际娱乐场| 澳博足球| 百家乐长胜攻略| 百家乐官网永利娱乐网| 德州扑克 大小| 赌博百家乐探讨| 百家乐官网算点子打法攻略| 娱乐城棋牌| 威尼斯人娱乐场注册|