一、摘 要
本报告讨论了中国象棋程序设计的关键技术和方法。首先介绍了中国象棋的棋盘制作,利用Qt中的一些绘画类的函数来进行绘制。在创作中国象棋棋子方面,首先,我们先定义一下棋子类,将棋子中相同的部分进行打包,使用了循环和结构体定位的方式将棋子放在对应的位置上进行初始化。在棋子的走法方面,这一部分是课程设计的重要部分,在这一部分会将象棋的基本规则进行创建和完善,分别为七种棋子的走法进行封装,让棋盘的规则更加完善。这些内容为理解和设计高效的中国象棋程序提供了深入的技术基础和方法指导,了解和掌握C++语言的基本语法和面向对象编程思想,同时通过实践来加深对C++语言的理解和应用,了解和掌握C++语言的基本语法和面向对象编程思想,同时通过实践来加深对C++语言的理解和应用。
二、问题分析
2.1 有关中国象棋的背景
中国象棋,又称为"象棋"或"中国国际象棋",是中国文化中最具代表性的棋类游戏之一,有着悠久的历史和深厚的文化底蕴。以下是关于中国象棋背景的一些重要信息:
中国象棋的历史和文化的发展:中国象棋起源于中国约两千年前的春秋战国时期,最初称为"象戏"、"象棋",是中国古代文化遗产之一。象棋在中国的发展不仅仅是一种游戏,更是文化的传承和精神的表达。象棋的棋谱、名家棋局和赛事都被广泛收集、传播和研究;中国象棋的规则简单:象棋以其简单明了的规则而闻名,棋盘为九宫格,棋子包括将、士、象、车、马、炮和兵,双方分红黑两色,对于每一种棋子封装一个属于自己的象棋走法。
因此,由于上述两种情况,中国象棋程序设计是有必要,在帮助了解中国的文化底蕴的同时,可以提升编程思维,编码能力,进一步巩固C++基础。
2.2 中国象棋程序编程的目的
通过这次课设,可以了解和掌握C++语言的基本语法和面向对象编程思想,进一步学习Qt,同时通过实践来加深对C++语言的理解和应用。具体目标是实现一个简单的中国象棋游戏,包括界面设计、程序逻辑实现和人机博弈功能。通过完成这个课设,可以提高自己的编程能力,能够利用所学的基本知识和技能,解决简单的面向对象程序设计问题,能够熟练地使用C++的特点继承,封装,多态,加深对面向对象编程思想的理解,同时也为学习其他高级语言和开发其他复杂应用程序打下坚实的基础。
2.3 中国象棋程序编程的意义
中国象棋程序编程的意义有很多:智能象棋程序可以作为教学工具,帮助人们学习象棋策略和战术。此外,它们还可以提供高质量的游戏体验,为象棋爱好者和学习者提供挑战和娱乐。在课设中用到的技术也可以进行分享,象棋程序设计中的算法和技术不仅局限于象棋本身,还可以应用于其他领域,文化传承与推广:象棋作为中国传统文化的重要组成部分,通过智能象棋程序的设计和推广,可以增加公众对象棋的兴趣和了解,促进文化传承和推广。在对于这个未使用到的博弈算法,为人机版本的象棋奠定了基础,同时对于棋类游戏,可以优先考虑博弈算法。
综上所述,中国象棋程序设计不仅在技术和科学研究上具有重要意义,还在教育、娱乐和文化传承方面发挥着重要作用,为人工智能技术的发展和应用提供了宝贵的实验平台和应用场景。
2.4 中国象棋程序设计的功能需求分析
在中国象棋程序设计中,总共分为四个部分:棋盘的绘制,棋子的摆放,棋子的移动规则,简易的单机游戏。
在棋盘绘制方面中,因为棋盘整体是一个矩形,拥有10条竖线和9条横线,最后还有两个九宫格,需要创建一个board类,在这个类中利用一些有关绘画的知识点进行棋盘的绘制。最后在主函数中将这个窗口进行显示即可。
在棋子的摆放方面中,因为棋子的种类一共有7种,棋子的个数一共有32个,所以需要建立一个棋子类在这个棋子类中,可以将一些棋子相同的部分放在一起,最后利用结构体将棋子的坐标和种类进行初始化,完成棋子的摆放。
在棋子的移动规则方面中,在棋子的移动,必然需要使用鼠标点击事件和鼠标释放事件,在下象棋中,由于最后是鼠标释放,所以在这个程序中,使用的是鼠标释放事件。将鼠标可以控制棋子的移动,最后在棋子的移动中,需要将棋子的移动进行设置,防止作弊。根据中国象棋的棋子移动规则:
1、帅(将):帅(将)是棋中的首脑,是双方竭力争夺的目标。它只能在九宫之内活动,可上可下,可左可右,每次走动只能按竖线或横线走动一格。帅与将不能在同一直线上直接对面,否则走方判负。
2、仕(士):仕(士)是将(帅)的贴身保镖,它也只能在九宫内走动。它的行棋路径只有九宫内的四条斜线。
3、相(象):相(象)的主要作用是防守,保护自己的帅(将)。它的走法是每次循对角线走两格,俗称"象飞田"。相(象)的活动范围限于河界以内的本方阵地,不能过河,且如果它走的田字中央有一个棋子,就不能走,俗称"塞象眼"。
4、车:车在象棋中威力最大,无论横线、竖线均可行走,只要无子阻拦,步数不受限制。因此,一车可以控制十七个点,故有"一车十子寒"之称。
5、炮:炮在不吃子的时候,移动与车完全相同。当吃子时,己方和对方的棋子中间必须间隔1个棋子(无论对方或己方棋子),炮是象棋中唯一可以越子的棋种。
6、马:马走动的方法是一直一斜,即先横着或直着走一格,然后再斜着走一个对角线,俗称"马走日"。马一次可走的选择点可以达到四周的八个点,故有"八面威风"之说。如果在要去的方向有别的棋子挡住,马就无法走过去,俗称"蹩马腿"。
7、兵(卒):兵(卒)在未过河前,只能向前一步步走,过河以后,除不能后退外,允许左右移动,但也只能一次一步,即使这样,兵(卒)的威力也大大增强,故有"过河的卒子顶半个车"之说。
三、总体设计
3.1 界面设计
在界面设计中,一共有两个主要的页面,一个开始页面,我们需要一个按钮,点击这个按钮就可以进入游戏的主界面中;另一个页面是中国象棋游戏进行页面,我们需要使用这个页面进行游戏,具体流程图如图所示。
在开始页面设计中,需要创建出一个窗口类,在页面中添加一个点击按钮,点击这个按钮,可以进行象棋的游戏界面。在这个窗口启动之前,先使用qt设计者类中创建一个新的窗口以便于进入游戏,之后就是象棋的界面,之后需要使用painter类中的函数进行绘制,在board类中创建一个painterEvevt函数,在这个函数进行绘画设计。
2.2 类的设计
中国象棋项目总共需要设计三个主要类,一个类是棋盘类,一个类是棋子类,一个是开始结束页面类:
在棋盘类中,需要进行棋子的初始化,棋盘的绘制,棋子的绘制,判断棋子是否可以移动(棋子的移动规则),判断棋子是否重合。这个类的主要功能是:进行棋盘的绘制,然后根据棋子类中的棋子的位置进行棋子的绘制,在这个类中使用鼠标释放事件使棋子可以按照玩家想点击在哪里就点击在哪里,但是还是要有一定的约束,因此在这个类中实现了棋子的移动规则用来约束玩家的移动,最后判断棋子是否重合,因为如果棋子重合,需要将该棋子进行释放,表示吃掉了该棋子。
在棋子类中,将棋子的相同属性进行创建,有添加了初始化棋子的函数和将棋子的姓名和棋子进行对应的函数,这个类的主要功能是:列出棋子的种类,将棋子进行初始化,然后将这个棋子进行摆放在其对应的位置。
在开始结束页面类中,在开始页面中创建一个对话框类,在对话框内创建两个点击点击按钮,然后点击开始按钮就可以进入中国象棋游戏界面中,点击退出游戏按钮就可以退出游戏。在结束页面中,当任何一方的将被吃掉后就会弹出一个消息框,在消息框汇总显示胜利的字样。这个类中的主要功能就是:点击按钮就可以进入游戏页面,完善一下程序的界面。
这些类的关系是:在开始页面中,点击进入游戏页面的端口中,然后进入游戏中;调用棋盘类,让棋盘类进行游戏界面的绘制;之后在棋盘类中,需要调用棋子类,将棋子进行初始化,利用棋盘类中的鼠标释放事件进行游戏。当游戏结束之后,程序会弹出一个胜利的窗口。
四、界面设计
4.1 界面的详细设计
在开始页面设计中,需要在项目中建立一个新的对话框类,选择Qt设计师界面类进行创建,选择界面模板为:Dialog without Buttons,填写类名,在把其添加到项目中。之后,在设计界面中dialog.ui中拖入一个 Push Button,将其上的文本改为"进入主窗口"。
利用信号槽机制将点击事件和按钮进行关联。在其属性窗口中将其 objectName 改为 enterBtn,在下面的 Signals and slots editor 中进行信号和槽的关联,其中,Sender(发送者) 设为 enterBtn,Signal(信号) 设为 clicked(),Receive (接收者)设为 myDlg,Slot(槽)设为 accept()。这样就实现了单击这个按钮使这个对话框关闭并发出 Accepted 信号的功能,如图3.1所示。下面利用这个信号将其按钮与游戏界面进行关联。
其函数的代码如下,将开始界面类与游戏界面进行关联,进行判断,当有鼠标点击事件发生,需要进行判断,然后将游戏界面类进行显示;如果判断没有鼠标点击事件发生,则不会进入主窗口,整个程序结束运行。
cpp
QApplication app(argc, argv);
Dialog dia1; // 创建一个进入窗口
SingleGame board; // 创建一个棋盘,即游戏界面
if(dia1.exec() == QDialog::Accepted)
// 进行判断,利用 Accepted 信号判断 enterBtn 是否被按下
{
board.show(); // 如果判断成功,则进行显示棋盘
return app.exec(); // 程序一直执行,直到主窗口关闭
}
else return 0; // 如果没被按下,则不会进入主窗口,整个程序结束运行
在游戏界面设计中,需要根据这个象棋棋盘的样式进行设计。由于在Qt中,一般有关的绘画的函数都在paintEvent函数,所以在棋盘的绘制中,大部分函数都是在paintEvent函数中实现。在这个函数中,首先创建一个画家,初始化棋盘格子的大小为d。然后开始绘画出棋盘的本体,利用drawLine函数进行画出10条竖线和9条横线,在这个规程中,需要解决一些bug,因为在棋盘中有一个楚河汉界分界线。所以在进行竖线的绘画中,需要控制中间的7条竖线不能越过这个分界线,其主要代码如下:
cpp
QPainter paint(this);// 创建一个画家
paint.drawLine(QPoint(d, i * d), QPoint(9 * d, i * d)); // 循环10次
if(i == 1 || i == 9)
paint.drawLine(QPoint(i * d, d), QPoint(i * d, 10 * d));
else
{
paint.drawLine(QPoint(i * d, d), QPoint(i * d, 5 * d));
paint.drawLine(QPoint(i * d, 6 * d), QPoint(i * d, 10 * d));
}
之后创建出九宫格,还是利用这个drawLine函数创建出九宫格,找出这个点的坐标,然后将其进行连接,其主要代码如下:
cpp
// 画出九宫格
// 还是用直线来进行画出九宫格
paint.drawLine(QPoint(4 * d, 1 * d), QPoint(6 * d, 3 * d));
paint.drawLine(QPoint(6 * d, 1 * d), QPoint(4 *d, 3 * d));
// 进行平移
paint.drawLine(QPoint(4 * d, 8 * d), QPoint(6 * d, 10 * d));
paint.drawLine(QPoint(6 * d, 8 * d), QPoint(4 * d, 10 * d));
对于棋盘中的准星,还是利用drawLine函数进行绘制,也是主要是要找对点的位置,需要进行排布点的位置,其主要代码如下:
cpp
// 画出棋盘中的准星
paint.drawLine(QPoint(d + 35, 2 * d + 25), QPoint(d + 35, 2 * d + 35));
paint.drawLine(QPoint(d + 35, 2 * d + 35), QPoint(d + 25, 2 * d + 35));
paint.drawLine(QPoint(d + 35, 2 * d + 45), QPoint(d + 25, 2 * d + 45));
paint.drawLine(QPoint(d + 35, 2 * d + 45), QPoint(d + 35, 2 * d + 55));
在楚河汉界中,使用drawText函数进行在棋盘中写字,在棋盘的分界线上写出"楚河"和"汉界"几个字,然后棋盘更加丰富。使用setFont函数对棋盘的文字的字体进行修改,对于文字的大小进行修改,以及文字的粗细也可以进行修改,注意在这个文字中需要进行定位将文字放置与于正确的位置。
cpp
// 绘制出楚河汉界的文字
paint.setFont(QFont("SimSun", _r, 700)); // 设置文字的相关样式
paint.drawText(2 * d, 6 * d - 8, QStringLiteral("楚河"));
paint.drawText(7 * d - 10, 6 * d - 8, QStringLiteral("汉界"));
3.2 类的详细设计
中国象棋中最主要的两个类为:棋盘类和棋子类。这两个类进行结合,进行象棋游戏主题进行设置。
在棋子类中,需要创建的属性为:棋子的横坐标,棋子的纵坐标,棋子的类型,棋子是否已经死亡,棋子是否是红色的,棋子的id。因为在象棋中,棋子的种类只有7种,需要将棋子的种类更加的进行维护,所以在棋子类中创建了棋子的联合体。
在棋子类中,需要创建的行为有:将棋子的姓名和id进行绑定的函数,将棋子进行初始化的函数,将棋子的红黑方的坐标进行计算,该棋子类的声明代码如下:
cpp
Stone();
~Stone();
QString name();
void rotate();
void init(int id);
// 棋子的类型
enum TYPE{CHE, MA, PAO, BING, JIANG, SHI, XIANG};
// 棋子的类
int _row;
int _col;
TYPE _type;
bool _dead;
bool _red;
int _id;
在将棋子的姓名和id进行绑定的函数中,需要根据棋子中type属性进行绑定,所以使用swatch语句一一进行对应。在将棋子进行初始化的函数中,需要将棋子在棋盘中对应的坐标进行写入一个结构体中,方便利用循环进行初始化棋子。在计算棋子的红黑方的坐标函数中,需要使用一些对应的数学关系来进行计算,该棋子类函数的主要代码如下:
cpp
QString Stone::name()
{
switch(this->_type)
{
case CHE:
return "车";
case MA:
return "马";
case PAO:
return "炮";
case BING:
return "兵";
case JIANG:
return "将";
case SHI:
return "士";
case XIANG:
return "相";
}
return "错误";
}
在棋盘类中,需要创建的属性有:维护棋子的数组,被选中的棋子的id,棋子是否需要变红,棋子的半径。
在棋盘类中,需要创建的行为有:将棋盘的行列值转为坐标点的函数,绘制棋子函数,鼠标释放事件,绘制一个棋盘的函数,判断棋子是否可以移动的函数,判断棋子是够重合的函数,将棋子的坐标点转换为行列值的函数,返回棋盘行列对应的像素坐标的函数。
在这些函数中,需要进行一一详细地介绍:将棋盘的行列值转换为坐标点的函数中,这个函数的主要功能是判断鼠标点击的是哪一个棋子,然后返回棋子对应的坐标。
该函数的详细设计为:在这个函数中,首先传入获取到的鼠标的位置,之后,一个思路是循环遍历每一个棋盘上的点,进行计算,根据点与点之间的距离公式计算出鼠标点击的距离与以坐标为圆心的园的半径的比较,如果小于棋子的半径,则返回true,以及棋子的坐标;如果遍历完之后也没有,则返回false。这个思路是比较慢的,但是代码是比较容易实现。还有一个高效的算术,在获取到鼠标的行列值之后,直接进行除法运算,算出该行列值在主窗口中的位置,然后在与棋盘中的坐标进行比较,计算出该坐标。该函数的实现代码如下:
cpp
for(row = 0; row <= 9; row++)
{
for(col = 0; col <= 8; col++)
{
QPoint c = center(row, col);
int dx = c.x() - pt.x();
int dy = c.y() - pt.y();
int dist = dx * dx + dy * dy;
if(dist < _r * _r)
{
return true;
}
}
}
在返回棋盘行列对应的像素坐标的函数中,该函数的功能是:通过棋子的id进行将棋子的行列值计算出来,利用函数重载将调用接口进行简化,在这个函数中需要根据坐标计算出行列值。
在绘制棋子函数中,需要将绘制棋盘的画家通过引用的方式床底过去,因为在这个项目中绘制图像,基本都在paintEvent函数中,通过引用传递是为了让画家是同一个。该函数的主要功能是:绘画出棋子,对于已经死亡的棋子不进行绘制。
该函数的详细设计是:在这个函数中,首先进行判断棋子是否已经死亡,如果棋子已经死亡,则退出该函数;如果棋子没有死亡,则继续进行绘制。将传入的id通过center函数的转换,将棋子的坐标得出。然后以这个坐标为圆心进行绘制棋子。在绘制棋子的过程中,需要设置棋子的红黑颜色,通过棋子类中的属性来进行判断是用红笔画圆,还是使用黑笔画圆。根据棋子的类型,将棋子的名字写入,并设置字体的样式使字体更加好看。最后需要将选中的棋子的颜色进行更换,让其显示出来。其函数的代码如下:
cpp
if(_s[id]._dead) return;
QPoint c = center(id);
QRect rect = QRect(c.x() - _r, c.y() - _r, _r * 2, _r * 2);
if(id == _selectid)
painter.setBrush(QBrush(Qt::gray)); // 将被点击的棋子的颜色改变
else
painter.setBrush(QBrush(Qt::yellow));
painter.setPen(Qt::black);
painter.drawEllipse(center(id), _r, _r); // 画一个圆圈
if(_s[id]._red)
{
painter.setPen(Qt::red);
}
// 设置字体
painter.setFont(QFont("SimSun", _r, 700));
painter.drawText(rect, _s[id].name(), QTextOption(Qt::AlignCenter));
在鼠标释放事件的函数中,该函数的主要功能是:点击象棋,之后将被选中象棋的图像进行改变,判断出棋子是否是同一方的函数,如果不是同一方的函数,就可以进行移动棋子,如果是同一方的棋子,则将点击的棋子进行转移。
该函数的详细设计是:首先通过鼠标类型的变量,获取到鼠标点击的坐标。判断该坐标是否在棋盘中,并同时将鼠标点击的坐标计算出来。如果鼠标不在棋盘中,则退出函数,如果鼠标在棋盘中,则需要进行遍历这个棋子,看是哪一个棋子被选中,然后将id赋值给clickid。然后将clickid赋值给selectid,在这期间,需要将这次点击的是红方还是黑房进行判断。最后判断出可以移动棋子,则进行移动棋子,如果在移动棋子中,移动到棋子重合,在吃掉该棋子。然后将棋子的红黑双方进行更换,最后要记得使用update()更新画面,其代码如下:
cpp
QPoint pt = event->pos();
bool bRet = getRowCol(pt, row, col);
if(bRet == false) return ;
for(i = 0; i < 32; i++)
{
if(_s[i]._row == row && _s[i]._col == col && _s[i]._dead == false)
{
break; // 说明选中了
}
}
if(i < 32)
{
clickid = i;
}
// 判断是黑方还是红方
if(_s[clickid]._red)
qDebug("这是红方的棋子");
else
qDebug("这是黑方的棋子");
if(_selectid == -1)
{
if(clickid != -1)
{
if(_bRedTurn == _s[clickid]._red)
_selectid = clickid;
update(); // 将这个棋子显示出来
}
}
else
{
if(canMove(_selectid, row, col, clickid))
{
// 移动棋子
_s[_selectid]._row = row;
_s[_selectid]._col = col;
if(clickid != -1)
{
_s[clickid]._dead = true;
}
_selectid = -1;
_bRedTurn = !_bRedTurn;
update();
}
}
判断棋子是否可以移动的函数,在这个函数中,需要创建出7个相同的函数进行约束棋子的走法。该种类型的函数的主要功能是指定棋子移动的规则。
该函数的详细设计是:如果移动的颜色和killed的颜色相同,则将棋子的选择更换,否则返回false。其中设置一些swatch语句,用于将棋子的移动规则进行返回。其代码如下:
cpp
qDebug() << _s[moveid].name();AA
if(_s[moveid]._red == _s[killed]._red)
// 如果移动的颜色和killed的颜色相同
{
// 换选择
_selectid = killed;
update();
return false;
}
七种棋子的移动规则在前面已经详细地介绍了,现在需要一一根据移动的规则将代码设计出来:
首先设计一些将的移动代码:在将的移动代码中,需要先进行判断,如果是红方,则不能出去九宫格内,其坐标和黑方的坐标不一样,由表示的代码不同,所以需要使用不同的代码进行。将的走法每次只能走一格,且不能斜着走,所以移动的坐标和原本所在的坐标的差值只能是0和1,或者是1和0,其详细的代码如下:
cpp
if(_s[moveid]._red)
{
if(row > 2) return false;
}
else
{
if(row < 7) return false;
}
if(col < 3) return false;
if(col > 5) return false;
// 只能走一步
int dr = _s[moveid]._row - row;
int dc = _s[moveid]._col - col;
int d = abs(dr) * 10 + abs(dc);
if(d == 1 || d == 10)
return true;
return false;
士的走法和将的走法基本一样,只是士是斜着走的,而将是横着走和竖着走的。因此,只需将移动的坐标和原本所在的坐标的差值只能是1和1,或者是-1和1,或者是1和-1,或者是-1和-1。其详细的代码如下:
cpp
if(_s[moveid]._red)
{
if(row > 2) return false;
}
else
{
if(row < 7) return false;
}
if(col < 3) return false;
if(col > 5) return false;
// 只能走一步
int dr = _s[moveid]._row - row;
int dc = _s[moveid]._col - col;
int d = abs(dr) * 10 + abs(dc);
if(d == 11)
return true;
return false;
象的走法设计可以分为三步,首先象不能过河:在红方和黑方中的限制的坐标不一样;其次象是走田字,所以移动的坐标和原本所在的坐标的差值只能是2和2,或者是-2和2,或者是2和-2,或者是-2和-2。最后,如果有棋子在象眼处,则不能进行移动,需要在象眼处判断是否有棋子存在,其代码如下:
cpp
int rEye = (_s[moveid]._row + row) / 2;
int cEye = (_s[moveid]._col + col) / 2;
if(GetStoneId(rEye, cEye) != -1)
return false;
// 不能出界
if(_s[moveid]._red)
{
if(row > 4) return false;
}
else
{
if(row < 5) return false;
}
int dr = _s[moveid]._row - row;
int dc = _s[moveid]._col - col;
int d = abs(dr) * 10 + abs(dc);
if(d != 22)
return false;
return true;
马的移动规则有两条:首先马走日,所以移动的坐标和原本所在的坐标的差值只能是2和1,或者是-2和1,或者是1和-2,或者是-1和-2等。因此,其绝对值相加为21,或者12。最后,如果在马腿处有棋子存在的话,则马不能移动,需要在马腿处判断是否有棋子存在,其代码如下:
cpp
int dr = _s[moveid]._row - row;
int dc = _s[moveid]._col - col;
int d = abs(dr) * 10 + abs(dc);
if(d != 12 && d != 21)
return false;
int cd = col + _s[moveid]._col / 2;
int rd = row + _s[moveid]._row / 2;
int row1 = _s[moveid]._row;
int col1 = _s[moveid]._col;
if(d == 12)
{
if(GetStoneId(row1, (col+col1)/2) != -1)
return false;
}
else
{
if(GetStoneId((row+row1)/2, col1) != -1)
return false;
}
// 不能出界
if(row < 0) return false;
if(row > 9) return false;
if(col < 0) return false;
if(col > 8) return false;
return true;
炮的移动规则是比较麻烦的,在炮的移动过程中,只能横跨0个或1个棋子,所以需要判断出在炮的移动路径上存在棋子的个数,如果存在两个及两个以上的棋子,不能移动炮,反之则可以。先获取到所要移动的棋子的水平路线和垂直路线中的所有棋子,存储在一个map中,然后遍历map,返回其存储的值是多少,如果是0或者1则可以移动,否则不可以进行移动,其详细代码如下:
cpp
int ret = getStoneCountAtLine(row, col, _s[moveid]._row, _s[moveid]._col);
if(killed != -1)
{
if(ret == 1) return true;
}
else
{
if(ret == 0) return true;
}
return false;
车的移动规则更加简单,需要借助于炮中的思路,先获取到所要移动的棋子的水平路线和垂直路线中的所有棋子,存储在一个map中,然后遍历map,返回其存储的值是多少,如果是0则可以移动,否则不可以进行移动,其详细代码如下:
cpp
int ret = getStoneCountAtLine(row1, col1, row, col);
if(ret == 0) return true;
return false;
兵的走棋规则分为两个部分:在未出自己方的分界线时,兵只能向前走。需要将向左向右向后走的路线给封锁,在这一部分中,兵每次只能走一格,所以可以复用将行走的代码,因此移动的坐标和原本所在的坐标的差值只能是0和1,或者是1和0。在进入到对方的棋盘中,兵可以进行向左向右向前走,其代码如下:
cpp
if(row < 0) return false;
if(row > 9) return false;
if(col < 0) return false;
if(col > 8) return false;
if(_s[moveid]._red) // 如果是红方
{
if(row <= 4)
{
if(dr <= 0 && dc == 0) return true;
}
else
{
if(d == 1 || d == 10)
{
if(dr <= 0) return true;
}
}
}
else // 如果是黑方的
{
if(row >= 5)
{
if(dr >= 0 && dc == 0) return true;
}
else
{
if(d == 1 || d == 10)
{
if(dr >= 0) return true;
}
}
}
return false;
在判断棋子是够重合的函数中,只需遍历32个棋子,看是否存在棋子和所选择的棋子重合并且不能是死亡状态,如果存在则返回该棋子对应的id,否则返回-1。
五、系统测试
5.1 主页面测试
当我们启动Qt程序之后,首先映入眼帘的就是一个主页面,主页面中有两个按钮,一个按钮是进入象棋游戏页面,一个按钮是退出象棋游戏页面。点击这两个按钮分别有不同的事件发生,在点击"进入象棋游戏"按钮会直接跳转到象棋的页面;在点击"退出象棋游戏"按钮会关闭页面,如图4.1所示;并在控制台输出退出代码0,如图4.2所示。
4.2 游戏页面测试
在进入游戏页面之后,我们发现有红方和黑方。中国象棋的规则是红方先下,然后黑方才能下棋。在之后就是红方下一次,黑方下一次。通过点击棋子,在控制台中会发生点击的是什么颜色的棋子来判断只能点击红色方棋子才能选中,测试结果如图4.3所示。
之后,就是检验各种棋子的移动规则,在这里我们使用将棋子原本坐标和棋子要移动的坐标进行打印出来,根据棋子的移动规则约束,我们可以进行验证棋子的移动是否正确,如果棋子的移动不正确,则在棋盘中,该棋子不会进行移动;而如果对应上棋子的移动规则,则棋子可以进行移动。通过在控制台中打印出现在移动的是哪个棋子,棋子没有移动前的坐标和想要棋子移动的坐标进行判断,测试结果如图4.4所示。
游戏结束时,该程序会弹出一个"胜利"的消息对话框,当有一方的将被吃掉后,该程序就会弹出该消息对话框,测试结果如图4.5所示。
六、总结
在完成该课设之后,学习到了很多,学习了如何使用Qt来创建多个窗口,并进行绘制图像,如何使用信号槽机制将按钮和窗口结合起来,如何创建一个消息对话框,也学到了如何使用函数将鼠标点击事件和窗口进行交互。但是这个程序中,还是有很多不足的点:没有引入Alpha-Beta算法,没有实现人机对站;没有实现悔棋功能,没有实现网络服务,进行联网操作。因此,在之后的学习中,还是要学习更多的内容,在之后进行重新对该程序进行修改,以至于可以完成上述各项内容。这个小型项目的完成帮助进行了更深层次的了解项目的组成,为之后的项目的学习奠定了基础。