基于QT实现的中国象棋
源码在文章末尾,开源自提
1. 课程设计的目的
了解和掌握C++语言的基本语法和面向对象编程思想,同时通过实践来加深对C++语言的理解和应用。具体目标是实现一个简单的中国象棋游戏,包括界面设计、程序逻辑实现和人机博弈功能。通过完成这个课设,可以提高自己的编程能力,能够利用所学的基本知识和技能,解决简单的面向对象程序设计问题,能够熟练地使用C++的特点继承,封装,多态,加深对面向对象编程思想的理解,同时也为学习其他高级语言和开发其他复杂应用程序打下坚实的基础。
2.课程设计的要求
- 界面设计:实现一个简单的游戏界面,用户通过该界面选择游戏模式,开始游戏,悔棋,重新开始,计时等。
- 游戏规则:包括棋盘的布局,棋子的移动,吃棋子的规则,判断是否被将军的规则,以及输赢判断的规则。
- 人工智能算法:实现一个基础的AI算法,让计算机具有一定的智能,算两步以内的走法,在一定程度上协助用户提升游戏体验度。
- 数据结构:使用C++数据结构和面向对象程序设计思想,实现棋盘,棋子等对象,并进行一定程度的封装,以提高代码的复用和可读性。
- 程序性能和稳定:保证程序的性能和稳定性,防止程序出现崩溃和卡顿等问题,尤其是AI部分。
- 代码规范:要求程序代码设计规范,注释清晰,模板化设计,易于理解和维护。
3、项目亮点
主要亮点:
在实现AI算法的计算步数时,使用了剪枝优化算法。
图解:
4.课程设计报告内容
4.1 整体的游戏的流程图
4.2 游戏整体的类层次结构图
4.3 重点代码描述
1)类与类之间的关系
Qobject与QpaintDevice是基类,Voice,Step,Qwidget是其的派生类,其中Qwidget具有多继承,而QDailog,Board是前面派生类的派生类,ChooseMain和HMMode是强两个派生类的派生类。整个UML图中自己创建的类有Voice,Step,Board,ChooseMain,HMMode,其中Board类属于是核心类。这上图中既有多继承也有单继承,同还具有多层继承。这在很大程度上实现了代码的重用,将类的概念按照逻辑层次分层,便于组织管理代码。
Board类作为自己编写的核心类,内部主要定义了从画棋盘一直到判定游戏结束的核心代码,以下就是具体的核心代码的描述:
2)棋盘,棋子的绘制方法
主要定义了
paintEvent(QPaintEvent *),
drawInitPosition(QPainter &p, int row, int col),
drawStone(QPainter& painter, int id),
这三个函数以及他们的部分重载函数,通过QT所提供的类中的
drawLine(),
drawText(),
drawEllipse()
等方法实现绘制棋盘,棋盘上的楚河汉界字样,以及棋子和棋子的名字。棋子的类型则是通过枚举的方式列举出。
3)棋子的走步规则
int Board::movableRange(int row1, int col1, int row, int col){
//该函数是各类棋子行走的限定位置作用的。
return abs(row1-row)*10+abs(col1-col);}
int Board::getStoneCountAtLine(int row1, int col1, int row2, int col2 ) //计算两点之间有几个棋子
bool Board::isBottomSide(int id){
//用来判断是否是底部
return _bSide == _s[id]._red;}
//该函数主移动函数,以下都要先进入该函数判断是否是同色和棋子的类型在具体调用以下具体的走步函数
bool canMove(int moveid, int row, int col, int killid);
//该函数函数是限定将军的走法,将军除了飞将的情况下,只能在宫格内以步长为一的限定行走,其行走规则满足通过
//movableRange计算出来等于1或10的,并且在一定范围之内移动。将军的飞将功能就是通过下面车走步实现的。
bool canMoveJIANG(int moveid, int row, int col, int killid);
//该函数是限定士的走法的,通过movableRange计算出来等于11的位置移动,其范围的限定和将军一样。
bool canMoveSHI(int moveid, int row, int col, int killid);
//该函数是限定相的走步,通过movableRange计算出来等于22的位置移动,并且还需要考虑是否有相脚,相脚的计算是通过
//getStoneId(int row, int col)函数去判断某个位置是否有棋子的方式,去判断是否有相脚。
bool canMoveXIANG(int moveid, int row, int col, int killid);
//该函数是限定相的走步,通过movableRange计算出来等于12或者21的位置移动,并且还需要考虑是否有马脚,马脚的计算也
//是通过getStoneId(int row, int col)函数去判断某个位置是否有棋子的方式,去判断是否有马脚。
bool canMoveMA(int moveid, int row, int col, int killid);
//该函数是限定车的走法的,通过getStoneCountAtLine(int row1, int col1, int row, int col)去计算两点之间是否有棋子,
//没有则是指哪去哪。
bool canMoveCHE(int moveid, int row, int col, int killid);
//该函数是限定炮的走法的,通过getStoneCountAtLine(int row1, int col1, int row, int col)去计算两点之间是否有棋子,
//没有则是指哪去哪。如果有,当且仅当只有一个并且有敌人可以吃的情况下可以走动。
bool canMovePAO(int moveid, int row, int col, int killid);
//该函数是限定兵的走法的,通过movableRange去限定步长和位置,兵在没有过河之前只能向前走,
//过了河之后可以左右,前进,就是不能向后走。
bool canMoveBING(int moveid, int row, int col, int killid);
4)鼠标点击事件——>本身就是一个虚函数,通过在自定义重载的方式重新实现点击功能
void Board :: mouseReleaseEvent(QMouseEvent *ev){
click(ev->pos());
}
5)点击选棋子,移动棋子,吃棋子
//主要是判断鼠标点击的位置是否有棋子
void Board::click(QPoint pt) {
int row, col;
bool bRet = isChecked(pt,row,col);
if(!bRet)
return;
clickStone(getStoneId(row, col)