标题:C语言从零起步分析《象眼》象棋引擎的源码
只看楼主
longswallow2
Rank: 1
等 级:新手上路
帖 子:30
专家分:0
注 册:2016-3-26
得分:0 
C语言从零起步分析《象眼》象棋引擎的源码(8)

vc6.0如何添加资源到工程?
点击菜单上的
工程------>增加到工程------>选文件------选资源文件(XQWLIGHT.RC)
接下来,需要做的是:1装载资源图片2显示到窗口;
===========================================
1装载资源图片
程序代码:
void zhuangrutupianziyuan()//装入图片资源
{
    Xqwl.bmpBoard = LoadResBmp(IDB_BOARD);//棋盘
    Xqwl.bmpSelected = LoadResBmp(IDB_SELECTED);//选子  
}

全局变量Xqwl下的一个图片句柄的数组;
 HBITMAP bmpBoard, bmpSelected, bmpPieces[24]; // 资源图片句柄
--------------------------------
Xqwl.hInst = hInstance;//记录应用程序的实例句柄
  //Xqwl.hWnd为主窗口句柄;Xqwl.hInst为程序的实例句柄;有区别
  MSG msg;
  zhuangrutupianziyuan();//装入图片资源
  // 设置窗口,注册窗口类
  MyRegisterClass(hInstance);
紫色的代码,添加于WinMain主函数之内;
--------------------------------
// 装入资源图片
程序代码:
inline HBITMAP LoadResBmp(int nResId) {
  return (HBITMAP) LoadImage(Xqwl.hInst, 
      MAKEINTRESOURCE(nResId), IMAGE_BITMAP, 
      0, 0, LR_DEFAULTSIZE | LR_SHARED);
}

这是装入图片函数LoadResBmp,放入另一头文件之中。
===========================================
2显示到窗口
程序代码:
// 绘制棋盘
static void DrawBoard(HDC hdc) {

 // int x, y, xx, yy, sq, pc;
  HDC hdcTmp;

  // 画棋盘
  hdcTmp = CreateCompatibleDC(hdc);
  SelectObject(hdcTmp, Xqwl.bmpBoard);
  BitBlt(hdc, 0, 0, BOARD_WIDTH, BOARD_HEIGHT, hdcTmp, 0, 0, SRCCOPY);
  // 画棋子
  
  DeleteDC(hdcTmp);
}

------------------------
程序代码:
case WM_PAINT:  // 绘图
        hdc = BeginPaint(Xqwl.hWnd, &ps);//绘画
        DrawBoard(hdc);//--------------------------添加
        EndPaint(Xqwl.hWnd, &ps);
        break;

添加于回调函数(WndProc之中)
========================
这样,就完成了棋盘的装载与显示:

gongfumi.blog.
 ======================
粘贴一些函数的注释:
===========================
SelectObject(hdcTmp, bmp);//选择一对象到指定的设备上下文环境中
===========================
hdcTmp = CreateCompatibleDC(hdc);//创建一个兼容DC
当不再需要内存设备上下文环境时,可调用DeleteDc函数删除它
===========================
BitBlt   目标hDC,   目标X,   目标Y,   图像高,   图像宽,   源hDC,  源X,   源Y,   光栅运算常数
图片拷贝,从源拷贝到目标;
函数原型:
BOOL BitBlt(HDC hdcDest,//目标DC
  int nXDest,//目标X偏移
  int nYDest,//目标Y偏移
  int nWidth,//宽度
  int nHeight,//高度
  HDC hdcSrc,//源DC
  int nXSrc,//源X起点
  int nYSrc,//源Y起点
  DWORD dwRop);//光栅操作代码

BitBlt(hdc, 0, 0, BOARD_WIDTH, BOARD_HEIGHT, hdcTmp, 0, 0, SRCCOPY);
BitBlt(目标hDC, 目标X, 目标Y,宽, 高, 源hDC, 源X起点, 源y起点, SRCCOPY---拷贝);
===========================
本节分析了资源图片的装载与显示的大略过程。

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

2016-03-26 22:34
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
wmf2014
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:216
帖 子:2039
专家分:11273
注 册:2014-12-6
得分:4 

能编个毛线衣吗?
2016-03-27 13: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



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




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

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