标题:C语言从零起步分析《象眼》象棋引擎的源码
只看楼主
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
亲琪琪
Rank: 2
等 级:论坛游民
帖 子:55
专家分:38
注 册:2016-3-12
得分:4 
好高端
2016-03-28 17:24
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
luckhide
Rank: 5Rank: 5
来 自:青岛
等 级:职业侠客
帖 子:51
专家分:338
注 册:2016-3-19
得分:0 
ai比较麻烦!
2016-03-28 20:33
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
到后阶段,需要恶补一些数学知识:
递归算法;排序;博弈树的最大最小截断;
哈希,散列,密码RC4等等;
俺正在学习当中,觉得很不容易;
人工智能,建立在数学基础之上;

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

2016-04-01 12:49
pelinX
Rank: 1
等 级:新手上路
帖 子:2
专家分:0
注 册:2016-8-13
得分:0 

怎么没了,求继续分析啊!
2016-08-13 19:17
ehszt
Rank: 12Rank: 12Rank: 12
等 级:贵宾
威 望:40
帖 子:1728
专家分:3216
注 册:2015-12-2
得分:0 
这是用windows SDK开发的吧?感觉好难的样子。
2016-08-14 09:15
pelinX
Rank: 1
等 级:新手上路
帖 子:2
专家分:0
注 册:2016-8-13
得分:0 
关键是要看ai部分啊!
2016-08-15 12:21



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




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

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