| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 24202 人关注过本帖, 9 人收藏
标题:C语言从零起步分析《象眼》象棋引擎的源码
取消只看楼主 加入收藏
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
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
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
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(23)
下面分析一下,另个函数:判断是否被杀棋
程序代码:
// 判断是否被杀
BOOL PositionStruct::IsMate(void) {
  int i, nGenMoveNum, pcCaptured;
  int mvs[MAX_GEN_MOVES];//const int MAX_GEN_MOVES = 128; // 最大的生成走法数

  nGenMoveNum = GenerateMoves(mvs);// 生成所有走法-----放在后面再研究
  for (i = 0; i < nGenMoveNum; i ++) {//遍历全部走法
    pcCaptured = MovePiece(mvs[i]);//试走一步棋
    // int MovePiece(int mv); // 搬一步棋的棋子
    if (!Checked()) {//如果没有被将
      UndoMovePiece(mvs[i], pcCaptured);//撤销一步
      return FALSE;//无被杀
      //只要有1种下法未被将军,则说明暂时没有被杀棋;
    } else {
      UndoMovePiece(mvs[i], pcCaptured);//撤销一步
    }
  }
  return TRUE;//当所有下法,都被将军,则说明被杀;
}

关于生成所有下法的函数,在本节,暂不分析;
下面是使用最频繁的搬动棋子函数:
程序代码:
// 搬一步棋的棋子
int PositionStruct::MovePiece(int mv) {
  int sqSrc, sqDst, pc, pcCaptured;
  sqSrc = SRC(mv);//提取起点
  sqDst = DST(mv);//提取终点
  pcCaptured = ucpcSquares[sqDst];//记录终点棋子编号
  DelPiece(sqDst);// 从棋盘上拿走一枚棋子

 // 从棋盘上拿走一枚棋子//void DelPiece(int sq) {ucpcSquares[sq] = 0;}
  pc = ucpcSquares[sqSrc];//取起点棋子编号
  DelPiece(sqSrc);//从起点处拿走
  AddPiece(sqDst, pc);// 在棋盘上放一枚棋子
  // 在棋盘上放一枚棋子// void AddPiece(int sq, int pc) {ucpcSquares[sq] = pc;}
  return pcCaptured;//返回吃掉的子
}

// 撤消搬一步棋的棋子
void PositionStruct::UndoMovePiece(int mv, int pcCaptured) {
  int sqSrc, sqDst, pc;
  sqSrc = SRC(mv);//提取起点
  sqDst = DST(mv);//提取终点
  pc = ucpcSquares[sqDst];//取终点处子号
  DelPiece(sqDst);//拿下这枚子
  AddPiece(sqSrc, pc);//放回起点处
  AddPiece(sqDst, pcCaptured);//把终点处放上被吃的子(也许是0)
}

// 走一步棋
BOOL PositionStruct::MakeMove(int mv) {
  int pc;
  pc = MovePiece(mv);//搬一步棋的棋子MovePiece
  if (Checked()) {//如果被将
    UndoMovePiece(mv, pc);//撤销这步
    return FALSE;//返回不能走子
  }
  ChangeSide();//换方
  //sdPlayer红1黑0
  // 交换走子方 void ChangeSide(void) {sdPlayer = 1 - sdPlayer;}
  return TRUE;//返回走子成功
}



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

2016-03-28 14:35
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(24)
因为熟悉了底层函数,所以分析起来挺顺利的;
下面是生成所有下法函数:
程序代码:
// 生成所有走法
int PositionStruct::GenerateMoves(int *mvs) const {
//下法数组int mvs[128](在外部),*msv就是下法数组地址
  int i, j, nGenMoves, nDelta, sqSrc, sqDst;
  int pcSelfSide, pcOppSide, pcSrc, pcDst;
  // 生成所有走法,需要经过以下几个步骤:

  nGenMoves = 0;//下法计数器
  pcSelfSide = SIDE_TAG(sdPlayer);//取本方红8黑16
  pcOppSide = OPP_SIDE_TAG(sdPlayer);//取对方边红16黑8
  for (sqSrc = 0; sqSrc < 256; sqSrc ++) {//扫描棋盘的每个角落

    // 1. 找到一个本方棋子,再做以下判断:
    pcSrc = ucpcSquares[sqSrc];//取这1格的棋子编号
    if ((pcSrc & pcSelfSide) == 0) {
//pcSelfSide=8或16,仅仅检查二进制的第4或第5位;
        //如果不是我方棋子
      continue;
      //则忽略下面,并继续扫描
    }

    // 2. 根据棋子确定走法
    switch (pcSrc - pcSelfSide) {//pcSrc - pcSelfSide为棋子编号0-6
    case PIECE_KING://0//将(帅)
      for (i = 0; i < 4; i ++) {//将(帅)的4个方向
        sqDst = sqSrc + ccKingDelta[i];//计算取得终点
        // 帅(将)的步长static const char ccKingDelta[4] = {-16, -1, 1, 16};
        if (!IN_FORT(sqDst)) {//终点不在九宫
// 判断棋子是否在九宫中inline BOOL IN_FORT(int sq) { return ccInFort[sq] != 0;}
//ccInFort:在九宫的数组
          continue;//忽略以下(for以内)
        }
        pcDst = ucpcSquares[sqDst];//取终点棋子编号
        if ((pcDst & pcSelfSide) == 0) {//如果不是我方棋子
          mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加一步下法
          nGenMoves ++;
        }
      }
      break;
    case PIECE_ADVISOR://1//
      for (i = 0; i < 4; i ++) {//士的4个方向
        sqDst = sqSrc + ccAdvisorDelta[i];//基于起点计算终点
// 仕(士)的步长static const char ccAdvisorDelta[4] = {-17, -15, 15, 17};
        if (!IN_FORT(sqDst)) {//终点不在九宫
          continue;//忽略以下(for以内)
        }
        pcDst = ucpcSquares[sqDst];//取终点棋子编号
        if ((pcDst & pcSelfSide) == 0) {//如果不是我方棋子
          mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加一步下法
          nGenMoves ++;
        }
      }
      break;
    case PIECE_BISHOP://2//
      for (i = 0; i < 4; i ++) {//象的4个方向
        sqDst = sqSrc + ccAdvisorDelta[i];//这里也是计算士的步
        if (!(IN_BOARD(sqDst) && HOME_HALF(sqDst, sdPlayer) && ucpcSquares[sqDst] == 0)) {
            //(在棋盘范围之内 并且 终点未过河 并且 终点无子 )之否定,
            //即在棋盘之外,或者,终点过河 ,或者终点有子;
            //注意:这里终点有棋子,是压象眼的!
          continue;//忽略以下(for以内)
        }
        sqDst += ccAdvisorDelta[i];//再加士的步长,+2次士的步,就是象的步了;
        pcDst = ucpcSquares[sqDst];//取终点棋子编号
        if ((pcDst & pcSelfSide) == 0) {//如果不是我方棋子
          mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加一步下法
          nGenMoves ++;
        }
      }
      break;
      //帅0士1象2马3车4炮5兵6
    case PIECE_KNIGHT://3//
      for (i = 0; i < 4; i ++) {//第一层,4次循环
        sqDst = sqSrc + ccKingDelta[i];//取将的步长,憋足处
// 帅(将)的步长static const char ccKingDelta[4] = {-16, -1, 1, 16};
        if (ucpcSquares[sqDst] != 0) {//憋足处有子
          continue;//忽略下,并继续上面的for循环
        }
        for (j = 0; j < 2; j ++) {//第2层,2次循环
          sqDst = sqSrc + ccKnightDelta[i][j];//基于起点计算终点
// 马的步长,以帅(将)的步长作为马腿static const char ccKnightDelta[4][2] = {{-33, -31}, {-18, 14}, {-14, 18}, {31, 33}};
          if (!IN_BOARD(sqDst)) {//棋盘之外
            continue;//忽略以下继续循环
          }
          pcDst = ucpcSquares[sqDst];//取终点棋子编号
          if ((pcDst & pcSelfSide) == 0) {//如果不是我方棋子
            mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加一步下法
            nGenMoves ++;
          }
        }
      }
      break;
    case PIECE_ROOK://4//
      for (i = 0; i < 4; i ++) {//车的4个方向
        nDelta = ccKingDelta[i];// 帅(将)的步长
        sqDst = sqSrc + nDelta;//前进一步
        while (IN_BOARD(sqDst)) {//棋盘内
          pcDst = ucpcSquares[sqDst];//取格子棋子编号
          if (pcDst == 0) {//空位,则加下法
              mvs[nGenMoves] = MOVE(sqSrc, sqDst);
              nGenMoves ++;
          } else {//非空(有子)
            if ((pcDst & pcOppSide) != 0) {//如果是对方子
            //pcOppSide红16黑8,(pcDst & pcOppSide)位与运算,是对方子则非0;
              mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加下法
              nGenMoves ++;
            }
            break;//遇到子,则结束本方向的扫描;
          }
          sqDst += nDelta;//继续前进
        }
      }
      break;
    case PIECE_CANNON://5//
      for (i = 0; i < 4; i ++) {//炮的4个方向
        nDelta = ccKingDelta[i];// 帅(将)的步长
        sqDst = sqSrc + nDelta;//前进一步
        while (IN_BOARD(sqDst)) {//棋盘内
          pcDst = ucpcSquares[sqDst];
          if (pcDst == 0) {//空位,则加下法
            mvs[nGenMoves] = MOVE(sqSrc, sqDst);
            nGenMoves ++;
          } else {
            break;//遇到子,则结束while;
          }
          sqDst += nDelta;//继续前进
        }
        sqDst += nDelta;//再前进一步
        while (IN_BOARD(sqDst)) {//还在棋盘之内,炮扫隔子
          pcDst = ucpcSquares[sqDst];//取子编号
          if (pcDst != 0) {//非空
            if ((pcDst & pcOppSide) != 0) {//如果是对方子
              mvs[nGenMoves] = MOVE(sqSrc, sqDst);//加下法
              nGenMoves ++;
            }
            break;//结束while;
          }
          sqDst += nDelta;//是空,则继续前进
        }
      }
      break;
    case PIECE_PAWN://6//兵或卒
      sqDst = SQUARE_FORWARD(sqSrc, sdPlayer);// 纵向前进一步
      if (IN_BOARD(sqDst)) {//棋盘内
        pcDst = ucpcSquares[sqDst];//取格子中棋子编号
        if ((pcDst & pcSelfSide) == 0) {//如果不是自己的子
          mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加下法
          nGenMoves ++;
        }
      }
      if (AWAY_HALF(sqSrc, sdPlayer)) {//如果起点已过河
        for (nDelta = -1; nDelta <= 1; nDelta += 2) {//-1,1两次循环
          sqDst = sqSrc + nDelta;//计算终点
          if (IN_BOARD(sqDst)) {//棋盘内
            pcDst = ucpcSquares[sqDst];//取格子中棋子编号
            if ((pcDst & pcSelfSide) == 0) {//如果不是自己的子
              mvs[nGenMoves] = MOVE(sqSrc, sqDst);//添加下法
              nGenMoves ++;
            }
          }
        }
      }
      break;
    }
  }
  return nGenMoves;//返回下法的个数
}



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

2016-03-28 16:20
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(25)
至此,第2阶段,代码分析完成了;
小结:第2阶段,就是加进了下棋的基本规则;这个规则,不仅仅是界面操作中使用;在电脑下棋中同样要使用;
图片附件: 游客没有浏览图片的权限,请 登录注册

下一阶段,就要加入电脑下棋代码,激动人心的时刻,就要来了!

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

2016-03-28 19:57
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(27)
电脑下棋的代码加在哪儿?
就是在人点击走棋之后,电脑就开始下棋了,所以应该在点击处理中;
程序代码:
// 点击格子事件处理
static void ClickSquare(int sq) {
  int pc, mv;
  Xqwl.hdc = GetDC(Xqwl.hWnd);
  Xqwl.hdcTmp = CreateCompatibleDC(Xqwl.hdc);
  sq = Xqwl.bFlipped ? SQUARE_FLIP(sq) : sq;
  pc = pos.ucpcSquares[sq];

  if ((pc & SIDE_TAG(pos.sdPlayer)) != 0) {
    // 如果点击自己的子,那么直接选中该子
    if (Xqwl.sqSelected != 0) {
      DrawSquare(Xqwl.sqSelected);
    }
    Xqwl.sqSelected = sq;
    DrawSquare(sq, DRAW_SELECTED);
    if (Xqwl.mvLast != 0) {
      DrawSquare(SRC(Xqwl.mvLast));
      DrawSquare(DST(Xqwl.mvLast));
    }
    PlayResWav(IDR_CLICK); // 播放点击的声音

  } else if (Xqwl.sqSelected != 0) {
    // 如果点击的不是自己的子,但有子选中了(一定是自己的子),那么走这个子
    mv = MOVE(Xqwl.sqSelected, sq);
    if (pos.LegalMove(mv)) {
      if (pos.MakeMove(mv, pc)) {
        Xqwl.mvLast = mv;
        DrawSquare(Xqwl.sqSelected, DRAW_SELECTED);
        DrawSquare(sq, DRAW_SELECTED);
        Xqwl.sqSelected = 0;
        if (pos.IsMate()) {
          // 如果分出胜负,那么播放胜负的声音,并且弹出不带声音的提示框
          PlayResWav(IDR_WIN);
          MessageBoxMute("祝贺你取得胜利!");
        } else {
          // 如果没有分出胜负,那么播放将军、吃子或一般走子的声音
          PlayResWav(pos.Checked() ? IDR_CHECK : pc != 0 ? IDR_CAPTURE : IDR_MOVE);
          ResponseMove(); // 轮到电脑走棋<---------------------------------------------------
        }
      } else {
        PlayResWav(IDR_ILLEGAL); // 播放被将军的声音
      }
    }
    // 如果根本就不符合走法(例如马不走日字),那么程序不予理会
  }
  DeleteDC(Xqwl.hdcTmp);
  ReleaseDC(Xqwl.hWnd, Xqwl.hdc);
}

箭头所指,就是了,也仅仅是这里了;
接着看看电脑走棋函数:
程序代码:
// 电脑回应一步棋
static void ResponseMove(void) {
  int pcCaptured;
  // 电脑走一步棋
  SetCursor((HCURSOR) LoadImage(NULL, IDC_WAIT, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED));//等待的鼠标
  SearchMain();//<---------------------------
  SetCursor((HCURSOR) LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED));//恢复鼠标
  pos.MakeMove(Search.mvResult, pcCaptured);
  // 清除上一步棋的选择标记
  DrawSquare(SRC(Xqwl.mvLast));
  DrawSquare(DST(Xqwl.mvLast));
  // 把电脑走的棋标记出来
  Xqwl.mvLast = Search.mvResult;
  DrawSquare(SRC(Xqwl.mvLast), DRAW_SELECTED);
  DrawSquare(DST(Xqwl.mvLast), DRAW_SELECTED);
  if (pos.IsMate()) {
    // 如果分出胜负,那么播放胜负的声音,并且弹出不带声音的提示框
    PlayResWav(IDR_LOSS);
    MessageBoxMute("请再接再厉!");
  } else {
    // 如果没有分出胜负,那么播放将军、吃子或一般走子的声音
    PlayResWav(pos.Checked() ? IDR_CHECK2 : pcCaptured != 0 ? IDR_CAPTURE2 : IDR_MOVE2);
  }
}

接下来,将要详细分析这个SearchMain()函数了,敬请关注

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

2016-03-28 20:25
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
到后阶段,需要恶补一些数学知识:
递归算法;排序;博弈树的最大最小截断;
哈希,散列,密码RC4等等;
俺正在学习当中,觉得很不容易;
人工智能,建立在数学基础之上;

[此贴子已经被作者于2016-4-1 12:50编辑过]

2016-04-01 12:49
快速回复:C语言从零起步分析《象眼》象棋引擎的源码
数据加载中...
 
   



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

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