| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 24201 人关注过本帖, 9 人收藏
标题:C语言从零起步分析《象眼》象棋引擎的源码
取消只看楼主 加入收藏
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(9)
这是棋子资源装载代码:
程序代码:
for (i = PIECE_KING; i <= PIECE_PAWN; i ++) {
    Xqwl.bmpPieces[SIDE_TAG(0) + i] = LoadResBmp(IDB_RK + i);
    Xqwl.bmpPieces[SIDE_TAG(1) + i] = LoadResBmp(IDB_BK + i);
}

===============
LoadResBmp:自定义的--装入资源图片--的函数;
PIECE_KING:const int PIECE_KING = 0;//将帅
PIECE_PAWN:const int PIECE_PAWN = 6;//卒或兵
-----------
SIDE_TAG:// 获得红黑标记(红子是8,黑子是16)
程序代码:
inline int SIDE_TAG(int sd) {
  return 8 + (sd << 3);
}

sd << 3
左移3位,即sd*8;
----------
SIDE_TAG(0)=8;
SIDE_TAG(1)=16;
===============
HBITMAP bmpBoard, bmpSelected, bmpPieces[24]; // 资源图片句柄;
bmpPieces[24],就是24格的句柄数组;
从0到6,共7次循环,分别把图片的句柄存放进去;
---------------
8----14;16---22;分别存放了图片句柄;
==================
存放了哪些图片句柄呢?
======================
在资源头文件中可以看到:
并从图片中可以看到:
#define IDB_RK         208红帅
#define IDB_RA         209红士
#define IDB_RB         210红象
#define IDB_RN         211红马
#define IDB_RR         212红车
#define IDB_RC         213红炮
#define IDB_RP         214红兵

#define IDB_BK         216黑将
#define IDB_BA         217黑士
#define IDB_BB         218黑象
#define IDB_BN         219黑马
#define IDB_BR         220黑车
#define IDB_BC         221黑炮
#define IDB_BP         222黑卒
======================
8----14:帅士象马车炮兵
16----22:将士象马车炮卒
=======================
本节分析了,棋子资源图片的计算与装载。

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

2016-03-26 22:36
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(10)

来尝试一下显示棋子吧!使用BitBlt函数画一枚棋子:
程序代码:
// 绘制棋盘
void DrawBoard(HDC hdc) {
  HDC hdcTmp;
  // 画棋盘
  hdcTmp = CreateCompatibleDC(hdc);
  SelectObject(hdcTmp, Xqwl.bmpBoard);
  BitBlt(hdc, 0, 0, BOARD_WIDTH, BOARD_HEIGHT, hdcTmp, 0, 0, SRCCOPY);
  // 画棋子
  SelectObject(hdcTmp, Xqwl.bmpPieces[10]);//象-----------加
  BitBlt(hdc, 0, 0, SQUARE_SIZE, SQUARE_SIZE, hdcTmp, 0, 0, SRCCOPY);//----加
   DeleteDC(hdcTmp);
}

在DrawBoard函数中加入两行代码,显示如下:
图片附件: 游客没有浏览图片的权限,请 登录注册

需要透明处理;但是源码中有项说明:
TransparentBlt2// TransparentBlt 的替代函数,用来修正原函数在 Windows 98 下资源泄漏的问题
现在谁还用98呢,俺还是尝试一下TransparentBlt算了,因为我看到那个2,代码太多了,呵呵
程序代码:

 // 画棋子
  SelectObject(hdcTmp, Xqwl.bmpPieces[10]);////BitBlt(hdc, 0, 0, SQUARE_SIZE, SQUARE_SIZE, hdcTmp, 0, 0, SRCCOPY);
// 画棋子1
 TransparentBlt(hdc,0, 0, SQUARE_SIZE, SQUARE_SIZE, //目标
      hdcTmp, 0, 0, SQUARE_SIZE, SQUARE_SIZE, //
      MASK_COLOR);//透明色(掩码色)

换成这个以后,就OK了
图片附件: 游客没有浏览图片的权限,请 登录注册

gongfumi.blog.
===========================
TransparentBlt函数
图片拷贝,从源拷贝到目标;可以过滤透明色;遇到透明色,就忽略;
BOOL TransparentBlt(
HDC hdcDest,      // 目标DC
int nXOriginDest,   // 目标X偏移
int nYOriginDest,   // 目标Y偏移
int nWidthDest,     // 目标宽度
int hHeightDest,    // 目标高度
HDC hdcSrc,         // 源DC
int nXOriginSrc,    // 源X起点
int nYOriginSrc,    // 源Y起点
int nWidthSrc,      // 源宽度
int nHeightSrc,     // 源高度
UINT crTransparent  // 透明色
);
===========================
TransparentBlt(hdc, xx, yy, SQUARE_SIZE, SQUARE_SIZE,
      hdcTmp, 0, 0, SQUARE_SIZE, SQUARE_SIZE,
      MASK_COLOR);
TransparentBlt(目标DC, 目标X偏移, 目标Y偏移, 目标宽度, 目标高度,
      源DC, 源X起点, 源Y起点, 源宽度, 源高度,
      透明色);
===========================
这里有个小麻烦,顺带说一下:
在编译时出错:
Linking...
b.obj : error LNK2001: unresolved external symbol __imp__TransparentBlt@44
Debug/xiangqi1.exe : fatal error LNK1120: 1 unresolved externals
执行 link.exe 时出错.
这个错误搞的人头昏眼花,就是找不出来

后来有个博客上提了一下:加LIB-------msigm32.lib;但是没有说怎么加,又查了很久,才解决问题;
============================================
怎样加LIB呢?
菜单栏的工程------>设置------>连接------->对象/库模块:里面加入msigm32.lib----->确定。
============================================
还有一种办法,在文件顶部声明:
#pragma comment(lib,"msimg32.lib")
============================================
本节尝试了透明化显示棋子。

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

2016-03-26 22:38
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(11)
程序代码:
// 棋盘初始设置
static const BYTE cucpcStartup[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, 20, 19, 18, 17, 16, 17, 18, 19, 20,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0, 21,  0,  0,  0,  0,  0, 21,  0,  0,  0,  0,  0,
  0,  0,  0, 22,  0, 22,  0, 22,  0, 22,  0, 22,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0, 14,  0, 14,  0, 14,  0, 14,  0, 14,  0,  0,  0,  0,
  0,  0,  0,  0, 13,  0,  0,  0,  0,  0, 13,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0, 12, 11, 10,  9,  8,  9, 10, 11, 12,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  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----14:帅士象马车炮兵;16----22:将士象马车炮卒
而且,局面结构中,下棋的盘面,也是这种格式的:
程序代码:
// 局面结构
struct PositionStruct {
  int sdPlayer;                   // 轮到谁走,0=红方,1=黑方
  BYTE ucpcSquares[256];          // 棋盘上的棋子

  void Startup(void) {            // 初始化棋盘
    sdPlayer = 0;
    memcpy(ucpcSquares, cucpcStartup, 256);
  }
  void ChangeSide(void) {         // 交换走子方
    sdPlayer = 1 - sdPlayer;
  }
  void AddPiece(int sq, int pc) { // 在棋盘上放一枚棋子
    ucpcSquares[sq] = pc;
  }
  void DelPiece(int sq) {         // 从棋盘上拿走一枚棋子
    ucpcSquares[sq] = 0;
  }
  void MovePiece(int mv);         // 搬一步棋的棋子
  void MakeMove(int mv) {         // 走一步棋
    MovePiece(mv);
    ChangeSide();
  }
};

这是一个类,包括一些基本的运算都在这里;
目前仅关注初始化棋盘,和这个盘面数组;
static PositionStruct pos; // 局面实例
pc =pos.ucpcSquares[sq];
这个pc关联着棋子图片Xqwl.bmpPieces[pc];
也就是说,棋子的显示,就是基于盘面数组,进行扫描,并显示的;
程序代码:

 // 画棋子
  for (x = FILE_LEFT; x <= FILE_RIGHT; x ++) {//纵3-11
//const int FILE_LEFT = 3;//最左列const int FILE_RIGHT = 11;//最右列
    for (y = RANK_TOP; y <= RANK_BOTTOM; y ++) {//横3-12
//const int RANK_TOP = 3;//最顶行const int RANK_BOTTOM = 12;//最底行
      if (Xqwl.bFlipped) {//BOOL bFlipped; // 是否翻转棋盘
        xx = BOARD_EDGE + (FILE_FLIP(x) - FILE_LEFT) * SQUARE_SIZE;//棋子横向位置
//const int BOARD_EDGE = 8;//棋盘边缘const int SQUARE_SIZE = 56;//单元的大小(正方形格子的边长)
        yy = BOARD_EDGE + (RANK_FLIP(y) - RANK_TOP) * SQUARE_SIZE;//棋子纵向位置
//// 纵坐标水平镜像inline int FILE_FLIP(int x) {  return 14 - x;}
// 横坐标垂直镜像inline int RANK_FLIP(int y) {  return 15 - y;}
      } else {
        xx = BOARD_EDGE + (x - FILE_LEFT) * SQUARE_SIZE;//棋子横向位置
        yy = BOARD_EDGE + (y - RANK_TOP) * SQUARE_SIZE;//棋子纵向位置
      }
      sq = COORD_XY(x, y);//// 根据纵坐标和横坐标获得格子inline int COORD_XY(int x, int y) {  return x + (y << 4);}
//即x+y*16
      pc = pos.ucpcSquares[sq];//此格的棋子编号
      if (pc != 0) {//如果有子,则显示
        DrawTransBmp(hdc, hdcTmp, xx, yy, Xqwl.bmpPieces[pc]);//显示编号对应的图片
      }
// 获得走法的起点inline int SRC(int mv) {  return mv & 255;}
// 获得走法的终点inline int DST(int mv) {  return mv >> 8;}
      if (sq == Xqwl.sqSelected || sq == SRC(Xqwl.mvLast) || sq == DST(Xqwl.mvLast)) {
//全局变量(Xqwl)----int sqSelected, mvLast; // 选中的格子,上一步棋
        DrawTransBmp(hdc, hdcTmp, xx, yy, Xqwl.bmpSelected);//显示摸子框
      }
    }
  }

================================
程序代码:
/ 绘制透明图片
inline void DrawTransBmp(HDC hdc, HDC hdcTmp, int xx, int yy, HBITMAP bmp) {
  SelectObject(hdcTmp, bmp);//选图片bmp,到hdcTmp中
  //把bmp图片拷贝到hdc中(过滤掩码色)
  TransparentBlt(hdc, xx, yy, SQUARE_SIZE, SQUARE_SIZE,//目标
      hdcTmp, 0, 0, SQUARE_SIZE, SQUARE_SIZE,//
      MASK_COLOR);//透明色(掩码色)
}

================================
图片附件: 游客没有浏览图片的权限,请 登录注册

 
本节分析了棋子的显示;

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

2016-03-26 22:40
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(12)
现在来分析一下与棋盘局面(在此简称盘面吧)相关的一些结构体;
为什么分析这些呢?因为,这既联系着棋盘的显示,又关系着高智能运算的操作;
这是一个承上启下的基础地带,熟悉了这些,才能-----为深入理解象棋引擎的工作原理-----打下基础;
程序代码:
int WINAPI WinMain(
                   HINSTANCE hInstance, 
                   HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine, 
                   int nCmdShow) 
{
  Xqwl.hInst = hInstance;//记录应用程序的实例句柄<--------------------------------------
  //Xqwl.hWnd为主窗口句柄;Xqwl.hInst为程序的实例句柄;有区别
  Xqwl.bFlipped = FALSE;//是否翻转棋盘<------------------------------------------------
  Startup();//<-------------------------------------------------------------------------
  MSG msg;
  zhuangrutupianziyuan();//装入图片资源
  // 设置窗口,注册窗口类
  MyRegisterClass(hInstance);

......箭头所指代码,是对盘面的初始化;
======================================
程序代码:
// 初始化棋局
static void Startup(void) {
  pos.Startup();
  Xqwl.sqSelected = Xqwl.mvLast = 0;
}

=====================================
可以看出,对盘面初始化,要操作两种结构体:1------- Xqwl;  2---------- pos;
程序代码:
// 与图形界面有关的全局变量
struct {
  HINSTANCE hInst;                              // 应用程序句柄实例
  HWND hWnd;                                    // 主窗口句柄
  HDC hdc, hdcTmp;                              // 设备句柄,只在"ClickSquare"过程中有效
  HBITMAP bmpBoard, bmpSelected, bmpPieces[24]; // 资源图片句柄
  int sqSelected, mvLast;                       // 选中的格子,上一步棋
  BOOL bFlipped;                                // 是否翻转棋盘
} Xqwl;

以上是Xqwl的结构定义;以下是pos对象:
static PositionStruct pos; // 局面实例
这个对象的实例化,是全局的;下面看看PositionStruct 类:
程序代码:
// 局面结构
struct PositionStruct {
  int sdPlayer;                   // 轮到谁走,0=红方,1=黑方
  BYTE ucpcSquares[256];          // 棋盘上的棋子
  void Startup(void) {            // 初始化棋盘
    sdPlayer = 0;
    memcpy(ucpcSquares, cucpcStartup, 256);
  }
  void ChangeSide(void) {         // 交换走子方
    sdPlayer = 1 - sdPlayer;
  }
  void AddPiece(int sq, int pc) { // 在棋盘上放一枚棋子
    ucpcSquares[sq] = pc;
  }
  void DelPiece(int sq) {         // 从棋盘上拿走一枚棋子
    ucpcSquares[sq] = 0;
  }
  void MovePiece(int mv);         // 搬一步棋的棋子
  void MakeMove(int mv) {         // 走一步棋
    MovePiece(mv);
    ChangeSide();
  }
};

=====================================
BOOL bFlipped;  // 是否翻转棋盘
在winmain中的初始是:FALSE  Xqwl.bFlipped = FALSE;//是否翻转棋盘
也就是不翻转棋盘,
不翻转棋盘,则红方在下,黑方在上;
                    否则黑方在下,红方在下;
==========================
Xqwl.bFlipped =TRUE;// FALSE;我试着修改了这一句,果然是这样,如图:
图片附件: 游客没有浏览图片的权限,请 登录注册

 那么为了好记,就是默认我方得红子,而对方得黑子;翻转,则让对方拿红子;
=====================================================
int sqSelected, mvLast;    // 选中的格子,上一步棋
这两个参数,在盘面显示中用到了,就是摸子框(暂且这么叫)相关;
if (sq == Xqwl.sqSelected || sq == SRC(Xqwl.mvLast) || sq == DST(Xqwl.mvLast)) {
当选中了这一枚棋子 或者 是上一步棋的起点 或者是 上一步的终点 时,就显示摸子框;
/ 获得走法的起点inline int SRC(int mv) {  return mv & 255;}// 获得走法的终点inline int DST(int mv) {  return mv >> 8;}
===以上是对Xqwl的分析=以下是对pos的分析============================================
int sdPlayer;   // 轮到谁走,0=红方,1=黑方
 // 交换走子方void ChangeSide(void) {  sdPlayer = 1 - sdPlayer;  }
当目前轮到红方下棋,sdPlayer=0;交换,则是用1-,1-1=0,1-0=1,这是求反的翻转运算;
// 在棋盘上放一枚棋子 void AddPiece(int sq, int pc) { ucpcSquares[sq] = pc;  }
 // 从棋盘上拿走一枚棋子void DelPiece(int sq) { ucpcSquares[sq] = 0; }
这个是关于吃子,和悔棋的运算;或者,应该,在象棋引擎的递归运算中要反复使用这个运算;
 // 走一步棋 void MakeMove(int mv) { MovePiece(mv);  ChangeSide();  }
走一步棋,有两个步骤1移动棋子2交换走子方
 void MovePiece(int mv);    // 搬一步棋的棋子
程序代码:
// 搬一步棋的棋子
void PositionStruct::MovePiece(int mv) {
  int sqSrc, sqDst, pc;
/ 获得走法的起点inline int SRC(int mv) {  return mv & 255;}// 获得走法的终点inline int DST(int mv) {  return mv >> 8;}
  sqSrc = SRC(mv);
  sqDst = DST(mv);
//从棋盘上拿走一枚棋子void DelPiece(int sq) { ucpcSquares[sq] = 0; }
  DelPiece(sqDst);//删除这格棋子,无论是有子还是无子
  pc = ucpcSquares[sqSrc];//取源棋子的编号
  DelPiece(sqSrc);//删除源格棋子
// 在棋盘上放一枚棋子 void AddPiece(int sq, int pc) { ucpcSquares[sq] = pc;  }
  AddPiece(sqDst, pc);//放上棋子,编号来自源
}

本节分析了象棋的两个重要的全局结构体。1------- Xqwl;  2---------- pos;

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

2016-03-26 22:42
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(13)
来看看鼠标点击事件,分析走棋时的显示过程;
程序代码:
static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  int x, y;
......
switch (uMsg) {
......

// 鼠标点击
  case WM_LBUTTONDOWN:
    x = FILE_LEFT + (LOWORD(lParam) - BOARD_EDGE) / SQUARE_SIZE;
//const int FILE_LEFT = 3;//最左列const int FILE_RIGHT = 11;//最右列
//const int RANK_TOP = 3;//最顶行const int RANK_BOTTOM = 12;//最底行
//const int BOARD_EDGE = 8;//棋盘边缘const int SQUARE_SIZE = 56;//单元的大小(正方形格子的边长)
//lParam表示鼠标坐标(lParam是4字节整数,高2字节表示Y坐标,低2字节表示X坐标
//   X坐标xPos = LOWORD(lParam);
  // Y坐标yPos = HIWORD(lParam);
//x=3+(鼠标坐标X-棋盘边缘)/56;得到了对应于盘面数组(16*16)的X,即横向对应格子;
    y = RANK_TOP + (HIWORD(lParam) - BOARD_EDGE) / SQUARE_SIZE;
//y=3+(鼠标坐标Y-棋盘边缘)/56;(16*16)的Y,即纵向对应格子;
    if (x >= FILE_LEFT && x <= FILE_RIGHT && y >= RANK_TOP && y <= RANK_BOTTOM) {
//x>=3且x<=11且y>=3且y<=12,即在棋盘格子范围之内
      ClickSquare(COORD_XY(x, y));//点击格子事件处理
//根据纵坐标和横坐标获得格子inline int COORD_XY(int x, int y) {  return x + (y << 4);}----------即x*y,在256格中的位置;
    }
    break;
......

=====================================================================
// 点击格子事件处理
程序代码:
static void ClickSquare(int sq) {//sq表示256格中的某一格;
  int pc;
//Xqwl定义:HDC hdc, hdcTmp; // 设备句柄,只在"ClickSquare"过程中有效;HWND hWnd;  // 主窗口句柄
  Xqwl.hdc = GetDC(Xqwl.hWnd);//取主窗口DC
  Xqwl.hdcTmp = CreateCompatibleDC(Xqwl.hdc);//建兼容DC
//Xqwt定义:BOOL bFlipped;  // 是否翻转棋盘
  sq = Xqwl.bFlipped ? SQUARE_FLIP(sq) : sq;
//如果翻转了棋盘,就把sq取反(这个取反是把棋盘转动180度的意思)
// 翻转格子inline int SQUARE_FLIP(int sq) {  return 254 - sq;}
  pc = pos.ucpcSquares[sq];//取此格的棋子编号
  if ((pc & SIDE_TAG(pos.sdPlayer)) != 0) {    // 如果点击自己的子
//pos:int sdPlayer; // 轮到谁走,0=红方,1=黑方
// 获得红黑标记(红子是8,黑子是16)inline int SIDE_TAG(int sd) {  return 8 + (sd << 3);}
//8----14:帅士象马车炮兵
//帅8=B01000士9=B01001象10=B01010马11=B01011车12=B01100炮13=B01101兵14=B01110
//16----22:将士象马车炮卒
////将16=B10000士17=B10001象18=B10010马19=B10011车20=B10100炮21=B10101卒22=B10110
//此时如果轮到红方下棋 SIDE_TAG(pos.sdPlayer)=8,否则=16;
//例如,此时点击的是红象(编号为10)与8进行位运算与,得到的是8;
//右例如,此时点击的是黑马(编号为19)与8进行与运算,得到的是0;
//总之,红子的第5位为0,第4位为1,而黑子的第5位为1,第4位为0;
// 如果点击自己的子:
    if (Xqwl.sqSelected != 0) {//即之前已经选中了某格Xqwl.sqSelected记录0-255;
//Xqwt定义:int sqSelected, mvLast;   // 选中的格子,上一步棋
      DrawSquare(Xqwl.sqSelected);//重画此处格子,相当于清除摸子框
    }
    Xqwl.sqSelected = sq;//更新-------选中的子--------的记录
// "DrawSquare"参数const BOOL DRAW_SELECTED = TRUE;
    DrawSquare(sq, DRAW_SELECTED);//重画格子并画入摸子框
    if (Xqwl.mvLast != 0) {//Xqwl.mvLast仅在初始化以后下棋之前为0,否则都不为0
//重画上一步棋的两个格子,即清除可能的摸子框
//Xqwt定义:int sqSelected, mvLast;   // 选中的格子,上一步棋
// 初始化棋局static void Startup(void) {  pos.Startup();  Xqwl.sqSelected = Xqwl.mvLast = 0;}
// 获得走法的起点inline int SRC(int mv) {  return mv & 255;}// 获得走法的终点inline int DST(int mv) {  return mv >> 8;}
      DrawSquare(SRC(Xqwl.mvLast));//起点处重画格子
      DrawSquare(DST(Xqwl.mvLast));//终点处重画格子
    }
    PlayResWav(IDR_CLICK); // 播放点击的声音

  } else if (Xqwl.sqSelected != 0) {//走子或吃子
// 1点击的不是自己的子(有可能是空,有可能是对方的子)

 //2之前有选中记录
    Xqwl.mvLast = MOVE(Xqwl.sqSelected, sq);//记录这一步棋
// 根据起点和终点获得走法inline int MOVE(int sqSrc, int sqDst) {  return sqSrc + sqDst * 256;}
    pos.MakeMove(Xqwl.mvLast);//执行这一步
// 走一步棋 void MakeMove(int mv) { MovePiece(mv);  ChangeSide();  }走一步棋,有两个步骤1移动棋子2交换走子方
//画记录处
    DrawSquare(Xqwl.sqSelected, DRAW_SELECTED);//重画格子,并画入摸子框
// "DrawSquare"参数const BOOL DRAW_SELECTED = TRUE;
//画点击处
    DrawSquare(sq, DRAW_SELECTED);//重画格子,并画入摸子框
    Xqwl.sqSelected = 0;//清摸子记录
    PlayResWav(pc == 0 ? IDR_MOVE : IDR_CAPTURE); // 播放走子或吃子的声音
  }
  DeleteDC(Xqwl.hdcTmp);
  ReleaseDC(Xqwl.hWnd, Xqwl.hdc);
}

=====================================================================
程序代码:
// 绘制格子//重画格子
static void DrawSquare(int sq, BOOL bSelected = FALSE) {
  int sqFlipped, xx, yy, pc;
//如果翻转了棋盘,就把sq取反
// 翻转格子inline int SQUARE_FLIP(int sq) {  return 254 - sq;}
  sqFlipped = Xqwl.bFlipped ? SQUARE_FLIP(sq) : sq;
// sqFlipped 表示256格中的某一格;
  xx = BOARD_EDGE + (FILE_X(sqFlipped) - FILE_LEFT) * SQUARE_SIZE;
  yy = BOARD_EDGE + (RANK_Y(sqFlipped) - RANK_TOP) * SQUARE_SIZE;
// 获得格子的横坐标inline int RANK_Y(int sq) {  return sq >> 4;}
// 获得格子的纵坐标inline int FILE_X(int sq) {  return sq & 15;}
//const int FILE_LEFT = 3;//最左列const int FILE_RIGHT = 11;//最右列
//const int RANK_TOP = 3;//最顶行const int RANK_BOTTOM = 12;//最底行
//const int BOARD_EDGE = 8;//棋盘边缘const int SQUARE_SIZE = 56;//单元的大小(正方形格子的边长)
//xx=8+(格子横坐标-最左列)*56;
//yy=8+(格子纵坐标-最顶行)*56
  SelectObject(Xqwl.hdcTmp, Xqwl.bmpBoard);//选人棋盘图片
//Xqwl定义:HBITMAP bmpBoard, bmpSelected, bmpPieces[24]; // 资源图片句柄
  BitBlt(Xqwl.hdc, xx, yy, SQUARE_SIZE, SQUARE_SIZE, Xqwl.hdcTmp, xx, yy, SRCCOPY);//画棋盘的某一块
  pc = pos.ucpcSquares[sq];//取此格的棋子编号
  if (pc != 0) {//有子则画块
    DrawTransBmp(Xqwl.hdc, Xqwl.hdcTmp, xx, yy, Xqwl.bmpPieces[pc]);
  }
  if (bSelected) {//画入摸子框
    DrawTransBmp(Xqwl.hdc, Xqwl.hdcTmp, xx, yy, Xqwl.bmpSelected);
  }
}

gongfumi.blog.
=====================================================================
这一节分析了鼠标点击事件的处理;
这里面有个逻辑需要捋捋:
当点击自己的棋子时,仅清扫摸子框,和记录摸子处;
否则,当有摸子记录时,就是走子或吃子;
            无摸子记录时,则不反应;

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

2016-03-26 22:44
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(14)
至此,第一阶段的代码分析,基本完成;
总结一下:1窗口消息循环;2回调函数;3显示棋盘;4透明化显示棋子;5鼠标点击事件------小块的重画;
图片附件: 游客没有浏览图片的权限,请 登录注册

这个象棋引擎的智能水平不错,我打算分析完所有。
第1阶段,是界面与基本操作,比较浅显;
大家也看到了,下1阶段,就要加入下棋规则了。


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

2016-03-27 10:39
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
象棋CPP第二阶段源码
我的代码超过5000字,不能发布,那么上传一个附件吧;
象棋小巫师 0.2
XQWL02.rar (7.31 KB)


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

2016-03-27 14:23
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
收藏
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(15)
第二阶段,主要在于判断走法是否合理;
可以想到,线索的起点,就在走子和吃子上;
我们来看看0.2中,点击格子事件处理,加进了哪些代码:
程序代码:
// 点击格子事件处理
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)) {//<--------- 走一步棋(如果被将军,不走动棋子,返回FALSE;否则返回TRUE)
        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);
        }
      } else {
        PlayResWav(IDR_ILLEGAL); // 播放被将军的声音
      }
    }
    // 如果根本就不符合走法(例如马不走日字),那么程序不予理会
  }
  DeleteDC(Xqwl.hdcTmp);
  ReleaseDC(Xqwl.hWnd, Xqwl.hdc);
}

三个箭头所指,有三个不同的地方:判断走法合理性;判断将军;判断被杀棋;
以后来详细的分析这三个函数;

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

2016-03-27 15:02
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
快速回复:C语言从零起步分析《象眼》象棋引擎的源码
数据加载中...
 
   



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

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