c++ course design--chess based on QT implementation

Chinese chess implemented based on QT

源码在文章末尾,开源自提

1. Purpose of course design

Understand and master the basic syntax and object-oriented programming ideas of C++ language, and at the same time deepen the understanding and application of C++ language through practice. The specific goal is to realize a simple Chinese chess game, including interface design, program logic implementation and human-computer gaming functions. By completing this course, you can improve your programming skills, be able to use the basic knowledge and skills you have learned to solve simple object-oriented programming problems, and be able to skillfully use the features of C++ such as inheritance, encapsulation, and polymorphism to deepen your understanding of object-oriented programming. The understanding of programming ideas also lays a solid foundation for learning other high-level languages ​​and developing other complex applications.

2. Requirements for course design

  1. Interface design: Implement a simple game interface through which the user selects the game mode, starts the game, regrets the game, restarts, timing, etc.
  2. Game rules: including the layout of the chessboard, the movement of chess pieces, the rules for capturing chess pieces, the rules for judging whether to be defeated, and the rules for winning or losing.
  3. Artificial Intelligence Algorithm: Implement a basic AI algorithm to allow the computer to have a certain level of intelligence and calculate moves within two steps, helping users improve the gaming experience to a certain extent.
  4. Data structure: Use C++ data structures and object-oriented programming ideas to implement objects such as chessboards and chess pieces, and encapsulate them to a certain extent to improve code reuse and readability.
  5. Program performance and stability: Ensure the performance and stability of the program and prevent problems such as program crashes and lags, especially the AI ​​part.
  6. Code specifications: Program code design is required to be standardized, with clear comments, templated design, and easy to understand and maintain.

3. Project highlights

Main highlights:
When implementing the calculation steps of the AI ​​algorithm, the pruning optimization algorithm is used.
Illustration:
Insert image description here
Insert image description here

4. Contents of course design report

4.1 Overall game flow chart

Insert image description here

4.2 Overall class hierarchy diagram of the game

Insert image description here

4.3 Key code description

1) The relationship between classes

Qobject and QpaintDevice are base classes, and Voice, Step, and Qwidget are their derived classes. Qwidget has multiple inheritance, while QDailog and Board are derived classes from the previous derived classes. ChooseMain and HMMode are derived classes from the two derived classes. The classes you created in the entire UML diagram include Voice, Step, Board, ChooseMain, and HMMode, among which the Board class is the core class. In the picture above, there are both multiple inheritance and single inheritance, as well as multi-level inheritance. This achieves code reuse to a large extent, layering the concepts of classes according to logical levels, making it easier to organize and manage code.
As the core class written by myself, the Board class mainly defines the core code from drawing the chessboard to determining the end of the game. The following is a description of the specific core code:

2) How to draw chessboard and chess pieces

Mainly defined

paintEvent(QPaintEvent *)drawInitPosition(QPainter &p, int row, int col)drawStone(QPainter& painter, int id)

These three functions and their partially overloaded functions are passed through the classes provided by QT

drawLine()drawText()drawEllipse()

Methods such as this are used to draw the chessboard, the words Chuhehanjie on the chessboard, as well as the chess pieces and their names. The types of chess pieces are listed through enumeration.

3) Rules for moving chess pieces
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) Mouse click event -> itself is a virtual function, and the click function is re-implemented through custom overloading

void Board :: mouseReleaseEvent(QMouseEvent *ev){
    
    
    click(ev->pos());
}

5) Click to select chess pieces, move chess pieces, and capture chess pieces

//主要是判断鼠标点击的位置是否有棋子
void Board::click(QPoint pt) {
    
    
    int row, col;
    bool bRet = isChecked(pt,row,col);
    if(!bRet)
        return;
    clickStone(getStoneId(row, col), row, col);
}

//主要是先具体的点击走棋功能
void Board::clickStone(int clickid, int row, int col){
    
    
    if(_selectid == -1)
        seleteStone(clickid);
    else
        testMoveStone(clickid, row, col);
}

//主要实现选棋子
void Board::seleteStone(int id)
{
    
    
    if(id == -1 )return;
    if(!isBeSeleted(id))return;
    _selectid = id;
    update();
}

//判断选中的是否是回合内的棋子
bool Board::isBeSeleted(int id){
    
    
    return _bRedTurn == _s[id]._red;
}

//实现棋子的移动在坐标上的变化
void Board::moveStone(int moveid, int row, int col){
    
    
    _s[moveid]._row = row;
    _s[moveid]._col = col;
    _bRedTurn = !_bRedTurn;
}

//具体的实现移动逻辑过程
void Board::moveStone(int moveid, int killid, int row, int col){
    
    
    saveStep(moveid, killid, row, col, _steps);
    killStone(killid);
    moveStone(moveid, row, col);
    whoWin();
    if(killid== -1)
        _voice.voiceMove(); //移动音效
    else
        _voice.voiceEat(); //吃子音效
    if(isGeneral(_bRedTurn)){
    
    
        if(isHongMen() != 0){
    
    
            _voice.voiceGeneral();
            if(!isHaveAntherStep()){
    
    
                if(!_bRedTurn){
    
    
                    winMessageBox("提示", "绝杀!!!红方胜利");
                }
                    winMessageBox("提示", "绝杀!!!黑方胜利");
            }
        }
        else
            backOne();
    }
    if(isGeneral()){
    
    
        backOne();
    }
}

//主要是实现移动棋子
void Board::testMoveStone(int killid, int row, int col){
    
    
    if(killid != -1 && isSameColor(_selectid, killid)){
    
    
        seleteStone(killid);
        return;
    }
    if(canMove(_selectid,  row, col, killid)){
    
    
        moveStone(_selectid, killid, row, col);
        _selectid = -1;
        update();
    }
}

//吃棋子功能
void Board::killStone(int id){
    
    
    if(id==-1) return;
    _s[id]._dead = true;
}

//判断选中的棋子与要吃的棋子是否是相同颜色。
bool Board::isSameColor(int moveid, int killid){
    
    
    if(killid == -1 || moveid == -1)return false;
    return isRed(moveid)== isRed(killid);
}

6) Regret function, save moves

//悔棋的逻辑方法
void Board::back(Step *step){
    
    
    /*
     * 1.复活死去的棋子
     * 2.棋子往回移动一步
     */
    reliveStone(step->_killid);
    moveStone(step->_moveid, step->_rowFrom, step->_colFrom);
}

//悔一步棋
void Board::backOne(){
    
    
    if(this->_steps.size() == 0) return;
    Step* step = this->_steps.last();
    _steps.removeLast();
    back(step);
    update();
    delete step;
}

//悔棋
void Board::back(){
    
    
    backOne();
}

//复活棋子
void Board::reliveStone(int id){
    
    
    if(id==-1) return;
    _s[id]._dead = false;
}

//保存通过Qvector存放移动棋子的位置
void Board::saveStep(int moveid, int killid, int row, int col, QVector<Step*>& steps){
    
    
    GetRowCol(row1, col1, moveid);
    Step* step = new Step;
    step->_colFrom = col1;
    step->_colTo = col;
    step->_rowFrom = row1;
    step->_rowTo = row;
    step->_moveid = moveid;
    step->_killid = killid;
    steps.append(step);
}

7) Determine whether the chess piece can be moved by being general, and the Hongmen Banquet cannot occur, and all chess pieces are obtained, and whether there are still pieces that can be moved in the status of general.

//计算两个将军之间有没有棋子间接证明是否处于鸿门宴
int Board::isHongMen(){
    
    
    if(_s[4]._dead || _s[20]._dead)
        return 0;
    return getStoneCountAtLine(_s[4]._row, _s[4]._col, _s[20]._row, _s[20]._col);
}

//判断自己走的棋子是否会让自己将军被将军
bool Board::isGeneral(){
    
    
    int generalId=20;
        if(!_bRedTurn)
            generalId=4;
        int row= _s[generalId]._row;
        int col= _s[generalId]._col;
        for(int i=0; i<32; ++i){
    
    
            if(canMove(i,row,col,generalId) && !_s[i]._dead){
    
    
                return true;
            }
        }
        return false;
}

//判断移动的棋子是否会让对方将军处于被将军的状态
bool Board:: isGeneral(bool _bRedTurn){
    
    
    int generalId; 
    if(_bRedTurn){
    
    
         generalId=4;
         int row= _s[generalId]._row;
         int col= _s[generalId]._col;
         for(int i=0; i<32; ++i)
             if((canMove(i,row,col,generalId) && !_s[i]._dead)){
    
    
                 return true;
             }
    }else{
    
    
        generalId=20;
        int row= _s[generalId]._row;
        int col= _s[generalId]._col;
        for(int i=0; i<32; ++i)
            if((canMove(i,row,col,generalId) && !_s[i]._dead)){
    
    
                return true;
            }
    }
    return 0;
}

//拿到红黑棋的所有走法的可能。
void Board::getAllPossibleMove(QVector<Step *> &steps1){
    
    
    int min, max;
    if(this->_bRedTurn){
    
    
        min = 0, max = 16;
    }else{
    
    
        min = 16, max = 32;
    }
    for(int i=min;i<max; i++){
    
    
        if(this->_s[i]._dead) continue;
        for(int row = 0; row<=9; ++row){
    
    
            for(int col=0; col<=8; ++col){
    
    
                int killid = this->getStoneId(row, col);
                if(isSameColor(i, killid)) continue;
                if(canMove(i, row, col, killid)){
    
    
                    Board::saveStep(i, killid, row, col, steps1);
                }
            }
        }
    }
}

//判断拿到的所有棋子走法的可能,判断是否在移动后不被将军
bool Board :: isHaveAntherStep(){
    
    
    QVector<Step *> steps1;
    bool isGen = false;
    if(_bRedTurn){
    
    
        getAllPossibleMove(steps1);
        for(QVector<Step*> :: iterator it = steps1.begin();it != steps1.end();it++){
    
    
            Step* step = *it;
            fakeMove(step);
            if(isGeneral()){
    
    
                unFakeMove(step);
                continue;
            }
            else{
    
    
                isGen = true;
                unFakeMove(step);
            }
        }
    }else {
    
    
        getAllPossibleMove(steps1);
        for(QVector<Step*> :: iterator it = steps1.begin();it != steps1.end();it++){
    
    
            Step* step = *it;
            fakeMove(step);
            if(isGeneral()){
    
    
                unFakeMove(step);
                continue;
            }
            else{
    
    
                isGen = true;
                unFakeMove(step);
            }
        }
    }
    return isGen;
}


//试探性的走一步
void Board::fakeMove(Step *step){
    
    
    killStone(step->_killid);
    moveStone(step->_moveid, step->_rowTo, step->_colTo);
}

//悔试探性走的棋子
void Board::unFakeMove(Step *step){
    
    
    reliveStone(step->_killid);
    moveStone(step->_moveid, step->_rowFrom, step->_colFrom);
}

8) Timing, start game, pause, restart, return to menu signals and slots, and connections.

//实现时间的更新
void updateTime();

//连接,四个参数分别是发送者,信号,接收者,槽
connect(_timer,SIGNAL(timeout()),this,SLOT(updateTime()));
   ui->setupUi(this);

//实现点击开始按键
void on_pushButton_start_clicked();

//实现点击重置按键
void on_pushButton_reset_clicked();

//实现重新开始按键
void on_pushButton_restart_clicked();

//实现悔棋按键
void on_pushButton_back_clicked();

//实现返回菜单按键
void on_pushButton_toMenu_clicked();

9) The HMMode class that implements simple AI has only the following functions since it inherits the internal functions:

//重载父类中的该方法从而实现选棋子,走棋子的功能
void HMMode::clickStone(int checkedID, int row, int col)

//计算最好的局面分
int HMMode::calcScore()

//拿到最好的移动位置
Step* HMMode::getBestMove()

//拿到对机器最小损失的位置
int HMMode::getLossMinScore()

4.4 Experimental results

1) Select game mode

Insert image description here

Figure 3-3

2) Enter two-player mode

Insert image description here

Figure 3-4

3) Start the game

Insert image description here

Figure 3-5

3) Determine winning or losing

Insert image description here

Figure 3-6

4) Return to the main menu by clicking

Insert image description here

Figure 3-7

5) Enter stand-alone mode

Insert image description here

Figure 3-8

6) Start the game

Insert image description here

Figure 3-9

4.5 Design summary

Team member name: XXXX
Through this course design, I have a deeper understanding of the use of qt, and mastered the basic syntax and object-oriented programming ideas of the C++ language. At the same time, I will deepen my understanding and application of the C++ language through practice. Through this course, I have improved my programming ability to a certain extent. I can use the basic knowledge and skills I have learned to solve simple object-oriented programming problems. I can skillfully use the characteristics of C++ such as inheritance, encapsulation, polymorphism, and deepening. The understanding of object-oriented programming ideas also lays a solid foundation for learning other high-level languages ​​and developing other complex applications.
I also encountered a lot of setbacks during this process. The problems were like climbing over mountains and ridges, one after another. The main ones that were more difficult and took longer were the following:

question:

1) When designing a tell, I simply think that the legs are designed in the same way, and ignore that the legs are the center point no matter what. Because the legs are not the center point, the two situations cannot be discussed in the same branch.

Solution: Both situations need to be discussed separately.

2) When designing the chess pieces whose general status cannot be moved and cannot remove the checked status, at first we only considered that moving the chess pieces can cause the opponent to be checked, but we forgot that moving the chess pieces may also cause oneself to be checked. As a result, the checked piece can always move other chess pieces that cannot be released.

Solution: Overload the original general judgment function without parameters to judge whether moving the chess piece will cause the general to be in a general state, so as to limit the choice of chess moves through this function.

3) When designing the decisive game to judge winning or losing, I always thought that as long as one move of the chess piece does not remove the general, there is no way to go. As a result, I kept making mistakes in judgment, but I forgot that there may be more than one chess piece that can be moved. There was no good application of AI at that time. The suggested algorithm is to write a function to get the possible moves of all chess pieces, and then use iterative traversal to determine whether there are any chess pieces that can be moved.

Solution: Based on the function originally written to get all the possible moves of chess pieces, write a function that iteratively traverses to find out whether there are any chess pieces that can be moved.

4) When the AI ​​​​simply calculates the steps, due to carelessness, the IDs of the red and black chess pieces were swapped, and it was mistakenly thought that 0-15 were black chess pieces. As a result, the AI ​​​​played chess at the beginning and moved all its own chess pieces, while black moved No.

Solution: Reverse the ID judgment as follows.

The above is the part of the course that takes a long time, and it is also the time when I scratch my head the most. Through this course, I exposed the shortcomings of my thinking that is not divergent enough. I keep turning around on a dead end, but I forget to take a step back. Thinking about problems, while increasing my experience, also made me more patient and enjoyed the debugging process more. The pleasure after successful debugging added a good scenery to my future learning.

references

[1] Ma Shian. Object-oriented Programming Tutorial (C++ Language Description) (3rd Edition). Beijing: Tsinghua University Press, 2018.
[2] Hu Ran. C++ QT Programming Engineering Training Tutorial. Beijing: Electronic Industry Publishing Society, 2018.6
[3] Wang Xiaochun. PC Game Programming: Human-Computer Game. Chongqing: Chongqing University Press, 2018.6

Appendix: Source program

https://gitee.com/z_nuyoah/chinese_-chess

Guess you like

Origin blog.csdn.net/qq_52495761/article/details/134610559