我们在选中棋子之后理所当然就是要移动棋子,要移动棋子我们不知需要知道选中的棋子点信息,还需要知道选中棋子后玩家下一次点击屏幕的触摸点信息。知道两点的信息之后我们就可以开始写棋子的移动规则了,这里将函数拆分得细一点,避免一个函数一大串代码,这样不容易管理还容易出错。
首先我们的移动操作应该是在触摸事件函数中实现,而且是在选中棋子后才可以移动,这样我们就需要在触摸事件的回调函数加一个判断,区分开选中和移动两个功能。
void LayerGameMain::touchEndedCallBack(Touch *pTouch, Event *pEvent)
{
if (_selectedId == -1)//没选中棋子才能选棋
{
selectStone(pTouch);
}
else//选中棋子后执行移动棋子
{
moveStone(pTouch);
}
}
接着我们就需要开始写移动棋子函数,移动棋子需要对不同棋子制定不同规则,我们给每种棋子写一个规则判断函数,然后通过switch来筛选需要使用哪种规则,最后再调用一个移动函数实现棋子移动效果。这里先写出车、炮的移动规则(他们之间存在相似点)。
获取棋子与目标点间的棋子数:
int LayerGameMain::getStoneCount(int row1, int col1, int row2, int col2)
{
int ret = 0;//记录棋子的个数
if (row1 != row2 && col1 != col2) return -1;//行列都不同,没有直线
if (row1 == row2 && col1 == col2) return -1;//行列都相同,两点相同,不需要移动
if (row1 == row2)//列相同
{
int mincol = col1 < col2 ? col1 : col2;//获取两点间较小的行
int maxcol = col1 > col2 ? col1 : col2;//获取两点间较大的行
for (int col = mincol+1; col < maxcol; col++)//遍历两点间所有点
{
if (getStoneIdFromRowCol(row1, col) > 0)//如果存在棋子,ret棋子计数器+1
{
++ret;
}
}
}
else if(col1 == col2)
{
int minrow = row1 < row2 ? row1 : row2;//获取两点间较小的列
int maxrow = row1 > row2 ? row1 : row2;//获取两点间较大的列
for (int row = minrow + 1; row < maxrow; row++)//遍历两点间所有点
{
if (getStoneIdFromRowCol(row, col1) > 0)//如果存在棋子,ret棋子计数器+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::PAO:
return canMovePao(moveid, row, col, killid);
break;
/*余下规则都在这*/
default:
break;
}
return false;
}
最后给车和炮写移动规则:
bool LayerGameMain::canMoveChe(int moveid, int row, int col)
{
//车只有在两点间不存在棋子时才能走
Stone *che = getStoneFromArrById(moveid);
if (getStoneCount(che->_row, che->_col, row, col)>0)
{
false;
}
return true;
}
bool LayerGameMain::canMovePao(int moveid, int row, int col, int killid)
{
//炮可以行走只有两种情况:1.两点间没有棋子。2.两点间存在一个棋子,并且目的点也存在棋子。
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;
}
其他棋子的规则就不多写了,看看代码很容易看懂,这里直接贴出所有规则实现后的LayerGameMain代码。
LayerGameMain.h
#ifndef __LAYERGAMEMAIN_H__
#define __LAYERGAMEMAIN_H__
#include "cocos2d.h"
#include "Plate.h"
#include "Stone.h"
/*
cocos2d-x3.14.1版本将大部分类的前缀CC去掉了,不过有部分还是兼容,如果用旧的命名创建对应的类会有警告提示/错误提示
*/
USING_NS_CC;
class LayerGameMain : public Layer
{
public:
static Scene* createScene();
virtual bool init();
CREATE_FUNC(LayerGameMain);
//添加棋盘和棋子
void addPlate();//用于在LayerGameMain上添加棋盘层/精灵
void addStones();//用于在LayerGameMain上添加棋子层/精灵
//触摸事件回调函数
bool touchBeganCallBack(Touch *pTouch, Event *pEvent);
void touchEndedCallBack(Touch *pTouch, Event *pEvent);
//选中棋子相关
Sprite *selectBox;//选中款精灵
static int _selectedId;//标记当前选中的棋子id,没有点中为-1
bool Screen2Plate(Point &point, int &row, int &col);//循环棋盘上所有点,通过Plate2Screen获取到棋子中心点,用引用输出中心点
Point Plate2Screen(int row, int col);//获取棋子中心点返回到世界坐标给Screen2Plate
void selectStone(Touch *pTouch);//选中棋子,将选中框精灵移动到选中的棋子上
//获取棋子相关
__Array *arrStone;//用于存放棋子
Stone* getStoneFromArrById(int id);//通过棋子id从arrStone获取对应棋子
int getStoneIdFromRowCol(int row, int col);//通过行列获取棋子id
//棋子移动相关
void moveStone(Touch *pTouch);
int getStoneCount(int row1, int col1, int row2, int col2);
bool canMove(int moveid, int row, int col, int killid);//筛选移动规则
//moveid为当前想要移动棋子的id,killid移动的目的点(-1为没有棋子),row和col为目的点的行列
//炮、兵、将、车移动规则都有点相似
bool canMoveChe(int moveid, int row, int col);//车移动规则
bool canMovePao(int moveid, int row, int col, int killid);//炮移动规则
bool canMoveBing(int moveid, int row, int col);//兵移动规则
bool canMoveJiang(int moveid, int row, int col, int killid);//将移动规则
bool canMoveMa(int moveid, int row, int col);//马移动规则
bool canMoveXiang(int moveid, int row, int col);//象移动规则
bool canMoveShi(int moveid, int row, int col);//士移动规则
};
#endif // !__LAYERGAMEMAIN_H__
LayerGameMain.cpp
#include "LayerGameMain.h"
int LayerGameMain::_selectedId = -1;
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();//计数器+1,否则会被回收
selectBox = Sprite::create("selected.png");
selectBox->setVisible(false);
selectBox->setZOrder(1000);//设置显示优先级
this->addChild(selectBox);
this->addPlate();
this->addStones();
//cocos2d-x3.14.1触摸事件,创建监听
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);//将棋子添加进数组
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))//获取点距的平方并比较,如果小于半径平方则为能选中中心点上的棋子
{
return true;
}
}
}
return false;
}
Point LayerGameMain::Plate2Screen(int row, int col)//该函数与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)
{
ret = stone->_id;
return ret;
}
}
return ret;
}
void LayerGameMain::selectStone(Touch *pTouch)
{
Point point = pTouch->getLocation();
int row, col;
if (!Screen2Plate(point, row, col))//点中范围不在棋盘上直接返回
{
selectBox->setVisible(false);
return;
}
int clickedId = getStoneIdFromRowCol(row, col);
if (clickedId < 0)//没选中棋子直接返回
{
selectBox->setVisible(false);
return;
}
Stone *clickedStone = getStoneFromArrById(clickedId);
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))//获取触点的行列
{
return;
}
int clickedId = getStoneIdFromRowCol(row, col);//获取触点上的棋子id,没有棋子为-1
if (!canMove(_selectedId, row, col, clickedId))
{
return;
}
Point pt = Plate2Screen(row, col);
MoveTo *to = MoveTo::create(0.3, pt);
Stone *selectedStone = getStoneFromArrById(_selectedId);
selectedStone->runAction(to);
selectedStone->_row = row;
selectedStone->_col = col;
}
int LayerGameMain::getStoneCount(int row1, int col1, int row2, int col2)
{
int ret = 0;//记录棋子的个数
if (row1 != row2 && col1 != col2) return -1;//行列都不同,没有直线
if (row1 == row2 && col1 == col2) return -1;//行列都相同,两点相同,不需要移动
if (row1 == row2)//列相同
{
int mincol = col1 < col2 ? col1 : col2;//获取两点间较小的行
int maxcol = col1 > col2 ? col1 : col2;//获取两点间较大的行
for (int col = mincol+1; col < maxcol; col++)//遍历两点间所有点
{
if (getStoneIdFromRowCol(row1, col) > 0)//如果存在棋子,ret棋子计数器+1
{
++ret;
}
}
}
else if(col1 == col2)
{
int minrow = row1 < row2 ? row1 : row2;//获取两点间较小的列
int maxrow = row1 > row2 ? row1 : row2;//获取两点间较大的列
for (int row = minrow + 1; row < maxrow; row++)//遍历两点间所有点
{
if (getStoneIdFromRowCol(row, col1) > 0)//如果存在棋子,ret棋子计数器+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)
{
//车只有在两点间不存在棋子时才能走
Stone *che = getStoneFromArrById(moveid);
if (getStoneCount(che->_row, che->_col, row, col)>0)
{
false;
}
return true;
}
bool LayerGameMain::canMovePao(int moveid, int row, int col, int killid)
{
//炮可以行走只有两种情况:1.两点间没有棋子。2.两点间存在一个棋子,并且目的点也存在棋子。
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)
{
//兵行走规则:1.只能走一格。2.不能回退。3.过河前只能直走
Stone *bing = getStoneFromArrById(moveid);
int dRow = abs(bing->_row - row);
int dCol = abs(bing->_col - col);
int d = dRow * 10 + dCol;//此处类似标记效果,10等价于(1,0),1代表可移动,第一位说明可以左右移动一格,第二位说明可以上下移动一格
if (d != 1 && d != 10)
{
return false;
}
if (bing->_red)
{
//兵不能回退
if (row < bing->_row)
{
return false;
}
//兵过河前不能左右移动
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)
{
//将规则:1.一次只能移动一格。2.不能出九宫格。3.将帅照面时可以直接对杀。
Stone *jiang = getStoneFromArrById(moveid);
//将照面杀
if (killid != -1)
{
Stone *kill = getStoneFromArrById(killid);
if (kill->_type == Stone::JIANG)
{
return canMoveChe(moveid, row, col);//复用车的移动方法,减少代码量
}
}
int dRow = abs(jiang->_row - row);
int dCol = abs(jiang->_col - col);
int d = dRow * 10 + dCol;//此处类似标记效果,10等价于(1,0),1代表可移动,第一位说明可以左右移动一格,第二位说明可以上下移动一格
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)
{
//马规则:1.马走日。2.卡住马脚不能移动。
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)
{
//蹩脚点
cCol = (col + ma->_col) / 2;
cRow = ma->_row;
}
else
{
cCol = ma->_col;
cRow = (ma->_row + row) / 2;
}
//获取蹩脚点后判断蹩脚点是否有棋子
if (getStoneIdFromRowCol(cRow, cCol) == -1)
{
return true;
}
}
return false;
}
bool LayerGameMain::canMoveXiang(int moveid, int row, int col)
{
//象规则:1.象走田。2.被卡象眼不能移动。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)//卡主香烟不能移动
{
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)
{
//士规则: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;
}
//不能两侧走出九宫
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;
}