中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格,...

29
中国象棋技术报告 编辑人:解鑫 项目题目:中国象棋的软件制作 项目目的: 1通过分组合作完成大作业,提高 C++编程能力,加深对面向对象技术的理解; 2培养团队合作精神。 项目要求: 1、完成中国象棋图形界面显示,窗口按钮等的创建; 2、能正常人人对战; 3、能分出胜负 4、能检测棋子走法的正确性 5、能悔棋 6、能中途退出 问题分析: 1中国象棋由棋盘、棋子构成; 2棋盘为 10*9 的方格,上下各 5 行,中间为楚河汉界; 各种风格 风格 1 风格 2 风格 3 风格 4 棋盘 3棋子共 32 个,红、黑各 16 个,棋子只能位于棋盘的横竖线的交叉点上,具体情况 见下表: 帅(将) 仕(士) 相(象) 兵(卒) 1 2 2 2 2 2 5

Transcript of 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格,...

Page 1: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

中国象棋技术报告

编辑人:解鑫

项目题目:中国象棋的软件制作

项目目的:

1、 通过分组合作完成大作业,提高 C++编程能力,加深对面向对象技术的理解;

2、 培养团队合作精神。

项目要求:

1、完成中国象棋图形界面显示,窗口按钮等的创建;

2、能正常人人对战;

3、能分出胜负

4、能检测棋子走法的正确性

5、能悔棋

6、能中途退出

问题分析:

1、 中国象棋由棋盘、棋子构成;

2、 棋盘为 10*9 的方格,上下各 5行,中间为楚河汉界;

各种风格 风格 1 风格 2 风格 3 风格 4

棋盘

3、 棋子共 32个,红、黑各 16个,棋子只能位于棋盘的横竖线的交叉点上,具体情况

见下表:

帅(将) 仕(士) 相(象) 马 车 炮 兵(卒)

目 1 2 2 2 2 2 5

Page 2: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

目 1 2 2 2 2 2 5

走法

横或竖直

走 1格,

只能在九

宫内活动

斜线走 1

格,只能

在九宫内

活动

按“田”

字对角线

走;田字

中心有棋

子时,则

被拌不能

走,不能

过河

按“日”

对角线

走;日字

内侧有棋

子时,则

被拌不能

沿横竖线

直走,不

能跨棋子

沿横竖线

直走,或

翻山吃敌

方棋子

过河前,

只能向前

一步;过

河后,沿

横竖线走

一步,但

不能后退

4、 象棋输赢判断:一方的将(帅)被吃,或者将(帅)走后与他方帅(将)照面,则

另一方赢;

5、 中国象棋的游戏,应当采用图像化界面,直观给出实时象棋的棋盘图像以及棋子的

位置,故需创建游戏窗口;

6、 用户主要采用鼠标点击与中国象棋游戏交互,传递用户信息,故必须对鼠标信息进

行实时捕捉;

7、 由于要求悔棋功能,故需要在移动棋子前,将棋子的移动情况进行存储。

算法设计:

一、结构设计:

根据中国象棋的特点,将次游戏风为 3 个部分:棋子(qi_zi)、棋盘(Qi_pan)、界面

(China_chess_window),分别创建类,关系如下:

1、棋子(qi_zi):

1)、由于各棋子类有共同的地方,故先建立基类,然后再继承;

2)、每个棋子设计一个类,共 14个,继承基类,从而减少代码长度、复杂度。

3)、各棋子类中功能:

China_chess_window类

Qi_pan类 qi_zi类

Button类

Menu类

...其他类

Page 3: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

a)、合法位置生成(向量存储、方便之后人机功能扩展)

b)、合法位置检测。

4)、关系如下:

2、棋盘(Qi_pan):

1)、棋盘类包含元素 int chess[256]数组,14个各棋子类的变量。数组存放当前棋

盘的情况,有棋子的地方存放棋子代号,无棋子的地方为 0,实际棋盘只对应数组中部 90个

位置。之所以采用一维 256 的数组,是因为一维数组只用一个下标定位,便于迅速搜索棋盘;

其次,一维 256 数组,棋盘只在中间,便于棋子走出局的检测。

2)、棋盘完成的功能:

a)、初始化数组;

b)、更新数组;

c)、判断各棋子合理位置(调用棋子类中函数完成);

d)、将军检测;

e)、判断输赢;

f)、判断特定位置的棋子的所属方 Side;

g)、返回特定位置棋子代号。

3)、棋子对应代号:(side=0表示红,1表示黑)

红方 帅 仕 相 马 车 炮 兵 特点

代号

F 16 17、18 19、20 21、22 23、24 25、26

27、28、

29、30、31 (side*16+16)

&F=1

便于判断本方棋

黑方 将 士 象 马 车 炮 卒

代号

F 32 33、34 35、36 37、38 39、40 41、42

43、44、

45、46、47

qi_zi类(基类)

红帅

合法位置向量

合法位置判断

红仕

合法位置向量

合法位置判断

红..

合法位置向量

合法位置判断

黑将

合法位置向量

合法位置判断

黑士

合法位置向量

合法位置判断

黑..

Page 4: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

4)、棋盘数组示意图:

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 39, 37, 35, 33, 32, 34, 36, 38, 40, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0,

0, 0, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 0, 0, 0,

0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 23, 21, 19, 17,16, 18, 20, 22, 24, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

2、 界面 China_chess_window:

1)、主体功能:

a)、window 显示;

b)、开始人机对战;//后期功能拓展

c)、人机难度设定;//后期功能拓展

d)、开始人人对战;

e)、重设新局;

f)、悔棋;

g)、继续上次棋局;//后期功能拓展

h)、更换背景颜色;

i)、声音设置;//后期功能拓展

j)、更换棋盘风格;

k)、保存棋局;//后期功能拓展

l)、退出游戏。

2)、辅助功能:(供主体功能调用)

a)、获取鼠标位置信息(Point 型),并转换成对应棋盘数组下标(int 型);

b)、获得棋盘数组下标(int 型),并转换成相对窗口位置(Point型);

实际对应棋盘部分

Page 5: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

c)、刷新棋局显示;

d)、交换下棋方;

3)、window 图像显示的层次化设计:

窗口图像分四层:第一层为按钮;

第二层为背景(矩形作为背景);

第三层为棋盘;

第四层为棋子、移动标记(红色圆圈作为标记);

具体见下:

第一层

第一、二层

捕捉鼠标信息

Page 6: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

第一、二、三

第一、二、

三、四层

二、代码设计:

1、棋子类:(创建头文件 chess_qizi.h,以下是声明和代码说明)

#include <stdio.h> #include <conio.h> #include "std_lib_facilities.h"

//**************存储一步吅理走法结构体***************

typedef struct move{ unsigned char from,to; };

//***********************基类************************

class qizi { public:

qizi(); //默认构造凼数

Page 7: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

unsigned char LegalPosition[2][256]; //吅理位置数组,前部分为红的,后部分为黑的,

unsigned char PositionMask[7]; //PositionMask 数组表示每个棋子能到达的特征位置

//值,依次为将(帅)、士(仕)、象(相)、车、马、炮、卒(兵),不吅理位置数组位不使用,选

//出每个棋子的吅理位置数组。

};

//***********************红兵类************************

class r_bing:qizi{ public:

r_bing(); //默认构造凼数(下同)

vector<struct move> get_MoveArray(unsigned char Now_position , int board[]);

//得到下一步可能的位置(下同)

bool is_legal(unsigned char To_position); //判断是否吅法(下同)

private:

vector<struct move> MoveArray; //存储下一步的吅法位置向量(下同)

short r_bingDir[3]; //存储下一步的相对位置偏移量(下同)

};

//***********************黑卒类************************

class b_zu:qizi{ public: b_zu(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_zuDir[3]; };

//***********************红相类************************

class r_xiang:qizi{ public: r_xiang(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_xiangDir[4];

short r_xiangCheck[4]; //存储相脚是否有子(下同)

};

//***********************黑象类************************

class b_xiang:qizi{ public: b_xiang(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray;

Page 8: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

short b_xiangDir[4]; short b_xiangCheck[4]; };

//***********************红车类************************

class r_che:qizi{ public: r_che(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_cheDir[4]; };

//***********************黑车类************************

class b_che:qizi{ public: b_che(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_cheDir[4]; };

//***********************红马类************************

class r_ma:qizi{ public: r_ma(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_maDir[8]; short r_maCheck[8]; };

//***********************黑马类************************

class b_ma:qizi{ public: b_ma(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_maDir[8]; short b_maCheck[8]; };

//***********************红炮类************************

class r_pao:qizi{ public: r_pao(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]);

Page 9: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_paoDir[4]; };

//***********************黑炮类************************

class b_pao:qizi{ public: b_pao(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_paoDir[4]; };

//***********************红帅类************************

class r_shuai:qizi{ public: r_shuai(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_shuaiDir[4]; };

//***********************黑将类************************

class b_jiang:qizi{ public: b_jiang(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_jiangDir[4]; };

//***********************红仕类************************

class r_shi:qizi{ public: r_shi(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]); bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short r_shiDir[4]; };

//***********************黑士类************************

class b_shi:qizi{ public: b_shi(); vector<struct move> get_MoveArray(unsigned char Now_position , int board[]);

Page 10: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

bool is_legal(unsigned char To_position); private: vector<struct move> MoveArray; short b_shiDir[4]; };

2、棋盘类:(创建头文件 chess_qipan.h,以下是声明和代码说明)

#include"std_lib_facilities.h" #include <conio.h> #include "chess_qizi.h"

//***********************棋盘类************************

class Qipan{ public:

void set_chess(); //初始化棋盘

void set_qizi(); //初始化棋子数组

int renew_chess1(int x1,int x2); //更新棋盘

void renew_chess2(int a[]); //更新棋盘

void renew_qizi(int from,int to); //更新棋子数组

int judge_right(int pre,int now); //判断是否能走

int judge_check(int lSide); //将军检测

int judge_win(int side); //判断输赢

int judge_side(int x,int side); //判断哪一方

int get_value(int x); //返回某一位置的棋子

private:

int chess[256]; //棋盘,存储棋子代号

unsigned char piece[48];//下标 16-47 对应棋子代号,内容存储对应棋子在棋盘中的位置

r_shuai shuai1; r_shi shi1; r_xiang xiang1; r_ma ma1; r_che che1; r_pao pao1; r_bing bing1; b_jiang jiang2; b_shi shi2; b_xiang xiang2; b_ma ma2; b_che che2; b_pao pao2; b_zu zu2; };

3、 界面类:(创建头文件 China_chess_window.h,以下是声明和代码说明)

#include<Simple_window.h> #include<Graph.h> #include<FL\Fl.H> #include<GUI.h> #include<Windows.h> #include<std_lib_facilities.h> #include "chess_qipan.h" using namespace Graph_lib;

struct Hui_qi{ //存储一步移位操作

int from; int from_qi_zhi; int to; int to_qi_zhi; }; struct China_chess_window:Window{

Page 11: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

public:

China_chess_window(Point xy,int w,int h,const string& title); //构造凼数

//***********辅助功能凼数**************

void re_draw(); //重画棋局

int cursor(); //返回鼠标的位置对应棋盘数组的下标

Point int_to_point(int x); //将棋盘数组下标转换成相对窗口的位置

void change_side(); //交换下棋方

void save_move(int pre,int now,vector<struct Hui_qi>& hui_move); //保存走法

void wait_for_button();

void init_BOARD(Vector_ref<Image>& BOARD); //初始化棋盘图片数组

void init_QI_ZHI(Vector_ref<Image>& QI_ZHI); //初始化棋子图片数组

void init_new_qi_pan(int new_qi_pan[]); //初始化前一步棋盘

void init_hui_move(vector<struct Hui_qi>& hui_move); //初始化悔棋链表

private:

int Side; //0 红方,1 黑方,电脑默认黑方 Side=1

int Sound; //声音类型,1 有声

int now_style; //棋盘风格

int pre_style; //棋盘之前风格

int Difficulty; //难度系数

int Flag1; //其实是否加了标识 1

int Flag2; //其实是否加了标识 2

int pre_cursor1; //标志 1 先前位置下标

int pre_cursor2; //标志 2 先前位置下标

Graph_lib::Circle mark1; //标志 1

Graph_lib::Circle mark2; //标志 2

Graph_lib::Rectangle rec; //矩形做背景

Image Title; //标题图像

Vector_ref<Image>BOARD; //存各式棋盘图片

Vector_ref<Image>QI_ZHI; //存所有棋子图片,以及胜利提示图片

vector<struct Hui_qi>hui_move; //保存走法,循环链表,只保存 5 步,6 个成员,其

//中一个-1 表示末尾,最后一位存储栈顶下标

Qipan Qi_pan; //棋盘类变量

int new_qi_pan[256]; //保存前一步棋盘情况

//******************按钮*********************

In_box difficulty; //难度系数

bool button_pushed; Button ren_ji_button; Button ren_ren_button; Button newone_button;

Page 12: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

Button hui_qi_button; Button go_on_button; Button save_button; Button quit_button; Menu color_menu; Button color_menu_button; Menu sound_menu; Button sound_menu_button; Menu style_menu; Button style_menu_button;

Vector_ref<Button>get_cursor; //90 个按钮,采集鼠标信号

//************主体功能凼数****************

void ren_ji(); //人机对战凼数

void ren_ren(); //人人对战凼数

void newone(); //新局凼数

void hui_qi(); //悔棋凼数

void go_on(); //继续上次棋局凼数

void save(); //保存棋盘凼数

void quit(); //退出凼数

//**********设置背景颜色的凼数************

void change_c(Color c);

void hide_c_menu();

void red_pressed();

void blue_pressed();

void black_pressed();

void white_pressed();

void color_menu_pressed();

//**********设置声音的凼数****************

void hide_sou_menu();

void nosound_pressed();

void yessound_pressed();

void sound_menu_pressed();

//**********设置棋盘风格的凼数************

void hide_sty_menu();

voidstyle1_pressed();

void style2_pressed();

void style3_pressed();

void style4_pressed();

void style_menu_pressed();

Page 13: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

//***********回调凼数********************

static void cb_ren_ji(Address,Address); static void cb_ren_ren(Address,Address); static void cb_newone(Address,Address); static void cb_hui_qi(Address,Address); static void cb_go_on(Address,Address); static void cb_quit(Address,Address); static void cb_save(Address,Address); static void cb_red(Address,Address); static void cb_blue(Address,Address); static void cb_black(Address,Address); static void cb_white(Address,Address); static void cb_c_menu(Address,Address); static void cb_nosound(Address,Address); static void cb_yessound(Address,Address); static void cb_sou_menu(Address,Address); static void cb_style1(Address,Address); static void cb_style2(Address,Address); static void cb_style3(Address,Address); static void cb_style4(Address,Address); static void cb_sty_menu(Address,Address); static void cb_cursor(Address,Address); };

4、 主凼数:

#include<Simple_window.h> #include<Graph.h> #include<FL\Fl.H> #include<GUI.h> #include<Windows.h> #include<std_lib_facilities.h> #include "China_chess_window.h" using namespace Graph_lib; int main(){ Point tl(30,30);

China_chess_window win(tl,700,620,"中国象棋"); //界面类变量

return gui_main(); }

三、核心模块说明:

1、棋子中的走法生成函数:

以红兵为例说明:(其他类似)

代码为:

vector< struct move> r_bing::get_MoveArray(unsigned char Now_position , int board[]){

struct move m; //存储一步走法

unsigned char n; //下一步可能行走的位置

int SideTag = 16; //side*16+16 得到的

for(int k = 0 ; k < 3 ; k++) {

n = Now_position + r_bingDir[k]; //n 下一步的绝对位置

现在位置 相对位置

Page 14: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

if(LegalPosition[0][n] & PositionMask[6]) //属于吅法位置内

{

if(!(board[n] & SideTag)) //目标位置没有本方棋子,存储

{ m.from = Now_position; m.to = n; MoveArray.push_back(m); } } } return MoveArray; }

2、棋盘类的将军检测函数:

我的构想是:

棋盘通过调用棋子数组中的现存棋子的下次合法位置函数,获得所有合法的下次位置,

然后只需扫描所有合法位置中是否有敌将位置正处于其中即可判断是否将军。这样快捷方便,

同时,获得了所有合法位置为今后人机对战的搜索算法提供了方便。

以下是杨迪昇完成的将军检测函数:(他负责棋盘类,当然也行)

int Qipan::judge_check(int lSide){

unsigned char wKing,bKing; //红黑双方将帅的位置

unsigned char p,q;

int r; //r=1 表示将军,否则为 0

int SideTag = 32 - lSide * 16; //此处表示 lSide 对方的将的值

int fSide = 1-lSide; //对方标志

int i;

int PosAdd; //位置增量

wKing = piece[16]; bKing = piece[32]; if(!wKing || !bKing) return 0;

//***********检测将帅是否照面************

r=1; if(wKing%16 == bKing%16){ for(wKing=wKing-16; wKing!=bKing; wKing=wKing-16){ if(chess[wKing]){ r=0; break; } } if(r)

return r; //将帅照面

}

q = piece[48-SideTag]; //lSide 方将的位置

//*********检测将是否被马攻击************

int k;

Page 15: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

unsigned char n;//下一步可能行走的位置

unsigned char m;//马腿位置

for(i=5;i<=6;i++){ p = piece[SideTag + i]; if(!p) continue;

for(k=0; k<8; k++){ //8 个方向

n = p + KnightDir[k]; //n 为新的可能走到的位置

if(n!=q) continue;

if(LegalPosition[fSide][n] & PositionMask[3]) { //马将对应下标为 3

m = p + KnightCheck[k];

if(!chess[m]) { //马腿位置无棋子占据

return 1; } } } }

//*********检测将是否被车攻击******

r=1; for(i=7;i<=8;i++){ p = piece[SideTag + i]; if(!p) continue;

if(p%16 == q%16) { //在同一纵线上

PosAdd = (p>q?-16:16); for(p=p+PosAdd; p!=q; p = p+PosAdd) {

if(chess[p]) { //车将中间有子隔着

r=0; break; } } if(r) return r; }

else if(p/16 ==q/16) { //在同一横线上

PosAdd = (p>q?-1:1); for(p=p+PosAdd; p!=q; p = p+PosAdd) { if(chess[p]) { r=0; break; } } if(r) return r; } }

Page 16: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

//********检测将是否被炮攻击*********

int OverFlag = 0; //翻山标志

for(i=9;i<=10;i++){ p = piece[SideTag + i]; if(!p) continue;

if(p%16 == q%16) { //在同一纵线上

PosAdd = (p>q?-16:16); for(p=p+PosAdd; p!=q; p = p+PosAdd) { if(chess[p]) {

if(!OverFlag) //隔一子

OverFlag = 1;

else{ //隔两子

OverFlag = 2; break; } } } if(OverFlag==1) return 1; }

else if(p/16 ==q/16) { //在同一横线上

PosAdd = (p>q?-1:1); for(p=p+PosAdd; p!=q; p = p+PosAdd) { if(chess[p]) { if(!OverFlag) OverFlag = 1; else{ OverFlag = 2; break; } } } if(OverFlag==1) return 1; } }

//*****检测将是否被兵攻击*********

for(i=11;i<=15;i++){ p = piece[SideTag + i]; if(!p) continue;

for(k=0; k<3; k++){ //3 个方向

n = p + PawnDir[fSide][k]; //n 为新的可能走到的位置

if((n==q) && (LegalPosition[fSide][n] & PositionMask[6]))

return 1; //兵士将对应下标为 6

} }

Page 17: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

return 0; } 主要实现是一致的,指数重新写了一遍合理走法生成的函数

3、界面类的人人对战函数:(代码太长,不加载了)

主要思想是:

(1)、等待鼠标点击

(2)、判断是否选中本方棋子

未选中,转(1)

选中,转(3)

(3)、加标志 1

(4)、等待鼠标点击

(5)、判断是否有子

没有,转(6)

有,转(13)

(6)、判断是否是合法下次位置

否,转(4)

是,转(7)

(7)、保存步法

(8)、移动棋子,更新棋盘数组,更新棋子数组

(9)、加标志 2

(10)、判断本方是否胜利

是,gameover=1(初值为 0)

(11)、若行棋为将帅,则判断他方是否胜利

是,gameover=1(初值为 0)

(12)、判断是否他方被将军,

是,提示将军

(13)、判断是否有本方棋子

有,标志 1移至该棋子,转(4)

没有,转(14)

(14)、判断是否是合法位置

否,转(4)

是,转(15)

(15)、保存走法

(16)、删棋子,移动棋子,更新棋盘数组,更新棋子数组

(17)、加标志 2

(18)、判断本方是否胜利

是,gameover=1(初值为 0)

(19)、若行棋为将帅,则判断他方是否胜利

是,gameover=1(初值为 0)

(20)、判断是否他方被将军,

是,提示将军

(21)检测 gameover是否为 1

是,提示胜利,return 主窗口

(22)改变下棋方,转(1)

本程序提示胜利为:新建窗口,贴该方胜利图像,等待 4秒自动关闭

Page 18: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

调试分析:

1、 最初将返回鼠标的位置函数写好时就在棋子加入了输出鼠标位置的函数,在 DOS

界面实时输出鼠标信息,经核对,显示是正常的;

2、 静态界面显示也与预期相同;

3、 背景颜色切换,完美相符;

4、 棋盘风格转换,完美相符;

5、 人人对战的实现遇到了些困难,最初接口有少许错误,经调试分析已解决;但是

由于人人对战需要等待鼠标点击的函数,

a)、照书本上 GUI内创建的 wait_a_button(),其循环只在该函数内,不能解

决这个问题;

Page 19: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

b)、改用 Sleep()函数减缓循环速率尝试,能实时获取鼠标信息,但是该函

数与图形显示有冲突,加入该函数,图形无法正常显示,尝试失败;

c)、改用 system(“PAUSE”)系统暂停函数尝试,只能进行一次行棋,之后系

统暂停,无法继续行棋,尝试失败;

e)、改用 windows句柄 HANDLE 尝试,但由于对 window 了解不多,新建 GUI窗

口与句柄不能很好结合,尝试失败;

f)、改用 MFC尝试,有望解决,但是需要将程序整体移至 MFC构架下,时日不

多,没有过多尝试;

g)、最后回到 Fl::wait()函数,通过体会 wai_for_button()函数的实现思想,

更改了结够,不再调用 wait_for_button()函数,而直接在人人对战函数中用

Fl::wait(),解决了等待鼠标问题。至此,人人函数内部问题彻底解决。

6、 通过 DOS 系统的数据输出,判断鼠标问题已解决。但是棋子还是不能走动。于是

采用断点跟踪,层层监视变量变化。最终发现,棋子类中,由于对类中变量初始

化方法的认识错误,导致变量并不能初始化。

最初的变量初始化:

r_bing::r_bing(){

short r_bingDir[3] = {-0x01,0x01,-0x10}; //方向

}

更改后的变量初始化:

r_bing::r_bing(){ qizi::qizi(); short a[3]= {-0x01,0x01,-0x10};

for(int i=0;i<3;i++)

r_bingDir[i] = a[i]; //方向

} 于是棋子可以移动了

7、 但是,红相,黑马,红马行棋不正常。断点跟踪发现:红相初始化,手误将 i=4(正常

i=0);黑马,红马是 get_MoveArray()中手误多加了一句 break,导致只能得到一个合

法位置。至此人人对战可以正常进行。

8、 悔棋时,一点悔棋就范围错误,查看代码是手误将 QI_ZHI写成了 BOARD,更改后可以

悔棋。但是标志、行棋方,悔棋后会出错,经分析是代码考虑不周全,小修之后,悔棋

可以完美实现。

9、 之后,美化了棋子,去除了边沿绿色背景;增加了下棋方指示;增加了更多错误窗口提

示。至此象棋基本功能全部实现,

以下是调试用的 DOS 界面输出内容:(部分)

Page 20: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

DOS

Page 21: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

最终结果:

1、完成的功能:

1)、能正常下棋

2)、能检测棋子走法合理性

3)、能检测将军,并给出提示

4)、能判断输赢,并给出提示

5)、能悔棋(设计最多悔棋 5步)

6)、能更改棋盘风格(4种风格)

7)、能更改背景颜色

8)、能正常退出

2、最终效果:

Page 22: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

队员的实际工作:

1、解鑫:

1)、项目整体规划,代码整体设计

2)、完成界面程序,人人对战程序,悔棋程序,棋盘风格更换程序,背景跟换程序

3)、完成界面美化,图片制作与美化,窗口美化

4)、代码汇总,整体调试(主要),代码改错

5)、指导队员程序编写,帮助解决难点问题

6)、技术报告编写,整理

2、骆明治:

1)、棋子类的程序编写(与林晓曦一同完成)

2)、参与调试(主要),和代码修改

3、林晓曦:

1)、棋子类的程序编写(与骆明治一同完成)

2)、参与程序测试、参与调试

4、杨迪昇:

1)、棋盘类的程序编写

2)、参与调试

附录:(其他队员对自己工作的概述)

1、杨迪昇:(是在棋子不能移动之前写的)

个人总结

杨迪昇

本次实验中我主要负责棋盘类的设计,该棋盘类中包括了初始化棋局,更新棋盘,

判断该步棋是否能走,将军检测,判断输赢,判断某一颗棋子是哪一边的,返回某一棋子

的信息等。整个棋盘的表示,我采取了一个 16*16的一维数组的形式,这样,一方面可以

简化后续的判断各种走棋情况的计算,又可以使棋盘以一种比较简单清晰的方式呈现出来。

大部分的棋盘类中的函数,我都采用了直接对于数组进行赋值和计算的方法来实现的,这

样当主函数进行调用和后续调试的时候可以降低调试的难度,并且使接口问题也可以比较

容易地解决。在这些函数中,我感觉最复杂的就是将军检测的函数,其中涉及到了将军照

面,困毙,车,马,炮,兵对将军的攻击,为此我专门上网查阅了许多的资料,并且采用

了棋子数组,棋子走法数组,棋子合理位置数组等新的一些思想。从而解决了将军检测这

Page 23: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

一难题。在我初步写完棋盘类之后我又配合主函数的调用进行了许多修改和调试,调试的

过程确实十分枯燥,不过,经过努力我还是确保了我设计的棋盘类基本正确了。不过,由

于我们组没有解决鼠标连续点击的问题,我们最后还是没能很好地完成这次大作业。由于

该问题用到了一些多线程和其他的我们没学过的知识,我们最终感到非常遗憾,不过,经

过这次的大作业,我深刻感受的了自己在编程这方面确实需要进行很大的提高,自己目前

的编程能力实在是非常薄弱。虽然开始的时候,组长带领我们进行了非常详细的部署,不

过还是没能完全地考虑到后面的困难,以后我们一定会在各方面努力提高自身的能力,下

次如果再有一个项目摆在我们面前,我们一定会毫不犹豫地将它攻克。

2、骆明治、林晓曦:(两人合写的)

1).变量说明

typedef struct move { unsigned char from,to; };

side:0 表示红方

: 1 表示黑方

2).构建棋子基类如下:

qizi::qizi() { unsigned char LegalPosition[2][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, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 1 ,25,1, 9, 1,25, 1, 9, 0, 0, 0, 0, 0, 0, 0, 9, 1, 9, 1, 9, 1, 9, 1, 9, 0, 0, 0, 0, 0, 0, 0,17, 1,1, 7,19, 7, 1, 1,17,0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 7, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,17,7, 3, 7,17, 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, 0, 0, 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,17,7, 3, 7,17, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 7, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,17, 1, 1,7,19, 7, 1, 1,17,0, 0, 0, 0, 0, 0, 0, 9, 1, 9, 1, 9, 1, 9, 1, 9, 0, 0, 0, 0, 0, 0, 0, 9, 1,25,1, 9, 1,25, 1, 9, 0, 0, 0, 0,

Page 24: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0 , 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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】用于红方,数组【2】用于黑方

unsigned char PositionMask[7] = {2, 4, 16, 1, 1, 1, 8};

} //将(帅)、士(仕)、象(相)、车、马、炮、卒(兵)对应特征值,

按此设定后,棋子特征值不所在位置在上述对应数组的值,进行按位不运算,

若=1,说明该棋子在可到达的区域内

若=0,则说明是在丌能到达的区域

3).为各棋子建类

红兵:

class r_bing:qizi { public: r_bing();

得到下一步可能的位置凼数

vector<struct move> get_MoveArray(棋子当前位置 Now_position ,当前棋盘数组 board[]);

判断位置吅法凼数;bool is_legal(unsigned char To_position);

private:

下一步的吅法位置向量;vector<struct move> MoveArray;

可能走的距离数组;short r_bingDir[3];

};

初始化凼数:

r_bing::r_bing() {

初始化兵可能走的距离数组:

short r_bingDir[3] = {-0x01,0x01,-0x10}; }

得到下一步可能的位置凼数:

vector< struct move> r_bing::get_MoveArray(unsigned char Now_position , int board[]) {

(红方) int SideTag = 16;

Page 25: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

(黑方)=32

for(k = 0 ; k < 3 ; k++) {

n=当前位置 Now_position +可能移动的距离 r_bingDir[k] ;

判断是否是在可到达的位置区域 if(LegalPosition[0][n] & PositionMask[6])

{

判断是否可能到达位置有本方棋子 if(!(board[n] & SideTag))

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

} } } return MoveArray; }

判断棋子将要到达的位置是否吅法:

bool r_bing::is_legal(To_position) {

做个循环:

If(传进来的位置在可行位置数组 MoveArray 内)

return true; return false; }

4).其他棋子类与红兵基本相似,区别在可能移动距离数组,可行位置数组算法不同,

以下介绍帅、士、象、车、马、炮的该函数算法

帅:可能移动位置数组:short Dir[4] = { -0x10 , 0x10 , -0x01 , 0x01 };

vector< struct move> shuai::get_MoveArray(unsigned char Now_position , int board[]) {

(红方) int SideTag = 16;

(黑方)=32

for(k = 0 ; k < 4 ; k++) {

n=当前位置 Now_position +可能移动的距离 Dir[k] ;

判断是否是在可到达的位置区域 if(LegalPosition[0][n] & PositionMask[0])

{

判断是否可能到达位置有本方棋子 if(!(board[n] & SideTag))

{

存在,则将该原位置不要走位置保存在 move 结构体内,

Page 26: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

并保存在数组 vector<struct move> MoveArray 内

} } } return MoveArray; }

士:可能移动距离 short Dir[4] = { -0x11 , 0x11 , -0x0f , 0x0f }

vector< struct move> get_MoveArray(unsigned char Now_position , int board[]) {

(红方) int SideTag = 16;

(黑方)=32

for(k = 0 ; k < 4 ; k++) {

n=当前位置 Now_position +可能移动的距离 Dir[k] ;

判断是否是在可到达的位置区域 if(LegalPosition[0][n] & PositionMask[1])

{

判断是否可能到达位置有本方棋子 if(!(board[n] & SideTag))

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

} } } return MoveArray; }

象:可行移动位置 short Dir[4] = {-0x22 , -0x1e , 0x1e , 0x22};

象脚相对位置 short Check[4] = {-0x11 , -0x0f , 0x0f , 0x11};

vector< struct move> get_MoveArray(unsigned char Now_position , int board[]) {

(红方) int SideTag = 16;

(黑方)=32

for(k = 0 ; k < 4 ; k++) {

n=当前位置 Now_position +可能移动的距离 Dir[k] ;

判断是否是在可到达的位置区域 if(LegalPosition[0][n] & PositionMask[1])

{

判断是否存在象脚

Page 27: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

}

判断是否可能到达位置有本方棋子 if(!(board[n] & SideTag))

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

} } } return MoveArray; }

马:可行距离 shortDir[8] = {-0x21 , -0x1f , -0x12 , -0x0e , +0x0e , +0x12 ,

+0x1f , +0x21 };

马脚相对位置 short Check[8] = {-0x10 , -0x10 , -0x01 , +0x01 , -0x01 , +0x01 , +0x10 ,

+0x10 }; vector< struct move> get_MoveArray(unsigned char Now_position , int board[]) {

(红方) int SideTag = 16;

(黑方)=32

for(k = 0 ; k < 4 ; k++) {

n=当前位置 Now_position +可能移动的距离 Dir[k] ;

判断是否是在可到达的位置区域 if(LegalPosition[0][n] & PositionMask[1])

{

判断是否存在马脚

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

}

判断是否可能到达位置有本方棋子 if(!(board[n] & SideTag))

{

存在,则将该原位置不要走位置保存在 move 结构体内,

并保存在数组 vector<struct move> MoveArray 内

} }

Page 28: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

} return MoveArray; }

车:车可能移动的四个方向的单位距离 short r_cheDir[4] = {-0x01 , 0x01 , -0x10 , 0x10};

vector<struct move> _MoveArray(Now_position , board[]) {

SideTag = 16 (红)

32(黑);

for(k=0; k<4; k++) //4 个方向

{

for(j=1; j<10; j++) //横的最多有 8 个可能走的位置,纵向最多有 9 个位置

{

下一步可能行走的位置 n = Now_position + j * r_cheDir[k];

if(!(LegalPosition[0][n] & PositionMask[4]))

break;//丌吅理的位置

if(! board[n] ) //目标位置上无子

{ m.from = Now_position; m.to = n; MoveArray.push_back(m); }

else if ( board[n] & SideTag) //目标位置上有本方棋子

break;

else //目标位置上有对方棋子

{ m.from = Now_position; m.to = n; MoveArray.push_back(m); break; } } } return MoveArray; }

炮:炮可能移动的四个方向的单位距离 short Dir[4] = {-0x01 , 0x01 , -0x10 , 0x10};

vector<struct move> pao::get_MoveArray(unsigned char Now_position , int board[]) {

int SideTag = 16 (红);

32(黑)

for(k=0; k<4; k++) { OverFlag = 0;

for(j=1; j<10; j++) //横的最多有 8 个可能走的位置,纵向最多有 9 个位置

{

Page 29: 中国象棋技术报告 - USTChome.ustc.edu.cn/~xx12345/resource/c++_china_chess.pdf · 走1格, 只能在九 宫内活动 斜线走1 格,只能 在九宫内 活动 按“田”

可能位置 n = Now_position + j * r_paoDir[k];

if(!(LegalPosition[0][n] & PositionMask[5]))

break;//丌吅理的位置

if(! board[n] ) //目标位置上无子

{

if(!OverFlag) //未翻山

{ m.from = Now_position; m.to = n; MoveArray.push_back(m); }

//已翻山则丌作处理,自动考察向下一个位置

}

else//目标位置上有子

{

if (!OverFlag) //未翻山则置翻山标志

OverFlag = 1;

else //已翻山

{

if(! (board[n] & SideTag))//对方棋子

{ m.from = Now_position; m.to = n; MoveArray.push_back(m); }

break; //丌论吃丌吃子,都退出此方向搜索

} } } } return MoveArray; }