标题:C语言从零起步分析《象眼》象棋引擎的源码
只看楼主
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(16)
我们来看看走法合理性判断函数:
程序代码:
// 判断走法是否合理
BOOL PositionStruct::LegalMove(int mv) const {
  int sqSrc, sqDst, sqPin;
  int pcSelfSide, pcSrc, pcDst, nDelta;
  // 判断走法是否合法,需要经过以下的判断过程:

  // 1. 判断起始格是否有自己的棋子
  sqSrc = SRC(mv);
  pcSrc = ucpcSquares[sqSrc];
  pcSelfSide = SIDE_TAG(sdPlayer);
  if ((pcSrc & pcSelfSide) == 0) {
    return FALSE;
  }

  // 2. 判断目标格是否有自己的棋子
  sqDst = DST(mv);
  pcDst = ucpcSquares[sqDst];
  if ((pcDst & pcSelfSide) != 0) {
    return FALSE;
  }

  // 3. 根据棋子的类型检查走法是否合理
  switch (pcSrc - pcSelfSide) {
  case PIECE_KING:
    return IN_FORT(sqDst) && KING_SPAN(sqSrc, sqDst);
  case PIECE_ADVISOR:
    return IN_FORT(sqDst) && ADVISOR_SPAN(sqSrc, sqDst);
  case PIECE_BISHOP:
    return SAME_HALF(sqSrc, sqDst) && BISHOP_SPAN(sqSrc, sqDst) &&
        ucpcSquares[BISHOP_PIN(sqSrc, sqDst)] == 0;
  case PIECE_KNIGHT:
    sqPin = KNIGHT_PIN(sqSrc, sqDst);
    return sqPin != sqSrc && ucpcSquares[sqPin] == 0;
  case PIECE_ROOK:
  case PIECE_CANNON:
    if (SAME_RANK(sqSrc, sqDst)) {
      nDelta = (sqDst < sqSrc ? -1 : 1);
    } else if (SAME_FILE(sqSrc, sqDst)) {
      nDelta = (sqDst < sqSrc ? -16 : 16);
    } else {
      return FALSE;
    }
    sqPin = sqSrc + nDelta;
    while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {
      sqPin += nDelta;
    }
    if (sqPin == sqDst) {
      return pcDst == 0 || pcSrc - pcSelfSide == PIECE_ROOK;
    } else if (pcDst != 0 && pcSrc - pcSelfSide == PIECE_CANNON) {
      sqPin += nDelta;
      while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {
        sqPin += nDelta;
      }
      return sqPin == sqDst;
    } else {
      return FALSE;
    }
  case PIECE_PAWN:
    if (AWAY_HALF(sqDst, sdPlayer) && (sqDst == sqSrc - 1 || sqDst == sqSrc + 1)) {
      return TRUE;
    }
    return sqDst == SQUARE_FORWARD(sqSrc, sdPlayer);
  default:
    return FALSE;
  }
}

在格子事件处理中有这样两行代码,来使用走法合理性判断函数:
mv = MOVE(Xqwl.sqSelected, sq);
if (pos.LegalMove(mv)) {//<---------- 判断走法是否合理

根据起点和终点获得走法inline int MOVE(int sqSrc, int sqDst) {  return sqSrc + sqDst * 256;}
从这里可以看出:mv的低位存放这源点(即一步棋的起点),而高位存放目标点(即一步棋的终点);

第一小段代码分析:
程序代码:
BOOL PositionStruct::LegalMove(int mv) const {
  int sqSrc, sqDst, sqPin;
  int pcSelfSide, pcSrc, pcDst, nDelta;
  // 判断走法是否合法,需要经过以下的判断过程:

  // 1. 判断起始格是否有自己的棋子
  sqSrc = SRC(mv);// 获得走法的起点
  pcSrc = ucpcSquares[sqSrc];
  //获取盘面中某格子的棋子的编号;这个格子就是下法起点格子;
  pcSelfSide = SIDE_TAG(sdPlayer);
  //SIDE_TAG 获得红黑标记(红子是8,黑子是16) 
  //int sdPlayer; // 轮到谁走,0=红方,1=黑方
  //即:目前是轮到红方走,返回8,轮到黑方走,返回16;
  if ((pcSrc & pcSelfSide) == 0) {
    //如果目前是轮到红方走,起点格子中是黑棋,则==0;
   //如果目前是轮到黑方走,起点格子中是红棋,则==0;
    return FALSE;//判断为,下棋不合理,因为起点处,不是自己的子;
  }


第二小段分析:
程序代码:

 // 2. 判断目标格是否有自己的棋子
  sqDst = DST(mv);// 获得走法的终点;
  pcDst = ucpcSquares[sqDst]; //获取盘终点棋子编号;
  if ((pcDst & pcSelfSide) != 0) {
  //目前是轮到红方走,pcSelfSide =8,轮到黑方走,pcSelfSide =16;
    //如果目前是轮到红方走8,终点格子中是红棋,则==8;
   //如果目前是轮到黑方走16,终点格子中是黑棋,则==16;
    return FALSE;//判断为,下棋不合理,因为终点处,有自己的子;
  }


第3小段,内容蛮多,暂告一段落。

[此贴子已经被作者于2016-3-27 16:33编辑过]

2016-03-27 15:45
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(17)
接下来分析第3部分:根据棋子的类型检查走法是否合理
程序代码:

 // 3. 根据棋子的类型检查走法是否合理
  switch (pcSrc - pcSelfSide) {
      //pcSrc - pcSelfSide//目前是轮到红方走,pcSelfSide =8,轮到黑方走,pcSelfSide =16;
      //红子8----14:帅士象马车炮兵//黑子16----22:将士象马车炮卒
      //(pcSrc - pcSelfSide)=[红子帅0士1象2马3车4炮5兵6]=[黑子将0士1象2马3车4炮5卒6]
      // 棋子编号
//const int PIECE_KING = 0;
//const int PIECE_ADVISOR = 1;
//const int PIECE_BISHOP = 2;
//const int PIECE_KNIGHT = 3;
//const int PIECE_ROOK = 4;
//const int PIECE_CANNON = 5;
//const int PIECE_PAWN = 6;
      //即:(pcSrc - pcSelfSide)对应着棋子编号;


将或帅:
程序代码:
case PIECE_KING://将或帅
  return IN_FORT(sqDst) && KING_SPAN(sqSrc, sqDst);
//sqDst走法的终点;sqSrc走法的起点;

函数IN_FORT:判断棋子是否在九宫中
程序代码:
// 判断棋子是否在九宫中
inline BOOL IN_FORT(int sq) {
  return ccInFort[sq] != 0;//ccInFort[]// 判断棋子是否在九宫的数组
}

数组ccInFort:
程序代码:
// 判断棋子是否在九宫的数组
static const char ccInFort[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

以上的一看就明白,因为已经图形化了;什么时候,俺自己开发一套图形化的编程工具去,让图形生成代码,你们就可以享福了
函数KING_SPAN:走法是否符合帅(将)的步长
程序代码:
// 走法是否符合帅(将)的步长
inline BOOL KING_SPAN(int sqSrc, int sqDst) {
  return ccLegalSpan[sqDst - sqSrc + 256] == 1;//ccLegalSpan// 判断步长是否符合特定走法的数组,1=帅(将),2=仕(士),3=相(象)
}

数组ccLegalSpan:
程序代码:
// 判断步长是否符合特定走法的数组,1=帅(将),2=仕(士),3=相(象)
static const char ccLegalSpan[512] = {
                       0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0
};

这个图形看上去就蒙了
sqDst - sqSrc + 256
0+256,刚好在中心点处----0,
如果位移差(纵或者横)(0,1)或(1,0)指向了1,则会返回1,
推演:斜位移差为(1,1),则返回2;斜(2,2)则返回3;
所以,这个数组适合三种子的下法合理性判断;
1=帅(将),2=仕(士),3=相(象)
总结一下return IN_FORT(sqDst) && KING_SPAN(sqSrc, sqDst);的意思:
当目标点在九宫范围之内 并且 移动棋子的位移差返回的字==1 时,将帅的下法合理;

士就无需分析了,哈哈:
  case PIECE_ADVISOR://
    return IN_FORT(sqDst) && ADVISOR_SPAN(sqSrc, sqDst);

程序代码:
// 走法是否符合仕(士)的步长
inline BOOL ADVISOR_SPAN(int sqSrc, int sqDst) {
  return ccLegalSpan[sqDst - sqSrc + 256] == 2;
}



[此贴子已经被作者于2016-3-27 17:33编辑过]

2016-03-27 16:43
hjx1120
Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15
来 自:李掌柜
等 级:贵宾
威 望:41
帖 子:1314
专家分:6927
注 册:2008-1-3
得分:4 
.
2016-03-27 16:49
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(18)
接下来分析象的下法合理性判断:
程序代码:
case PIECE_BISHOP://
    return SAME_HALF(sqSrc, sqDst) && BISHOP_SPAN(sqSrc, sqDst) &&
        ucpcSquares[BISHOP_PIN(sqSrc, sqDst)] == 0;

走法是否符合相(象)的步长inline BOOL BISHOP_SPAN(int sqSrc, int sqDst) {  return ccLegalSpan[sqDst - sqSrc + 256] == 3;}
BISHOP_SPAN这个函数也已经分析过了;
函数SAME_HALF:起点和终点 没有过河为TRUE 过河为FALSE
// 是否在河的同一边
inline BOOL SAME_HALF(int sqSrc, int sqDst) {
  return ((sqSrc ^ sqDst) & 0x80) == 0;
}

我们来学习一下基础知识吧:
“异或”运算符(^)用法:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1);
十六进制,以0x开头,比如0x7a;
八进制,以0开头,比如030;
0x80==128==二进制10000000;
也就是说,当二进制两者第8位不同,(sqSrc ^ sqDst) 的第8位为1,则((sqSrc ^ sqDst) & 0x80)!=0;
第8位代表什么?
0--127,的第8位为0,128--255的第8位为1;
也就是说,过河,不过河,分界,就是对半分的;
0--255当小于128时,第8位为0,否则为1;这样就可以判断:是否在河的同一边;
关于过河的更多讨论,在------C语言从零起步分析《象眼》象棋引擎的源码(21)
函数BISHOP_PIN:相(象)眼的位置
程序代码:
// 相(象)眼的位置
inline int BISHOP_PIN(int sqSrc, int sqDst) {
  return (sqSrc + sqDst) >> 1;
}

即(sqSrc + sqDst) \2;
当其他条件符合,即不过河并且走了田字则;
设起点为x,差为a;
(起点+终点)\2=(x+x+a)\2=x+a\2;
也就是起点+位移差\2;
这个位移差,是个田字,为16*2+2,或者16*2-2,或者-16*2+2,或者-16*2-2;
位移差\2则为16+1,16-1,-16+1,-16-1;
总之,水平方向隔着1格,同时,垂直方向隔着1格;
ucpcSquares[BISHOP_PIN(sqSrc, sqDst)] == 0
则是说这个象眼没有子;
return (sqSrc + sqDst) >> 1,这个得数不可能为负数,也不会大于255;
因此不会数组下标越界。

好难呀,考数学呢?

[此贴子已经被作者于2016-3-28 10:33编辑过]

2016-03-27 17:35
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(19)
马的下法合理性判断:
case PIECE_KNIGHT://
    sqPin = KNIGHT_PIN(sqSrc, sqDst);
    return sqPin != sqSrc && ucpcSquares[sqPin] == 0;

函数:KNIGHT_PIN马腿的位置
程序代码:
// 马腿的位置
inline int KNIGHT_PIN(int sqSrc, int sqDst) {
  return sqSrc + ccKnightPin[sqDst - sqSrc + 256];
}

数组:ccKnightPin蹩腿的数组
程序代码:
// 根据步长判断马是否蹩腿的数组
static const char ccKnightPin[512] = {
                              0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,-16,  0,-16,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0, -1,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  00,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0, -1,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0, 16,  0, 16,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0
};

sqDst - sqSrc + 256
0+256,刚好在中心点处----0,与将士象那个数组的原理相似;
我故意多+个0,以标记中心点;
当位移,走的是日字时(马行日,象飞田),
分别返回-1,1,-16,16;16的意思是纵向+1或-1;
向前纵向跳,则纵向+1,向后纵向跳,则纵向-1;
向左横向跳,则横向-1,向右横向跳,则横向+1;
其他位置为0;
return sqSrc + ccKnightPin[sqDst - sqSrc + 256];
这有两个作用:
1是返回憋足的位置;
2是如果取到0的时候,说明马跳的不是日字;此时,返回数为sqSrc+0,即自身;
 return sqPin != sqSrc && ucpcSquares[sqPin] == 0;
sqPin != sqSrc,这里可以巧妙地判断出跳日;
跳日字 并且 憋足处无子
这里面藏着高深的数学,代码却只有两行;
太精炼了!如果句句这样,那怎么办呢?
外星人才懂吧?也可能我数学太差

[此贴子已经被作者于2016-3-28 10:50编辑过]

2016-03-27 19:10
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(20)
车与炮 下法合理性判断:
程序代码:

 case PIECE_ROOK://
  case PIECE_CANNON://
    if (SAME_RANK(sqSrc, sqDst)) {//起点终点 是否在同一行
      nDelta = (sqDst < sqSrc ? -1 : 1);//同一行,目标<源,则nDelta =-1,否则1;
    } else if (SAME_FILE(sqSrc, sqDst)) {// 是否在同一列
      nDelta = (sqDst < sqSrc ? -16 : 16);//同一列,目标<源,则nDelta =-16,否则16;
    } else {
      return FALSE;//车炮只能横向或纵向,否则下法不合理;
    }
    sqPin = sqSrc + nDelta;//sqPin则为移动方向的前面(紧挨)的位置;
    while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {//扫描空位
      sqPin += nDelta;//如果是横向,则是+1或-1,纵向,则是+16或-16
    }
    if (sqPin == sqDst) {//相等说明中间无子阻挡
      return pcDst == 0 || pcSrc - pcSelfSide == PIECE_ROOK;
     //pcDst为终点棋子编号;pcSrc为起点棋子编号;
     //目前是轮到红方走,pcSelfSide =8,轮到黑方走,pcSelfSide =16;
     //即:(pcSrc - pcSelfSide)对应着棋子编号;
     //即在中间无子阻挡的情况之下:目标位为空,或者 ,这个子是车(车吃子);则是合理的;

    } else if (pcDst != 0 && pcSrc - pcSelfSide == PIECE_CANNON) {
     //中间隔着子:终点有子,并且这个子是炮
     //此时sqPin的位置是有子的,注意
      sqPin += nDelta;//前进1步
      while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {//扫描空位
        sqPin += nDelta;//同上
      }
      return sqPin == sqDst;//这次扫到终点,则是合理的,即炮隔1子,而且仅仅隔1子;
    } else {
      return FALSE;//否则不合理
    }

同行判断,同列判断:
程序代码:
// 是否在同一行
inline BOOL SAME_RANK(int sqSrc, int sqDst) {
  return ((sqSrc ^ sqDst) & 0xf0) == 0;
//0xf0=二进制11110000;
  //高位,确实是代表纵向坐标的;
  //当纵向坐标不同时,则不在同一行;
  //纵向坐标相同时,则(sqSrc ^ sqDst)的高4位都为0;
}
// 是否在同一列
inline BOOL SAME_FILE(int sqSrc, int sqDst) {
  return ((sqSrc ^ sqDst) & 0x0f) == 0;
}
//原理同上

===========================================================
同行
或同列
从方向的下1格,向前扫描,到终点为止,或者到遇子为止;
到终点,即中间无隔子;这时终点无子,则走法合理;终点有子,是车也合理;
扫描没有到终点,即遇到子了;
这时:
   如果是炮,并且终点有子;
   前进1步,即跳过此子,然后向前扫描;
   到终点,则合理;

===========================================================
程序代码:
case PIECE_PAWN://兵卒
    if (AWAY_HALF(sqDst, sdPlayer) && (sqDst == sqSrc - 1 || sqDst == sqSrc + 1)) {
//sqDst走法的终点,//int sdPlayer; 轮到谁走,0=红方,1=黑方
        //AWAY_HALF// 是否已过河
//(sqDst == sqSrc - 1 || sqDst == sqSrc + 1))//横向走动1格
        //横向走动1格,并且,目标格已经过河;
      return TRUE;
    }
    // SQUARE_FORWARD纵向前进
    return sqDst == SQUARE_FORWARD(sqSrc, sdPlayer);
    //如果,纵向前进的位置与走法终点相同,则合理

函数:AWAY_HALF
程序代码:
// 是否已过河
inline BOOL AWAY_HALF(int sq, int sd) {
//sq走法的终点,sd轮到谁走,0=红方,1=黑方
  return (sq & 0x80) == (sd << 7);

 //sd<<7,1<<7=二进制10000000,0<<7=00000000;
  //0x80=二进制10000000;(sq & 0x80)
  //sq:0--255当小于128时,第8位为0,否则为1;
}

关于过河判断的讨论,在C语言从零起步分析《象眼》象棋引擎的源码21)有。
程序代码:
//square广场,垂直;forward前进;源代码中注释错误,注意!
// 纵向前进
inline int SQUARE_FORWARD(int sq, int sd) {
  //sd轮到谁走,0=红方,1=黑方
  return sq - 16 + (sd << 5);
  //sd<<5;
  //如果黑方下,则二进制00100000=32;即(sq - 16 + (sd << 5))=(sq+16);
  //如果红方下,则0;即sq-16;
//轮红方走棋,返回sq-16,即向上进1步;
//轮黑方走棋,返回sq+16,即向下进1步;
}


[此贴子已经被作者于2016-3-28 10:28编辑过]

2016-03-27 19:55
tlliqi
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:204
帖 子:15453
专家分:65956
注 册:2006-4-27
得分:4 
学习
2016-03-27 20:30
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(21)
过河,不过河,我是哪方,红方在数组的哪边?有点晕

盘面数组 PositionStruct:: BYTE ucpcSquares[256];   // 棋盘上的棋子
所有的下法合理性判断,都是基于盘面数组的;我们来整理一下思路,看看这个盘面数组;
盘面数组有256个单元,转换成16进制表示,就是0xff个单元;
0x7f=127;0x80=128;这就是我方地盘与对方地盘的分界线;
哪是我方?
以前分析过:默认是我方得红子的,
BOOL bFlipped;  // 是否翻转棋盘
在winmain中的初始是:FALSE  Xqwl.bFlipped = FALSE;//是否翻转棋盘

bFlipped,这个翻转,完全是面向显示的,注意,与盘面数组无关;
与盘面数组有关的是另一个-----PositionStruct::  int sdPlayer; 轮到谁走;
0=红方,1=黑方;此时红方,是指我方,因为默认是我方得红子(winmain中初始确定的);

基于以上分析,下方为红方,下方是我方;
我方格子,从128-255,从0x80-0xff;
128=0x80的二进制1000 0000;
相当于,我方格子=128+A,A的范围从0-127)之间;
对方格子,行0-127,从0x00-0x7f;
127=0x7f的二进制0111 1111;
通过位运算,检测第8位,就可以判断格子属于哪1方;
任意格子为sq,我方格子则(sq&0x80)=0x80;
              对方格子则(sq&0x80)=0;

sdPlayer: 轮到谁走;0=红方我方下方,1=黑方对方上方;

当sdPlayer=0,轮到红方走棋,sq在[0~127]之间,则过界;sq在[128~255]之间,,否则未过界;
当sdPlayer=1,轮到黑方走棋,sq在[0~127]之间,则未过界;sq在[128~255]之间,,否则过界;

1<<1=2;B00000010;H2;0X02;
1<<2=4;B00000100;H4;0X04;
1<<3=8;B00001000;H8;0X08;
1<<4=16;B00010000;H10;0X10;
1<<5=32;B00100000;H20;0X20;
1<<6=64;B01000000;H40;0X40;
1<<7=128;B100000000;H80;0X80;
1<<8=256;B1000000000;H100;0X100;
程序代码:
// 是否已过河
inline BOOL AWAY_HALF(int sq, int sd) {
  //sd<<7,1<<7=128,0<<7=0;
//当sq处于棋盘的上方,sq<128,(sq & 0x80)=0;
//当sq处于棋盘的下方,sq>=128,(sq & 0x80)=128;
  return (sq & 0x80) == (sd << 7);
//当sd=0,轮到红方走棋,我方下方走棋,sd<<7=0;二进制第8位为0,表示棋盘的上方范围;这个范围是过河范围;
//当sd=1,轮到黑方走棋,对方上方走棋,sd<<7=128;二进制第8位为1,表示棋盘的下方范围;这个范围是过河范围;
//即:左边(sq & 0x80)指出棋子所处范围;右边 (sd << 7)指出走子方的过河范围;
//例如:走子范围在上0,过河范围在上0,两边相等,则走子范围过河了;
//总之,走子sq,走子范围(sq & 0x80),过河范围(sd << 7);返回(走子范围==过河范围);
}
// 是否未过河
inline BOOL HOME_HALF(int sq, int sd) {
  return (sq & 0x80) != (sd << 7);
//返回(走子范围!=过河范围);
}
// 是否在河的同一边
inline BOOL SAME_HALF(int sqSrc, int sqDst) {
  return ((sqSrc ^ sqDst) & 0x80) == 0;
  //“异或”运算符(^)用法:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1);
//第8位,相同,则同边;
}


[此贴子已经被作者于2016-3-28 09:50编辑过]

2016-03-28 08:05
wanglianyi1
Rank: 11Rank: 11Rank: 11Rank: 11
等 级:贵宾
威 望:14
帖 子:647
专家分:2067
注 册:2015-6-18
得分:4 
2016-03-28 08:42
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(22)
7种棋子,一张棋盘,下法合理性判断,已经分析完成;
下面分析将军的判断函数:
程序代码:
// 判断是否被将军
BOOL PositionStruct::Checked() const {
  int i, j, sqSrc, sqDst;
  int pcSelfSide, pcOppSide, pcDst, nDelta;
  pcSelfSide = SIDE_TAG(sdPlayer);// 获得红黑标记(红子是8,黑子是16)
  pcOppSide = OPP_SIDE_TAG(sdPlayer);// 获得对方红黑标记(16,8)
  // 找到棋盘上的帅(将),再做以下判断:

  for (sqSrc = 0; sqSrc < 256; sqSrc ++) {
    if (ucpcSquares[sqSrc] != pcSelfSide + PIECE_KING) {
        //pcSelfSide + PIECE_KING
        //pcSelfSide(红走是8,黑走是16);const int PIECE_KING = 0;
        //轮到红走,则帅8,否则将16;
      continue;//忽略以下,并继续循环;
              //0-255格,只有1次,执行以下代码;
    }

    // 1. 判断是否被对方的兵(卒)将军
    if (ucpcSquares[SQUARE_FORWARD(sqSrc, sdPlayer)] == pcOppSide + PIECE_PAWN) {
        //此时sqSrc,指向帅(将)的位置;
        //SQUARE_FORWARD 纵向前进;帅纵向前进,与卒纵向前进一样;
        //即,如果帅(将)的纵向前进位,有个卒则说明被将;
      return TRUE;
    }
    for (nDelta = -1; nDelta <= 1; nDelta += 2) {//-1,1,两次循环
      if (ucpcSquares[sqSrc + nDelta] == pcOppSide + PIECE_PAWN) {
          //即左或右有卒,则说明被将;
        return TRUE;
      }
    }

    // 2. 判断是否被对方的马将军(以仕(士)的步长当作马腿)
    for (i = 0; i < 4; i ++) {//0,1,2,3 四次循环
      if (ucpcSquares[sqSrc + ccAdvisorDelta[i]] != 0) {
          //// 仕(士)的步长static const char ccAdvisorDelta[4] = {-17, -15, 15, 17};
          //-17,-15,15,17
          //-16-1,-16+1,16-1,16+1,这就是以帅为中心4角的点,即马憋足点;
        continue;
        //如果有马的憋足点,则忽略以下;
      }
      //
      for (j = 0; j < 2; j ++) {//0,1两次循环
        pcDst = ucpcSquares[sqSrc + ccKnightCheckDelta[i][j]];//取此格棋子编号
// ccKnightCheckDelta马被将军的步长,以仕(士)的步长作为马腿
//static const char ccKnightCheckDelta[4][2] = {{-33, -18}, {-31, -14}, {14, 31}, {18, 33}};
//{-33, -18}, {-31, -14}, {14, 31}, {18, 33}
//{-32-1, -16-2}, {-32+1, -16+2}, {16-2, 32-1}, {16+2, 32+1}
        if (pcDst == pcOppSide + PIECE_KNIGHT) {//如果pcDst是对方的马
          return TRUE;//说明被马将军
        }
      }
    }

    // 3. 判断是否被对方的车或炮将军(包括将帅对脸)
    for (i = 0; i < 4; i ++) {//0,1,2,3 四次循环
        // 帅(将)的步长static const char ccKingDelta[4] = {-16, -1, 1, 16};
      nDelta = ccKingDelta[i];//取带方向的单步单位
      sqDst = sqSrc + nDelta;//前进一格

      while (IN_BOARD(sqDst)) {//sqDst在棋盘中,就循环

 // 判断棋子是否在棋盘中inline BOOL IN_BOARD(int sq) {return ccInBoard[sq] != 0;}
        pcDst = ucpcSquares[sqDst];//取此格棋子编号
        if (pcDst != 0) {//如果有子
          if (pcDst == pcOppSide + PIECE_ROOK || pcDst == pcOppSide + PIECE_KING) {
              //棋子为对方的车 或者 棋子为对方的老将,则说明被将军;
            return TRUE;
          }
          break;//break是结束while循环;
        }
        sqDst += nDelta;//没有子,则前进1步;
      }
      //以上的while,的作用是沿着4个扫描----首个遇到的子;非对方车非对方将,就往下继续;
     //以下是扫描对方的炮
      sqDst += nDelta;//前进1步
      while (IN_BOARD(sqDst)) {//sqDst在棋盘中,就循环
        //int pcDst = ucpcSquares[sqDst];//pcDst这是在块内重新定义的变量?
          pcDst = ucpcSquares[sqDst];//我把代码改了以下,也通过了,这里不能理解,水平有限,不好依稀
        if (pcDst != 0) {//如果有子
          if (pcDst == pcOppSide + PIECE_CANNON) {//如果是对方的炮,则被将
            return TRUE;
          }
          break;
        }
        sqDst += nDelta;//前进1步
      }
      //--------------以上是炮的扫描
    }
    return FALSE;//横竖4个方向的扫描无
  }//for(256)
  return FALSE;//没有找到老将?是的
}


下面粘贴有关的函数:
程序代码:
// 判断棋子是否在棋盘中
inline BOOL IN_BOARD(int sq) {
  return ccInBoard[sq] != 0;
}

程序代码:
// 判断棋子是否在棋盘中的数组
static const char ccInBoard[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

程序代码:
// 获得红黑标记(红子是8,黑子是16)
inline int SIDE_TAG(int sd) {
  return 8 + (sd << 3);
}

// 获得对方红黑标记
inline int OPP_SIDE_TAG(int sd) {
    //红sd=0;黑sd=1;
    //16 - (sd << 3)=16-sd*8;
  return 16 - (sd << 3);
  //现在轮到红走,返回16,黑,则返回8;
}

在块内重新定义一个同名的变量,编译没有报错,运行也正常;
但我觉得这是源码的一个错误。大家可以讨论一下;
另外,这个函数的最后一句,我加断点测试了一下,
我把数组中的帅去掉试了一下,贴图出来:


[此贴子已经被作者于2016-3-28 14:04编辑过]

2016-03-28 11:15



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-462942-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.372708 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved