Cocos2d-x制作跳棋第三步:棋子动作实现及AI算法思想

时间:2022-05-18 09:07:01

                 

           上周跟大家分享了Cocos2d-x跳棋制作如何建立工程和界面编写,今天来      跟大家分享一下如何实现棋子相关动作及AI思想。

           

         在跳棋中棋子有两种动作:移动、跳跃。


             在工程建立时我们便为棋子建立了类Chess,我们只需要在类中声明相应的动作方法并在方法中          对动作进行封装即可。


             棋子移动动作比较简单,在2d-x中有相应的Action实现。代码如下:

<span style="font-size:14px;">/****************
函数名:moveto
参数:x(移动目标的x坐标) y(移动目标的y坐标)
返回值:无
作用:将棋子移动到指定坐标
****************/
MoveTo* Chess::moveto(int x,int y)
{
this->x = x; //将x坐标存到Chess.x中
this->y = y; //将y坐标存到Chess.y中
ActionInterval *moveto = MoveTo::create(0.3,Point(332+x*tmp,166 + 44.5*y*(sqrt(3)/2))); //定义MoveTo对象设置移动动作
return moveto;
}</span>
                      在移动方法实现中我们用到MoveTo来实现棋子的移动动作,并且在移动完成后将Chess的坐标          x、y更改。

             MoveTo中第一个参数表示动作完成时间,第二个参数为移动目标位置坐标。


             棋子跳跃动作的实现比较麻烦,我们先把跳跃动作分成两个部分,第一部分是棋子的跳起及落        下,第二部分是棋子移动到指定位置,这两部分是同时进行的,在这里我们用的Spawn对象,Spawn是        组合动作中的一种,它的参数中是动作对象表示参数中的动作对象时同时执行的。其次第一部分棋子跳        起和落下也是两个动作,用ScaleBy来实现棋子的放缩,然后再用Sequence对象实现棋子跳起落下的顺        序实现,在Sequence的参数中要有先后顺序(先放大后缩小)。代码如下:

<span style="font-size:14px;">/****************
函数名:jumpto
参数:x(移动目标的x坐标) y(移动目标的y坐标)
返回值:无
作用:将棋子跳跃到执行坐标
****************/
Spawn* Chess::jumpto(int x,int y)
{
this->x = x; //将x坐标存到Chess.x中
this->y = y; //将y坐标存到Chess.y中
this->setZOrder(2); <span style="white-space:pre"> </span>//设置棋子优先级为2
ActionInterval *scaleby = ScaleBy::create(0.3,1.2); //定义棋子缩放比例和执行时间
Sequence * seq = Sequence::create(scaleby,scaleby->reverse(),CallFunc::create(this,<span style="color:#ff0000;">callfunc_selector(Chess::setOrderTo_0))</span>,NULL); <span style="white-space:pre"> </span>//包装组合动作顺序执行棋子的放大和回缩
ActionInterval *moveto = MoveTo::create(0.6,Point(332+x*tmp,166 + 44.5*y*(sqrt(3)/2))); //定义移动动作和执行时间
Spawn * spa = Spawn::create(seq,moveto,NULL); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//包装组合动作同时执行棋子放缩和移动,设置动作执行完成的回调函数setOrderTo_0()
return spa;
}</span>
 

/****************
函数名:setOrderTo_0
参数:无
返回值:无
作用:棋子执行跳跃动作的回调函数
****************/
void Chess::setOrderTo_0()
{
this->setZOrder(0); //设置棋子优先级为0
}

                 
                       在棋子跳跃过程中可能遇到棋子并没有越过所隔棋子,那是因为两个棋子在图层中的优先级不         同,所以我们在组合动作Sequence用CallFunc加入回调函数并在开始用setZOrder()提高跳跃棋子的         优先级,跳跃后在还原棋子的优先级。



             棋子相应动作已经封装好了,接下来看如何在棋盘上实现了。


             首先要建立一个二维数组来保存棋盘中所有棋子的状态。代码如下:

<span style="font-size:14px;">int lay1[17][25] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,
0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,
-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,
-1,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,
-1,-1,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,-1,
-1,-1,-1,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,-1,-1,
-1,-1,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,-1,
-1,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,
-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1,
0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,-1, 0,
-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};</span>

                        数组中0表示棋格未落棋子,-1表示不能在此落子。上文棋盘初始化过程中已经对棋盘进行修          改,相应棋格位置会改成棋子颜色所代表的的状态。


             棋盘已经搞定接下来为棋盘添加触摸事件来让棋子产生动作。为棋盘添加触摸事件要在图层的          init函数中添加,代码如下:

<span style="font-size:14px;">	//添加触摸事件
auto listener = EventListenerTouchOneByOne::create(); //创建触摸侦听对象
//定义监听对象回调函数
listener->onTouchBegan = CC_CALLBACK_2(Game::onTouchBegan,this);
//在事件分发器中注册
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
</span>
                       在onTouchBegin函数中的代码如下:

<span style="font-size:12px;">/****************
函数名:onTouchBegin
参数:t,e
返回值:bool
作用:执行触摸开始时的动作
****************/
bool Game::onTouchBegan(Touch *t,Event *e)
{
int x = (t->getLocation().x - 332)/(tmp/2); <span style="white-space:pre"> </span>//获得触摸x坐标并处理后赋值给int x
int y = (t->getLocation().y - 166)/(44.5*(sqrt(3)/4)); //获得触摸y坐标并处理后赋值给ini y
x = x > 0 ? (x+1)/2 : (x-1)/2; //将x处理成棋盘x坐标
y = y > 0 ? (y+1)/2 : (y-1)/2; //将y处理成棋盘y坐标
if(flag == 0) <span style="white-space:pre"> </span>//如果flag为0则进行棋子选中的操作
{
for(i = 0;i < chess1.size();i++)
{
if(chess2.at(i)->x == x && chess2.at(i)->y == y && flag_move) //判断是否选中chess2中的棋子
{
j = 2; //设置j为2表示为chess2
chess2.at(i)->scale(); //将选中的棋子放大
step2 = 0; //初始化step2
ShowJump(x,y,0); //显示选中棋子可以跳到的位置坐标
ShowMove(x,y); <span style="white-space:pre"> </span>//显示选中棋子可以移动到的位置坐标
break;
}
}
if(i != chess1.size()) //判断是否有选中棋子
{
flag++; //标志flag加一
c_x = x; //将选中棋子坐标x赋值给c_x
c_y = y; //将选中棋子坐标y赋值给c_y
flag_move = -1; //设置flag_move为-1
flag_jump = 1; //表示已选中棋子的标志
flag_moved = 2;
}
}else if(flag == 1){ //如果flag为1则表示已选中棋子开始执行移动功能
if(lay[y+4][x+8] == 0) //判断触摸点是否有棋子
{
if(((abs(x-c_x) == 2 && y == c_y) || (abs(x-c_x) == 1 && abs(y-c_y) == 1)) && flag_jump == 1) //判断触摸到的位置坐标是否可以移动
{
chess2.at(i)->runAction(chess2.at(i)->moveto(x,y)); //棋子执行移动动作到(x,y)位置
lay[y+4][x+8] = 2; //将该位置标识设置为2表示棋子到达该位置
lay[c_y+4][c_x+8] = 0; //将原先位置设置为0表示棋子已经移动
c_x = x;
c_y = y;
flag_moved = 1; //将flag_moved设置为1表示棋子已经移动
flag_jump = 0; <span style="white-space:pre"> </span>//将flag_jump设置为0表示棋子不能跳跃
}else if(flag_jump) //判断棋子是否可跳
{
flag = 11; //设置flag为11表示在棋子跳跃期间不能触发触摸事件
step1 = 0; //初始化step1
step = 0; //初始化step
Jumpto(chess2.at(i)->x,chess2.at(i)->y,x,y,0); //执行算法判断是否可以跳到该位置
moveto(); //执行跳跃
}
}else if(x == c_x && y == c_y) //判断触摸位置是否为选中棋子的位置
{

chess2.at(i)->reverse(); //将棋子缩小到原先大小
invisible(); //隐藏所有标识
isWin(); //判断输赢
if(flag_moved == 2) //判断棋子是否已经移动
{
flag = 0; <span style="white-space:pre"> </span>//棋子未移动将flag设置为0重新选择棋子
flag_move = -flag_move; //还原flag_move
}else
{
flag = 10; //棋子已经移动设置flag为10表示在AI移动期间不能触发触摸事件
jump.clear(); //清空容器jump中的数据
AI(1); //执行chess1的AI
if(chess3.size() > 0) //判断是否为多人模式
{
AI(3); //执行chess3的AI
AI(4); //执行chess4的AI
AI(5); //执行chess5的AI
AI(6); //执行chess6的AI
}
num = 0; //初始化num
runMyAction(); //执行动作
}
}
}
return true;
}</span>
               
                      代码中首先获得触摸点的x坐标和y坐标再进行加工转化成相应在棋盘上的坐标,然后把这些坐标        带入到二维lay数组中判断触摸到位置是否为Chess2棋子,若是则将棋子放大表示已选中该棋子并将          flag设置为1,执行ShowJump和ShwoMove函数显示选中棋子可以到达的位置,然后再选择位置判断该        位置与选中棋子位置的关系若可以移动则执行moveto动作,若需要跳动则执行Jumpto函数直接跳到相        应位置。



                                    最后分享一下跳棋AI的思想:

                      跳棋的目的就是将自己的所有棋子移动到正对面阵营为胜利,在做AI的过程中我们要搜索所有的               棋子来找到最优路径,这就和迷宫中寻找目标点的最短路径一样,我们要运用的搜索的方法即A*算法,

            在跳棋AI算法中遍历一个棋子六个方向是否有可以跳的位置,若有则以该位置为中心判断它五个方向是               否有可以跳的位置并要判断该位置是否已经遍历过以此递归并将遍历到的位置坐标保存到数组并在每一               次递归中进行对比选择出最好的 路径保存起来,至于什么样的条件是最好的就要看自己的想法了~~