【Qt课设】基于Qt实现的中国象棋

news2024/12/28 20:44:50

一、摘 要 

       本报告讨论了中国象棋程序设计的关键技术和方法。首先介绍了中国象棋的棋盘制作,利用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所示。下面利用这个信号将其按钮与游戏界面进行关联。

       其函数的代码如下,将开始界面类与游戏界面进行关联,进行判断,当有鼠标点击事件发生,需要进行判断,然后将游戏界面类进行显示;如果判断没有鼠标点击事件发生,则不会进入主窗口,整个程序结束运行。

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条竖线不能越过这个分界线,其主要代码如下:

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函数创建出九宫格,找出这个点的坐标,然后将其进行连接,其主要代码如下:

// 画出九宫格

// 还是用直线来进行画出九宫格

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函数进行绘制,也是主要是要找对点的位置,需要进行排布点的位置,其主要代码如下:

// 画出棋盘中的准星

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函数对棋盘的文字的字体进行修改,对于文字的大小进行修改,以及文字的粗细也可以进行修改,注意在这个文字中需要进行定位将文字放置与于正确的位置。

// 绘制出楚河汉界的文字

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进行绑定的函数,将棋子进行初始化的函数,将棋子的红黑方的坐标进行计算,该棋子类的声明代码如下:  

  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语句一一进行对应。在将棋子进行初始化的函数中,需要将棋子在棋盘中对应的坐标进行写入一个结构体中,方便利用循环进行初始化棋子。在计算棋子的红黑方的坐标函数中,需要使用一些对应的数学关系来进行计算,该棋子类函数的主要代码如下:

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。这个思路是比较慢的,但是代码是比较容易实现。还有一个高效的算术,在获取到鼠标的行列值之后,直接进行除法运算,算出该行列值在主窗口中的位置,然后在与棋盘中的坐标进行比较,计算出该坐标。该函数的实现代码如下:

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函数的转换,将棋子的坐标得出。然后以这个坐标为圆心进行绘制棋子。在绘制棋子的过程中,需要设置棋子的红黑颜色,通过棋子类中的属性来进行判断是用红笔画圆,还是使用黑笔画圆。根据棋子的类型,将棋子的名字写入,并设置字体的样式使字体更加好看。最后需要将选中的棋子的颜色进行更换,让其显示出来。其函数的代码如下:

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()更新画面,其代码如下:

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语句,用于将棋子的移动规则进行返回。其代码如下:

qDebug() << _s[moveid].name();AA

if(_s[moveid]._red == _s[killed]._red)

// 如果移动的颜色和killed的颜色相同

    {

        // 换选择

        _selectid = killed;

        update();

        return false;

    }

       七种棋子的移动规则在前面已经详细地介绍了,现在需要一一根据移动的规则将代码设计出来:

       首先设计一些将的移动代码:在将的移动代码中,需要先进行判断,如果是红方,则不能出去九宫格内,其坐标和黑方的坐标不一样,由表示的代码不同,所以需要使用不同的代码进行。将的走法每次只能走一格,且不能斜着走,所以移动的坐标和原本所在的坐标的差值只能是0和1,或者是1和0,其详细的代码如下:

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。其详细的代码如下:

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。最后,如果有棋子在象眼处,则不能进行移动,需要在象眼处判断是否有棋子存在,其代码如下:

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。最后,如果在马腿处有棋子存在的话,则马不能移动,需要在马腿处判断是否有棋子存在,其代码如下:

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则可以移动,否则不可以进行移动,其详细代码如下:

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则可以移动,否则不可以进行移动,其详细代码如下:

int ret = getStoneCountAtLine(row1, col1, row, col);

    if(ret == 0)  return true;

    return false;

       兵的走棋规则分为两个部分:在未出自己方的分界线时,兵只能向前走。需要将向左向右向后走的路线给封锁,在这一部分中,兵每次只能走一格,所以可以复用将行走的代码,因此移动的坐标和原本所在的坐标的差值只能是0和1,或者是1和0。在进入到对方的棋盘中,兵可以进行向左向右向前走,其代码如下:

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算法,没有实现人机对站;没有实现悔棋功能,没有实现网络服务,进行联网操作。因此,在之后的学习中,还是要学习更多的内容,在之后进行重新对该程序进行修改,以至于可以完成上述各项内容。这个小型项目的完成帮助进行了更深层次的了解项目的组成,为之后的项目的学习奠定了基础。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1912734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python:安装/Mac

之前一直陆陆续续有学python&#xff01;今天开始&#xff01;正式开肝&#xff01;&#xff01;&#xff01; 进入网站&#xff1a;可能会有点慢&#xff0c;多开几个网页 https://www.python.org 点击下载&#xff0c;然后进入新的页面&#xff0c;往下滑 来到File&#xff0…

PHP验证日本免费电话号码格式

首先&#xff0c;您需要了解免费电话号码的格式。 日本免费电话也就那么几个号段&#xff1a;0120、0990、0180、0570、0800等开头的&#xff0c;0800稍微特殊点&#xff0c;在手机号里面有080 开头&#xff0c;但是后面不一样了。 关于免费电话号码的划分&#xff0c;全部写…

忘记Apple ID密码怎么退出苹果ID账号?

忘记Apple ID密码怎么退出账号&#xff1f;Apple ID对每个苹果用户来说都是必不可少的&#xff0c;没有它&#xff0c;用户就不能享受iCloud、App Store、iTunes等服务。苹果手机软件下载、丢失解锁、恢复出厂设置等都需要使用Apple ID。如果忘记Apple ID 密码&#xff0c;这会…

Linux——多线程(五)

1.线程池 1.1初期框架 thread.hpp #include<iostream> #include <string> #include <unistd.h> #include <functional> #include <pthread.h>namespace ThreadModule {using func_t std::function<void()>;class Thread{public:void E…

九、Linux二进制安装ElasticSearch集群

目录 九、Linux二进制安装ElasticSearch集群1 下载2 安装前准备(单机&#xff0c;集群每台机器都需要配置)3 ElasticSearch单机&#xff08;7.16.2&#xff09;4 ElasticSearch集群&#xff08;8.14.2&#xff09;4.1 解压文件&#xff08;先将下载文件放到/opt下&#xff09;4…

Java系列-valitile

背景 volatile这个关键字可以说是面试过程中出现频率最高的一个知识点了&#xff0c;面试官的问题也是五花八门&#xff0c;各种刁钻的角度。之前也是简单背过几道八股文&#xff0c;什么可见性&#xff0c;防止指令重拍等&#xff0c;但面试官一句&#xff1a;volatile原理是什…

Vue基础--v-model/v-for/事件属性/侦听器

目录 一 v-model表单元素 1.1 v-model绑定文本域的value 1.1.1 lazy属性&#xff1a;光标离开再发请求 1.1.2 number属性&#xff1a;如果能转成number就会转成numer类型 1.1.3 trim属性&#xff1a;去文本域输入的前后空格 1.2v-model绑定单选checkbox 1.3代码展示 二 …

Python OpenCV 教学取得视频资讯

这篇教学会介绍使用OpenCV&#xff0c;取得影像的长宽尺寸、以及读取影像中某些像素的颜色数值。 因为程式中的OpenCV 会需要使用镜头或GPU&#xff0c;所以请使用本机环境( 参考&#xff1a;使用Python 虚拟环境) 或使用Anaconda Jupyter 进行实作( 参考&#xff1a;使用Anaco…

基于单片机的温湿度感应智能晾衣杆系统设计

&#xff3b;摘 要&#xff3d; 本设计拟开发一种湿度感应智能晾衣杆系统 &#xff0c; 此新型晾衣杆是以单片机为主控芯片 来控制的实时检测系统 &#xff0e; 该系统使用 DHT11 温湿度传感器来检测大气的温湿度 &#xff0c; 然后通过单 片机处理信息来控制 28BYJ &…

配置路由器支持Telnet操作 计网实验

实验要求&#xff1a; 假设某学校的网络管理员第一次在设备机房对路由器进行了初次配置后&#xff0c;他希望以后在办公室或出差时也可以对设备进行远程管理&#xff0c;现要在路由器上做适当配置&#xff0c;使他可以实现这一愿望。 本实验以一台R2624路由器为例&#xff0c;…

使用 Hugging Face 的 Transformers 库加载预训练模型遇到的问题

题意&#xff1a; Size mismatch for embed_out.weight: copying a param with shape torch.Size([0]) from checkpoint - Huggingface PyTorch 这个错误信息 "Size mismatch for embed_out.weight: copying a param with shape torch.Size([0]) from checkpoint - Hugg…

Redis管理禁用命令

在redis数据量比较大时&#xff0c;执行 keys * &#xff0c;fluashdb 这些命令&#xff0c;会导致redis长时间阻塞&#xff0c;大量请求被阻塞&#xff0c;cpu飙升&#xff0c;严重可能导致redis宕机&#xff0c;数据库雪崩。所以一些命令在生产环境禁止使用。 Redis 禁用命令…

开始尝试从0写一个项目--前端(二)

修改请求路径的位置 将后续以及之前的所有请求全都放在同一个文件夹里面 定义axios全局拦截器 为了后端每次请求都需要向后端传递jwt令牌检验 ps&#xff1a;愁死了&#xff0c;翻阅各种资料&#xff0c;可算是搞定了&#xff0c;哭死~~ src\utils\request.js import axio…

【QML之·基础语法概述】

系列文章目录 文章目录 前言一、QML基础语法二、属性三、脚本四、核心元素类型4.1 元素可以分为视觉元素和非视觉元素。4.2 Item4.2.1 几何属性(Geometry&#xff09;:4.2.2 布局处理:4.2.3 键处理&#xff1a;4.2.4 变换4.2.5 视觉4.2.6 状态定义 4.3 Rectangle4.3.1 颜色 4.4…

互联网3.0时代的变革者:华贝甄选大模型创新之道

在当今竞争激烈的商业世界中&#xff0c;华贝甄选犹如一颗璀璨的明星&#xff0c;闪耀着独特的光芒。 华贝甄选始终将技术创新与研发视为发展的核心驱动力。拥有先进的研发团队和一流设施&#xff0c;积极探索人工智能、大数据、区块链等前沿技术&#xff0c;为用户提供高性能…

Knife4j的介绍与使用

目录 一、简单介绍1.1 简介1.2 主要特点和功能&#xff1a; 二、使用步骤&#xff1a;2.1 添加依赖&#xff1a;2.2 yml数据源配置2.3 创建knife4j配置类2.4 注解的作用 最后 一、简单介绍 1.1 简介 Knife4j 是一款基于Swagger的开源文档管理工具&#xff0c;主要用于生成和管…

【PTA天梯赛】L1-003 个位数统计(15分)

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法刷题 &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录 题目题解总结 题目 题目链接 题解 使用string把长度达1000位的数字存起来开一个代表个位数的数组 a[11]倒序计算最后一位&#xff0c;…

第16章 主成分分析:四个案例及课后习题

1.假设 x x x为 m m m 维随机变量&#xff0c;其均值为 μ \mu μ&#xff0c;协方差矩阵为 Σ \Sigma Σ。 考虑由 m m m维随机变量 x x x到 m m m维随机变量 y y y的线性变换 y i α i T x ∑ k 1 m α k i x k , i 1 , 2 , ⋯ , m y _ { i } \alpha _ { i } ^ { T } …

从微软 Word 中提取数据

从 Microsoft Word 文档中提取数据可以通过编程来实现&#xff0c;有几种常见的方法&#xff0c;其中之一是使用 Python 和 python-docx 库。python-docx 是一个处理 .docx 文件&#xff08;Microsoft Word 文档&#xff09;的 Python 库&#xff0c;可以读取和操作 Word 文档的…

泛微开发修炼之旅--36通过js控制明细表中同一列中多个浏览框的显示控制逻辑(明细表列中多字段显示逻辑控制)

文章链接&#xff1a;36通过js控制明细表中同一列中多个浏览框的显示控制逻辑&#xff08;明细表列中多字段显示逻辑控制&#xff09;