有了前两篇的象棋棋盘和象棋棋子的铺垫,那么基本的象棋的雏形就出来,接下来要完善各个棋子行走的规则就可以让棋子移动起来,在贴代码之前,让我们回想一下象棋棋子的行走规则吧!在象棋中:士走斜线,象走田字格,马走日子格,車和炮都能直接移动,兵只能前进,不能后退,过河不能左右移动,过河后可以移动,将只能在规定的方格中行走。这是象棋棋子行走规则的基本规则,下面看看详细的移动步骤实现。
在 chessarea.h 头文件中添加相关函数声明:
// 判断一颗棋子能不能移动
bool canMove(int moveid, int killid, int x, int y);
// 判断士能不能移动
bool canMoveSHI(int moveid,int killid, int x,int y);
// 判断兵能不能移动
bool canMoveBING(int moveid, int killid, int x, int y);
// 判断象能不能移动
bool canMoveXIANG(int moveid, int killid, int x, int y);
// 判断马能不能移动
bool canMoveMA(int moveid, int killid, int x, int y);
// 判断車能不能移动
bool canMoveCHE(int moveid, int killid, int x, int y);
// 判断炮能不能移动
bool canMovePAO(int moveid, int killid, int x, int y);
// 判断将能不能移动
bool canMoveJIANG(int moveid, int killid, int x, int y);
// 输入行列坐标判断该坐标上有没有棋子
bool beChess(int row, int col);
// 输入行列坐标获取棋子的id
int getChessId(int row, int col);
// 计算即将行走的棋子与某一坐标之间有几颗棋子
int numChess(int moveid, int x, int y);
// 判断两个棋子是否是同一方的
bool sameColor(int id1, int id2);
接着在 chessarea.cpp 中实现具体的方法描述:
// 输入行列坐标判断该坐标上有没有棋子
bool ChessArea::beChess(int row, int col)
{
for(int i=0;i<32;i++)
if(myChess[i].row==row && myChess[i].col==col&&!myChess[i].isDead)
return true;
return false;
}
// 计算即将行走的棋子与某一坐标之间有几颗棋子
int ChessArea::numChess(int moveid, int x, int y)
{
int i;
int sum = 0; //记录中间有几颗棋子
if(myChess[moveid].row == x)
{
if(y-myChess[moveid].col > 0) //计算下列
{
for(i=myChess[moveid].col+1; i<y; i++)
{
if(beChess(myChess[moveid].row, i) == true)
sum++; //记录中间有几颗棋子
}
}
else /* 计算上列 */{
for(i=myChess[moveid].col-1; i>y; i--)
{
if(beChess(myChess[moveid].row,i)==true)
sum++;
}
}
return sum;
}
else if(myChess[moveid].col == y)
{
if(x - myChess[moveid].row > 0)
{
for(i=myChess[moveid].row+1; i<x; i++)
{
if(beChess(i, myChess[moveid].col)==true)
sum++;
}
}
else {
for(i=myChess[moveid].row-1; i>x; i--)
{
if(beChess(i, myChess[moveid].col)==true)
sum++;
}
}
return sum;
}
//两个棋子不在一条直线上
return -1;
}
numChess 函数的实现要依赖beChess函数,beChess函数实现就是检查一遍输入参数的横纵坐标和棋子的横纵坐标是否相同。numChess函数借用beChess函数,在同一直线上,计算移动前和移动后的横纵坐标差值,再根据差值检测该位置上面是否存在棋子,如果存在棋子那么sum就自加,如果没有,那么两个棋子就不在同一根直线,则返回-1。numChess函数在后面的車和炮行走规则中起关键作用,下面开始实现各个棋子的行走规则。
士的走法:
bool ChessArea::canMoveSHI(int moveid, int killid, int x, int y)
{
// 判断士行走是否超了出米字格范围
if (myChess[moveid].isRed) {
if(y<7 || x<3 || x>5) return false;
}
else {
if(y>2 || x<3 || x>5) return false;
}
// 判断是否为沿着对角线斜着行走
int dx = myChess[moveid].row - x;
int dy = myChess[moveid].col - y;
if(abs(dx)==1 && abs(dy)==1)
return true;
return false;
}
要想让士沿着对角线斜着走,那么就要让士移动前后的横坐标差的绝对值和纵坐标差的绝对值都为1,否则士不能移动。
兵的走法:
bool ChessArea::canMoveBING(int moveid, int killid, int x, int y)
{
int dx = myChess[moveid].row - x;
int dy = myChess[moveid].col - y;
if (myChess[moveid].isRed) {
// 红方棋子,过河前的行走规则
if(myChess[moveid].col>=5 && myChess[moveid].col<=6) {
if(dy==1 && dx==0) //竖着走,不回头
return true;
else //横着走,走不通
return false;
}
else /* 过河后 */{
if (abs(dy)==1 && abs(dx)==0||(abs(dx)==1 && abs(dy)==0)) {
if(dy == -1) //竖着走
return false; //竖着走走了回头路就要返回错误
else //横着走
return true;
}
else return false;
}
}
else /* 黑棋 */{
// 黑方棋子,过河前的行走规则
if (myChess[moveid].col>= 3 && myChess[moveid].col<=4) {
if(dy == -1 && dx==0) //竖着走,不回头
return true;
else //横着走,走不通
return false;
}
else /* 过河后 */{
if(abs(dx)==1&&abs(dy)==0||(abs(dy)==1&&abs(dx)==0)){
if(dy == 1) //竖着回头走,走不通
return false;
else //横着走,一样走得通
return true;
}
else return false;
}
}
return false;
}
兵的走法比士的走法复杂,考虑的细节也较多。首先无论兵有没有过河都不能回头走,过河后,兵才能向左右走。根据这个规则代码可以分为红方和黑方的兵过河前和过河后四个方面进行处理,这样子逻辑比较容易理解。
马的走法:
bool ChessArea::canMoveMA(int moveid, int killid, int x, int y)
{
// 获取移动前和移动后的坐标差
int dx = myChess[moveid].row - x;
int dy = myChess[moveid].col - y;
// 获取移动前和移动后的坐标差的中值坐标
int medium_x = (myChess[moveid].row + x)/2;
int medium_y = (myChess[moveid].col + y)/2;
if(abs(dx)==1&&abs(dy)==2 || (abs(dx)==2&&abs(dy)==1))
{
if(abs(dx)==2)
{
// 别马腿检验
if(beChess(medium_x, myChess[moveid].col)==false)
return true;
}
else if(abs(dy)==2)
{
// 别马腿检验
if(beChess(myChess[moveid].row, medium_y)==false)
return true;
}
}
return false;
}
马的走法是走日字格,那么就有两种情况,一种是横坐标绝对值差为2,纵坐标绝对值差为 1,还有一种是纵坐标绝对值差为2,横坐标绝对值差为 1。除了这样之后,还要注意别马脚判断,那么依靠横纵坐标差的中值坐标可以获取到中值坐标,根据这个坐标值,调用beChess函数判断在该位置是否有棋子别马脚,如果有棋子那么不能走。
象的走法:
bool ChessArea::canMoveXIANG(int moveid, int killid, int x, int y)
{
// 象不能过河
if (myChess[moveid].isRed) {
if(y<5)
return false;
}
else/* 黑象 */{
if(y>4)
return false;
}
//走田字格
int dx = myChess[moveid].row - x;
int dy = myChess[moveid].col - y;
int medium_x = (myChess[moveid].row + x)/2;
int medium_y = (myChess[moveid].col + y)/2;
if(abs(dx)==2 && abs(dy)==2)
{
//别象眼检验
if(!beChess(medium_x, medium_y))
return true;
}
return false;
}
如果懂了前面马的走法,那么象的走法就比较简单了,基本相似。注意象也是需要做别象脚检测。
車的走法:
bool ChessArea::canMoveCHE(int moveid, int killid, int x, int y)
{
if(numChess(moveid, x, y) == 0)
return true;
return false;
}
車的走法有了前面numChess的铺垫,那么就可以变得很简洁。
炮的走法:
bool ChessArea::canMovePAO(int moveid, int killid, int x, int y)
{
// 不吃棋子
if(numChess(moveid, x, y)==0 && killid==-1)
return true;
// 吃棋子
if( numChess(moveid,x, y)==1 && killid!=-1)
return true;
return false;
}
炮的走法和車的很相似,能走沿着直线走,但炮和車唯一的不同就是:炮是隔着一个棋子才能吃棋子。
将的走法:
bool ChessArea::canMoveJIANG(int moveid, int killid, int x, int y)
{
//flag_be用来存放位于同一列上的两个将之间棋子的个数
int flag_be = -1;
int step = 0;
//列超出范围
if(x<3 || x>5)
return false;
//行、列坐标差
int dx = myChess[moveid].row - x;
int dy = myChess[moveid].col - y;
//判断步长是否为1
if((abs(dx)==1&&abs(dy)==0) || (abs(dx)==0&&abs(dy)==1))
step = 1;
if(myChess[moveid].isRed) // 红棋
{
// 要杀掉的棋子必须是对面的老将且与对面的老将在同一列上
if(killid == 16 && myChess[moveid].row == myChess[16].row)
{
flag_be = numChess(moveid, x, y);
if(flag_be == 0)
return true;
}
// 在田字格里面行走
if(y<=9 && y>=7 && step==1)
return true;
}
//黑棋
else
{
//要杀掉的棋子必须是对面的老将且与对面的老将在同一列上
if(killid == 0 && myChess[moveid].row == myChess[0].row)
{
flag_be =numChess(moveid, x, y);
if(flag_be == 0)
return true;
}
//在田字格里面行走
if(y>=0 && y<=2 && step==1)
return true;
}
return false;
}
棋子将的走法只能在米字格里面走,每次只能走一个步长,另外注意,当双方的将在同一直线上,而且没有隔着任何棋子时,那么将就可以吃掉对方的将。