悔棋需要用到備忘錄模式。大致思路是:我們先創(chuàng)建一個(gè)Step結(jié)構(gòu)體悬秉,這個(gè)結(jié)構(gòu)體記錄著兩個(gè)棋子的id(一個(gè)是移動(dòng)棋子,一個(gè)是被殺棋子)冰蘑、移動(dòng)前行列和泌,移動(dòng)后行列。然后創(chuàng)建一個(gè)Step鏈表存放所有Step祠肥,在調(diào)用移動(dòng)棋子函數(shù)moveStone時(shí)把當(dāng)前棋子狀態(tài)放進(jìn)_steps中武氓,當(dāng)玩家點(diǎn)擊菜單按鈕的悔棋按鈕時(shí)調(diào)用悔棋按鈕的回調(diào)函數(shù)執(zhí)行悔棋操作。在完成這一篇內(nèi)容后搪柑,下一篇我們就開始寫人工智能聋丝,寫完AI類,簡(jiǎn)單的cocos2dx-3.14的中國象棋也就基本算完成了工碾。
這里把LayerGameMain需要添加的成員變量和成員函數(shù)都列出來
//定義一個(gè)結(jié)構(gòu)體記錄每一步信息
struct Step
{
int moveid;//移動(dòng)棋子的id
int killid;//被殺棋子的id
int rowFrom;//移動(dòng)前的行
int colFrom;//移動(dòng)前的列
int rowTo;//移動(dòng)后的行
int colTo;//移動(dòng)后的列
};
//悔棋相關(guān)
list<Step *> _steps;//存放整局棋譜
void recordStep(int moveid, int killid, int rowFrom, int colFrom, int rowTo, int colTo);//記錄悔棋相關(guān)參數(shù)
void addCtrlMenu();//添加悔棋等菜單
void regretItemCallback(Ref *obj);//悔棋菜單回調(diào)函數(shù)
void regret1Step();//悔棋1步執(zhí)行函數(shù)
這里就不大篇幅地解釋了弱睦,要看懂也很容易,先從moveStone找到addCtrlMenu函數(shù)然后查看一下addCtrlMenu函數(shù)基本就能看懂渊额。這里直接上所有代碼况木。
LayerGameMain.h
#ifndef __LAYERGAMEMAIN_H__
#define __LAYERGAMEMAIN_H__
#include "cocos2d.h"
#include "Plate.h"
#include "Stone.h"
#include <list>
#include <vector>
using namespace std;
/*
cocos2d-x3.14.1版本將大部分類的前綴CC去掉了,不過有部分還是兼容旬迹,如果用舊的命名創(chuàng)建對(duì)應(yīng)的類會(huì)有警告提示/錯(cuò)誤提示
*/
USING_NS_CC;
//定義一個(gè)結(jié)構(gòu)體記錄每一步信息
struct Step
{
int moveid;//移動(dòng)棋子的id
int killid;//被殺棋子的id
int rowFrom;//移動(dòng)前的行
int colFrom;//移動(dòng)前的列
int rowTo;//移動(dòng)后的行
int colTo;//移動(dòng)后的列
};
class LayerGameMain : public Layer
{
public:
static Scene* createScene();
virtual bool init();
CREATE_FUNC(LayerGameMain);
//添加棋盤和棋子
void addPlate();//用于在LayerGameMain上添加棋盤層/精靈
void addStones();//用于在LayerGameMain上添加棋子層/精靈
//觸摸事件回調(diào)函數(shù)
bool touchBeganCallBack(Touch *pTouch, Event *pEvent);
void touchEndedCallBack(Touch *pTouch, Event *pEvent);
//選中棋子相關(guān)
Sprite *selectBox;//選中款精靈
static int _selectedId;//標(biāo)記當(dāng)前選中的棋子id火惊,沒有點(diǎn)中為-1
bool Screen2Plate(Point &point, int &row, int &col);//循環(huán)棋盤上所有點(diǎn),通過Plate2Screen獲取到棋子中心點(diǎn)奔垦,用引用輸出中心點(diǎn)
Point Plate2Screen(int row, int col);//獲取棋子中心點(diǎn)返回到世界坐標(biāo)給Screen2Plate
void selectStone(Touch *pTouch);//選中棋子屹耐,將選中框精靈移動(dòng)到選中的棋子上
//獲取棋子相關(guān)
__Array *arrStone;//用于存放棋子
Stone* getStoneFromArrById(int id);//通過棋子id從arrStone獲取對(duì)應(yīng)棋子
int getStoneIdFromRowCol(int row, int col);//通過行列獲取棋子id
//棋子移動(dòng)相關(guān)
static bool _redTurn;
void moveStone(Touch *pTouch);
int getStoneCount(int row1, int col1, int row2, int col2);
bool canMove(int moveid, int row, int col, int killid);//篩選移動(dòng)規(guī)則
//moveid為當(dāng)前想要移動(dòng)棋子的id,killid移動(dòng)的目的點(diǎn)(-1為沒有棋子)椿猎,row和col為目的點(diǎn)的行列
//炮惶岭、兵、將犯眠、車移動(dòng)規(guī)則都有點(diǎn)相似
bool canMoveChe(int moveid, int row, int col);//車移動(dòng)規(guī)則
bool canMovePao(int moveid, int row, int col, int killid);//炮移動(dòng)規(guī)則
bool canMoveBing(int moveid, int row, int col);//兵移動(dòng)規(guī)則
bool canMoveJiang(int moveid, int row, int col, int killid);//將移動(dòng)規(guī)則
bool canMoveMa(int moveid, int row, int col);//馬移動(dòng)規(guī)則
bool canMoveXiang(int moveid, int row, int col);//象移動(dòng)規(guī)則
bool canMoveShi(int moveid, int row, int col);//士移動(dòng)規(guī)則
bool isSameColor(int clickedId);//判斷選中棋子顏色是否相同
//悔棋相關(guān)
list<Step *> _steps;//存放整局棋譜
void recordStep(int moveid, int killid, int rowFrom, int colFrom, int rowTo, int colTo);//記錄悔棋相關(guān)參數(shù)
void addCtrlMenu();//添加悔棋等菜單
void regretItemCallback(Ref *obj);//悔棋菜單回調(diào)函數(shù)
void regret1Step();//悔棋1步執(zhí)行函數(shù)
};
#endif // !__LAYERGAMEMAIN_H__
LayerGameMain.cpp
#include "LayerGameMain.h"
using namespace std;
int LayerGameMain::_selectedId = -1;
bool LayerGameMain::_redTurn = true;
Scene* LayerGameMain::createScene()
{
auto ret = Scene::create();
auto layer = LayerGameMain::create();
ret->addChild(layer);
return ret;
}
bool LayerGameMain::init()
{
if (!Layer::init())
{
return false;
}
arrStone = __Array::create();
arrStone->retain();//計(jì)數(shù)器+1按灶,否則會(huì)被回收
_steps.clear();//初始化steps鏈表
selectBox = Sprite::create("selected.png");
selectBox->setVisible(false);
selectBox->setLocalZOrder(1000);//設(shè)置顯示優(yōu)先級(jí)
this->addChild(selectBox);
this->addPlate();
this->addStones();
addCtrlMenu();
//cocos2d-x3.14.1觸摸事件,創(chuàng)建監(jiān)聽
EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispatcher();
auto listener = EventListenerTouchOneByOne::create();
listener->setEnabled(true);
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(LayerGameMain::touchBeganCallBack, this);
listener->onTouchEnded = CC_CALLBACK_2(LayerGameMain::touchEndedCallBack, this);
eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
return true;
}
void LayerGameMain::addPlate()
{
Plate *plate = Plate::create();
this->addChild(plate);
}
void LayerGameMain::addStones()
{
int i = 0;
Stone *stone;
for (i = 0; i < 32; i++)
{
stone = Stone::create(i);
arrStone->addObject(stone);//將棋子添加進(jìn)數(shù)組
this->addChild(stone);
}
}
Stone* LayerGameMain::getStoneFromArrById(int stoneId)
{
Stone *ret;
ret = nullptr;
if (stoneId > 0)
{
ret = (Stone *)arrStone->getObjectAtIndex(stoneId);
}
return ret;
}
bool LayerGameMain::touchBeganCallBack(Touch *pTouch, Event *pEvent)
{
return true;
}
void LayerGameMain::touchEndedCallBack(Touch *pTouch, Event *pEvent)
{
if (_selectedId == -1)
{
selectStone(pTouch);
}
else
{
moveStone(pTouch);
}
}
bool LayerGameMain::Screen2Plate(Point &point, int &row, int &col)
{
int distance = Stone::_d/2 * Stone::_d/2;//以半徑作為距離
for (row = 0; row <= 9; row++)
{
for (col = 0; col <= 8; col++)
{
Point ptCenter = Plate2Screen(row, col);
if (distance > ptCenter.getDistanceSq(point))//獲取點(diǎn)距的平方并比較筐咧,如果小于半徑平方則為能選中中心點(diǎn)上的棋子
{
return true;
}
}
}
return false;
}
Point LayerGameMain::Plate2Screen(int row, int col)//該函數(shù)與Stone的getPositionFromPlate類似
{
Point ret = Point(Stone::_offx + col*Stone::_d, Stone::_offy + row*Stone::_d);
return ret;
}
int LayerGameMain::getStoneIdFromRowCol(int row, int col)
{
int ret = -1;
Ref *obj;
CCARRAY_FOREACH(arrStone,obj)
{
Stone *stone = (Stone *)obj;
if (stone->_row == row && stone->_col == col && !stone->_isdead)//已死去的棋子不能選中
{
ret = stone->_id;
return ret;
}
}
return ret;
}
void LayerGameMain::selectStone(Touch *pTouch)
{
Point point = pTouch->getLocation();
int row, col;
if (!Screen2Plate(point, row, col))//點(diǎn)中范圍不在棋盤上直接返回
{
selectBox->setVisible(false);
return;
}
int clickedId = getStoneIdFromRowCol(row, col);
if (clickedId < 0)//沒選中棋子直接返回
{
selectBox->setVisible(false);
return;
}
Stone *clickedStone = getStoneFromArrById(clickedId);
if (clickedStone->_red != _redTurn)//如果沒有輪到鸯旁,不能選中噪矛、移動(dòng)
{
return;
}
selectBox->setPosition(Plate2Screen(row, col));
selectBox->setVisible(true);
_selectedId = clickedId;//記錄下棋子id
}
void LayerGameMain::moveStone(Touch *pTouch)
{
Point ptClicked = pTouch->getLocation();
int row, col;
if (!Screen2Plate(ptClicked, row, col))//獲取觸點(diǎn)的行列,如果觸點(diǎn)不在棋盤上直接返回
{
return;
}
int clickedId = getStoneIdFromRowCol(row, col);//獲取觸點(diǎn)上的棋子id铺罢,沒有棋子為-1
//如果點(diǎn)中相同顏色的棋子艇挨,選中框轉(zhuǎn)移到最后選中的棋子上
if (clickedId != -1 && isSameColor(clickedId))
{
Stone *clickedStone = getStoneFromArrById(clickedId);
selectBox->setPosition(Plate2Screen(clickedStone->_row, clickedStone->_col));
_selectedId = clickedId;
return;
}
if (!canMove(_selectedId, row, col, clickedId))
{
return;
}
Stone *selectedStone = getStoneFromArrById(_selectedId);
//悔棋相關(guān),移動(dòng)前記錄各項(xiàng)信息
recordStep(_selectedId, clickedId, selectedStone->_row, selectedStone->_col, row, col);
//移動(dòng)相關(guān)
Point pt = Plate2Screen(row, col);
MoveTo *to = MoveTo::create(0.3, pt);
selectedStone->runAction(to);
selectedStone->_row = row;
selectedStone->_col = col;
if (clickedId != -1)
{
Stone *clickedStone = getStoneFromArrById(clickedId);
clickedStone->setVisible(false);
clickedStone->_isdead = true;
}
//移動(dòng)完畢初始化選中框和選中id
selectBox->setVisible(false);
_selectedId = -1;//移動(dòng)完成后初始化為-1
_redTurn = !_redTurn;
}
int LayerGameMain::getStoneCount(int row1, int col1, int row2, int col2)
{
int ret = 0;//記錄棋子的個(gè)數(shù)
if (row1 != row2 && col1 != col2) return -1;//行列都不同畏铆,沒有直線
if (row1 == row2 && col1 == col2) return -1;//行列都相同雷袋,兩點(diǎn)相同,不需要移動(dòng)
if (row1 == row2)//列相同
{
int mincol = col1 < col2 ? col1 : col2;//獲取兩點(diǎn)間較小的行
int maxcol = col1 > col2 ? col1 : col2;//獲取兩點(diǎn)間較大的行
for (int col = mincol+1; col < maxcol; col++)//遍歷兩點(diǎn)間所有點(diǎn)
{
if (getStoneIdFromRowCol(row1, col) > 0)//如果存在棋子辞居,ret棋子計(jì)數(shù)器+1
{
++ret;
}
}
}
else if(col1 == col2)
{
int minrow = row1 < row2 ? row1 : row2;//獲取兩點(diǎn)間較小的列
int maxrow = row1 > row2 ? row1 : row2;//獲取兩點(diǎn)間較大的列
for (int row = minrow + 1; row < maxrow; row++)//遍歷兩點(diǎn)間所有點(diǎn)
{
if (getStoneIdFromRowCol(row, col1) > 0)//如果存在棋子,ret棋子計(jì)數(shù)器+1
{
++ret;
}
}
}
return ret;
}
bool LayerGameMain::canMove(int moveid, int row, int col, int killid)
{
Stone *stone = getStoneFromArrById(moveid);
switch (stone->_type)
{
case Stone::CHE:
return canMoveChe(moveid, row, col);
break;
case Stone::JIANG:
return canMoveJiang(moveid, row, col, killid);
break;
case Stone::BING:
return canMoveBing(moveid, row, col);
break;
case Stone::PAO:
return canMovePao(moveid, row, col, killid);
break;
case Stone::MA:
return canMoveMa(moveid, row, col);
break;
case Stone::XIANG:
return canMoveXiang(moveid, row, col);
break;
case Stone::SHI:
return canMoveShi(moveid, row, col);
break;
default:
break;
}
return false;
}
bool LayerGameMain::canMoveChe(int moveid, int row, int col)
{
//車只有在兩點(diǎn)間不存在棋子時(shí)才能走
Stone *che = getStoneFromArrById(moveid);
return getStoneCount(che->_row, che->_col, row, col) == 0;
}
bool LayerGameMain::canMovePao(int moveid, int row, int col, int killid)
{
//炮可以行走只有兩種情況:1.兩點(diǎn)間沒有棋子蛋勺。2.兩點(diǎn)間存在一個(gè)棋子瓦灶,并且目的點(diǎn)也存在棋子。
Stone *pao = getStoneFromArrById(moveid);
if (killid == -1)
{
return getStoneCount(pao->_row, pao->_col, row, col) == 0;
}
return getStoneCount(pao->_row, pao->_col, row, col) == 1;
}
bool LayerGameMain::canMoveBing(int moveid, int row, int col)
{
//兵行走規(guī)則:1.只能走一格抱完。2.不能回退贼陶。3.過河前只能直走
Stone *bing = getStoneFromArrById(moveid);
int dRow = abs(bing->_row - row);
int dCol = abs(bing->_col - col);
int d = dRow * 10 + dCol;//此處類似標(biāo)記效果,10等價(jià)于(1巧娱,0)碉怔,1代表可移動(dòng),第一位說明可以左右移動(dòng)一格禁添,第二位說明可以上下移動(dòng)一格
if (d != 1 && d != 10)
{
return false;
}
if (bing->_red)
{
//兵不能回退
if (row < bing->_row)
{
return false;
}
//兵過河前不能左右移動(dòng)
if (bing->_row <= 4 && bing->_row == row)
{
return false;
}
}
else
{
if (row > bing->_row)
{
return false;
}
if (bing->_row >= 5 && bing->_row == row)
{
return false;
}
}
return true;
}
bool LayerGameMain::canMoveJiang(int moveid, int row, int col, int killid)
{
//將規(guī)則:1.一次只能移動(dòng)一格撮胧。2.不能出九宮格。3.將帥照面時(shí)可以直接對(duì)殺老翘。
Stone *jiang = getStoneFromArrById(moveid);
//將照面殺
if (killid != -1)
{
Stone *kill = getStoneFromArrById(killid);
if (kill->_type == Stone::JIANG)
{
return canMoveChe(moveid, row, col);//復(fù)用車的移動(dòng)方法芹啥,減少代碼量
}
}
int dRow = abs(jiang->_row - row);
int dCol = abs(jiang->_col - col);
int d = dRow * 10 + dCol;//此處類似標(biāo)記效果,10等價(jià)于(1铺峭,0)墓怀,1代表可移動(dòng),第一位說明可以左右移動(dòng)一格卫键,第二位說明可以上下移動(dòng)一格
if (d != 1 && d != 10)
{
return false;
}
//將不能左右出九宮
if (col < 3 || col > 5)
{
return false;
}
//將不能上下出九宮
if (jiang->_red)
{
if (row > 2 || row <0)
{
return false;
}
}
else
{
if (row < 7 || row >9)
{
return false;
}
}
return true;
}
bool LayerGameMain::canMoveMa(int moveid, int row, int col)
{
//馬規(guī)則:1.馬走日傀履。2.卡住馬腳不能移動(dòng)。
Stone *ma = getStoneFromArrById(moveid);
int dRow = abs(ma->_row - row);
int dCol = abs(ma->_col - col);
int d = dRow * 10 + dCol;
//12為橫日莉炉,21為豎日,類似于(1钓账,2)(2,1)做法
if (d == 12 || d == 21)
{
int cRow, cCol;
if (d == 12)
{
//蹩腳點(diǎn)
cCol = (col + ma->_col) / 2;
cRow = ma->_row;
}
else
{
cCol = ma->_col;
cRow = (ma->_row + row) / 2;
}
//獲取蹩腳點(diǎn)后判斷蹩腳點(diǎn)是否有棋子
if (getStoneIdFromRowCol(cRow, cCol) == -1)
{
return true;
}
}
return false;
}
bool LayerGameMain::canMoveXiang(int moveid, int row, int col)
{
//象規(guī)則:1.象走田呢袱。2.被卡象眼不能移動(dòng)官扣。3.象不能過河
Stone *xiang = getStoneFromArrById(moveid);
int dRow = abs(xiang->_row - row);
int dCol = abs(xiang->_col - col);
int d = dRow * 10 + dCol;
if (d != 22)
{
return false;
}
int cRow, cCol;
cRow = (row + xiang->_row) / 2;
cCol = (col + xiang->_col) / 2;
if (getStoneIdFromRowCol(cRow, cCol) != -1)//卡主香煙不能移動(dòng)
{
return false;
}
if (xiang->_red)
{
if (row > 4) return false;
}
else
{
if (row < 5) return false;
}
return true;
}
bool LayerGameMain::canMoveShi(int moveid, int row, int col)
{
//士規(guī)則:1.一次斜著走一格。2.不能出九宮格
Stone *shi = getStoneFromArrById(moveid);
int dRow = abs(shi->_row - row);
int dCol = abs(shi->_col - col);
int d = dRow * 10 + dCol;
//只能斜線走
if (d != 11)
{
return false;
}
//不能兩側(cè)走出九宮
if (col<3 || col>5)
{
return false;
}
//不能上下走出九宮
if (shi->_red)
{
if (row>2 || row<0)
{
return false;
}
}
else
{
if (row<7 || row>9)
{
return false;
}
}
return true;
}
bool LayerGameMain::isSameColor(int clickedId)
{
Stone *clickedStone = getStoneFromArrById(clickedId);
Stone *selectedStone = getStoneFromArrById(_selectedId);
return selectedStone->_red == clickedStone->_red;
}
void LayerGameMain::recordStep(int moveid, int killid, int rowFrom, int colFrom, int rowTo, int colTo)
{
Step *step = new Step();
step->moveid = moveid;
step->killid = killid;
step->rowFrom = rowFrom;
step->colFrom = colFrom;
step->rowTo = rowTo;
step->colTo = colTo;
_steps.push_back(step);
}
void LayerGameMain::addCtrlMenu()
{
MenuItemImage *regretItem = MenuItemImage::create("regret.png",
"regretselected.png",
this,
menu_selector(LayerGameMain::regretItemCallback));
Menu *menu = Menu::create();
menu->addChild(regretItem);
this->addChild(menu);
menu->setPosition(Point(400, 160));
}
void LayerGameMain::regretItemCallback(Ref *obj)
{
if (_steps.size() <= 1)//只走了一步不能悔棋
{
return;
}
for (int i = 0; i < 2; i++)//悔棋兩步
{
regret1Step();
}
return;
}
void LayerGameMain::regret1Step()
{
Step *step = *_steps.rbegin();
Stone *stone = getStoneFromArrById(step->moveid);
stone->_row = step->rowFrom;
stone->_col = step->colFrom;
stone->setPosition(Plate2Screen(stone->_row, stone->_col));
if (step->killid != -1)//如果目標(biāo)點(diǎn)有棋子羞福,將棋子回歸為原來狀態(tài)
{
stone = getStoneFromArrById(step->killid);
stone->_isdead = false;
stone->setVisible(true);
}
_redTurn = !_redTurn;//執(zhí)棋權(quán)歸還給對(duì)方
delete step;
_steps.pop_back();
//防止選中棋子情況下悔棋
selectBox->setVisible(false);
_selectedId = -1;
return;
}