QT小游戏——中国象棋

  • 前言
  • 项目整体结构
  • 棋子类
  • 棋盘类
  • 几个重要方法
  • 1.坐标转化
  • 2.移动规则
  • 3.悔棋和撤销
  • 项目文件


前言

最近用qt做了个中国象棋的小游戏。

目前只能左右互博,支持悔棋。效果图如下,

Python Pygame 中国象棋 单机版源码 中国象棋单机小游戏_置数据

项目整体结构

如下,就三个类,棋子类棋盘类和主窗口

Python Pygame 中国象棋 单机版源码 中国象棋单机小游戏_游戏_02

棋子类

棋子类继承自qlabel,包含国家、职业、状态、位置等属性,以及属性状态的设置和读取函数,并响应棋子的点击事件

#ifndef CHESSPIECES_H
#define CHESSPIECES_H

#include <QWidget>
#include <qlabel.h>
#include<qpoint.h>
#include<qvector.h>
#include<QMouseEvent>
#include<qDebug>

typedef enum chessprefession{//职业
    ZERO,
    JIANG,
    SHI,
    XIANG,
    MA,
    JV,
    PAO,
    BING
} chess_prefession;
typedef enum country//国家
{
    CHU,
    HAN
}chess_country;
typedef enum status{//状态
    DEATH,
    CHECK,
    UNCHEK
} chess_status;
struct chessman_attr{
    chess_prefession prefession;//棋子的职业
    chess_country country;//棋子的国家
} ;
struct chessman_status{
    chess_status status;//棋子的状态
    QPoint coor;//棋子坐标
} ;
struct chessman_data{//棋子数据结构体
    chessman_attr attr;//棋子的固有属性
    chessman_status status;//棋子的状态属性
};
class chessman :public QLabel
{
    Q_OBJECT
private:
    chessman_data chessmandata;
public:
    explicit chessman(QWidget* parent = Q_NULLPTR);
    ~chessman();
    void realmove(QPoint pos);//棋子真实移动
    void chess_init(chess_prefession prefession,chess_country country);//棋子设置数据
    void chess_setcoor(QPoint pos);//棋子移动
    void chess_death();//棋子死亡
    void chess_checked();//选中棋子
    void chess_unchecked();//放下棋子
    bool isalive();//棋子是否存活
    bool ischecked();//是否选中
    int getcoorX();//获取横坐标
    int getcoorY();//获取纵坐标
    chess_prefession getprefession();//获取棋子职业
    QPoint getcoor();//获取棋子坐标
    void setcoor(QPoint coor);//设置坐标
    chess_country getcountry();//获取棋子国家
    void setdata(chessman_data data);
    chessman_data getdata();
    void update();

signals:
    void chess_clicked(chessman *chess);//棋子点击信号
protected:
    void mousePressEvent(QMouseEvent* event);//棋子点击事件
};

#endif // CHESSPIECES_H

棋盘类

棋盘类也是继承自qlabel,是象棋游戏的主体部分。包括棋子的显示、棋盘坐标和真是坐标的转化、棋盘数据的存储、棋子的移动吃子的规则检查等。并响应鼠标的移动事件(因为要做到点击棋子之后棋子跟随鼠标移动,直到再次点击棋盘放下棋子)

#ifndef CHESSBOARD_H
#define CHESSBOARD_H

#include <QWidget>
#include "widget/chessman.h"
#include <QLabel>
#include<QMouseEvent>
#include<qlist.h>
#include<qDebug>
//float InvSqrt(float x);//开方 ps:此方法比系统方法效率更高
double getdistance2(QPoint p1,QPoint p2);//求两点之间直线距离的平方
class chessboard:public QLabel
{
    Q_OBJECT

public:
    QPoint chessboard_coor[10][9];//棋盘位置数据

    QVector<QList<chessman_data>> data;//棋谱
    QVector<QList<chessman_data>> databackup;//棋谱备份
    QList<chessman *> chesslist;//棋子指针列表
    chessman *chessboard_data[10][9];//定义棋盘数据

public:
    explicit chessboard(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
    ~chessboard();
    QPoint coor2pos(QPoint coor);//棋盘坐标转真实坐标
    QPoint pos2coor(QPoint pos);//真实坐标转棋盘坐标
    void addchess(chessman *chess,QPoint coor);//增加棋子
    void resetchess(chessman *chess);//棋子返回原位
    void resetboard();//棋盘数据复位
    bool movechess(chessman *chess,QPoint coor);//移动棋子
    void updateboard(QList<chessman_data> datalist);
    bool regret();//悔棋的都是辣鸡
    bool back();//老子不悔棋了行吧
private:
    void setchess(chessman *chess,QPoint coor);//放置棋子
    bool isexistchess(QPoint coor);//坐标是否存在棋子
    bool issamecountry(chessman *chess,chessman *chess2);//棋子是否是相同国家
    chess_country getchesscountry(QPoint coor);//获取棋子国家
    chessman *getchessfromcoor(QPoint coor);//获取指定位置的棋子
    void killchess(chessman *killer,chessman *victim);//吃子
    void setdata();//设置数据
signals:
    void chess_move(QMouseEvent *event);//自定义一个信号
    void gameover(chessman *chess);//对局结束
protected:
    /******************/
    void mouseMoveEvent(QMouseEvent *event);//重写mouseMoveEvent函数

};


#endif // CHESSBOARD_H

几个重要方法

1.坐标转化

定义了一个10*9的坐标数组,其中每个点对应真实坐标

QPoint chessboard_coor[10][9];//棋盘位置数据

double getdistance2(QPoint p1,QPoint p2){//计算两点距离的平方
    QPoint p=p1-p2;
    return (p.rx()*p.rx()+p.ry()*p.ry());
}
QPoint chessboard::coor2pos(QPoint coor){//棋盘坐标转真实坐标
    QPoint pos;
    pos.rx()=chessboard_coor[coor.ry()][coor.rx()].x();
    pos.ry()=chessboard_coor[coor.ry()][coor.rx()].y();
    return pos;
}
QPoint chessboard::pos2coor(QPoint pos){//真实坐标转棋盘坐标
    QPoint coor(-1,-1);
    for(int i=0;i<10;i++){
        for(int j=0;j<9;j++){
            if(getdistance2(chessboard_coor[i][j],pos)<950){
                coor=QPoint(j,i);
                break;
            }
        }
    }
    return coor;
}

2.移动规则

基本方法就是判断当前位置和目标位置距离平方,如马是5,士是2,象是8等,然后判断特殊规则如,车炮的移动和吃子,将帅相对的特殊情况,以及别马腿别象眼的情况。

bool chessboard::movechess(chessman *chess, QPoint coor){
    QPoint mcoor= chess->getcoor();
    double mdistance =getdistance2(mcoor,coor);
    chess_country mcountry=chess->getcountry();
    chess_prefession mprefession=chess->getprefession();
    int mx=coor.rx()-mcoor.rx();
    int my=coor.ry()-mcoor.ry();
    int stepx=(mx>0)?1:-1;
    int stepy=(my>0)?1:-1;
    int chesscount=0;
    if(mcoor==coor){//放回原位
        return false;
    }
    if(isexistchess(coor)){//目标位置存在棋子
        if(issamecountry(chess,getchessfromcoor(coor))){//是相同国家,直接结束
            return false;
        }
        if(mprefession==PAO){//执行吃子特殊规则(打炮)
            if(mcoor.rx()!=coor.rx()&&mcoor.ry()!=coor.ry()){//不是同一行或者列
                return false;
            }
            if(mcoor.rx()==coor.rx()){
                for(int i=mcoor.ry()+stepy;i!=coor.ry();i+=stepy){
                    if(isexistchess(QPoint(mcoor.rx(),i))){
                        chesscount++;
                    }
                }
            }else if(mcoor.ry()==coor.ry()){
                for(int i=mcoor.rx()+stepx;i!=coor.rx();i+=stepx){
                    if(isexistchess(QPoint(i,mcoor.ry()))){
                        chesscount++;
                    }
                }
            }
            if(chesscount!=1){//中间棋子个数超过1
                return false;
            }
        }else if(mprefession==JIANG){//执行吃子特殊规则(将帅相对)
            if(mcoor.rx()!=coor.rx()){//不是同一行
                return false;
            }
            for(int i=mcoor.ry()+stepy;i!=coor.ry();i+=stepy){
                if(isexistchess(QPoint(mcoor.rx(),i))){
                    return false;
                }
            }
        }
        killchess(chess,getchessfromcoor(coor));
        return true;
    }
    switch (mprefession) {
    case JV:
        if(mcoor.rx()!=coor.rx()&&mcoor.ry()!=coor.ry()){//不是同一行或者列
            return false;
        }
        if(mcoor.rx()==coor.rx()){
            for(int i=mcoor.ry()+stepy;i!=coor.ry();i+=stepy){//中间无棋子
                if(isexistchess(QPoint(mcoor.rx(),i))){
                    return false;
                }
            }
        }else if(mcoor.ry()==coor.ry()){
            for(int i=mcoor.rx()+stepx;i!=coor.rx();i+=stepx){//中间无棋子
                if(isexistchess(QPoint(i,mcoor.ry()))){
                    return false;
                }
            }
        }
        break;
    case MA:
        if(mdistance!=5){
            return false;
        }
        if(abs(mcoor.rx()-coor.rx())==1){//别马腿
            if(isexistchess(QPoint(mcoor.rx(),mcoor.ry()+stepy))){
                return false;
            }
        }else if(abs(mcoor.rx()-coor.rx())==2){
            if(isexistchess(QPoint(mcoor.rx()+stepx,mcoor.ry()))){
                return false;
            }
        }
        break;
    case PAO:
        if(mcoor.rx()!=coor.rx()&&mcoor.ry()!=coor.ry()){//不是同一行或者列
            return false;
        }

        if(mcoor.rx()==coor.rx()){
            for(int i=mcoor.ry()+stepy;i!=coor.ry();i+=stepy){//中间无棋子
                if(isexistchess(QPoint(mcoor.rx(),i))){
                    return false;
                }
            }
        }else if(mcoor.ry()==coor.ry()){
            for(int i=mcoor.rx()+stepx;i!=coor.rx();i+=stepx){//中间无棋子
                if(isexistchess(QPoint(i,mcoor.ry()))){
                    return false;
                }
            }
        }

        break;
    case XIANG:
        if(mdistance!=8){
            return false;
        }
        if(mcountry==CHU&&coor.ry()>4){//纵坐标超限
            return false;
        }
        if(mcountry==HAN&&coor.ry()<5){
            return false;
        }
        if(isexistchess((coor+mcoor)/2)){//别象眼
            return false;
        }
        break;
    case SHI:
        if(mdistance!=2){//斜对角的距离平方为2
            return false;
        }
        if(coor.rx()<3||coor.rx()>5){//横坐标超限
            return false;
        }
        if(mcountry==CHU&&coor.ry()>2){//纵坐标超限
            return false;
        }
        if(mcountry==HAN&&coor.ry()<7){
            return false;
        }
        break;
    case JIANG:
        if(mdistance!=1){//移动超过一格
            return false;
        }
        if(coor.rx()<3||coor.rx()>5){//横坐标超限
            return false;
        }
        if(mcountry==CHU&&coor.ry()>2){//纵坐标超限
            return false;
        }
        if(mcountry==HAN&&coor.ry()<7){
            return false;
        }
        break;
    case BING:
        if(mdistance!=1){//移动超过一格
            return false;
        }
        if(mcountry==CHU&&((mcoor.ry()<5&&(coor.ry()-mcoor.ry())<1)||(mcoor.ry()>=5&&(coor.ry()-mcoor.ry()<0)))){
            return false;
        }
        if(mcountry==HAN&&((mcoor.ry()>=5&&(mcoor.ry()-coor.ry())<1)||(mcoor.ry()<5&&(mcoor.ry()-coor.ry()<0)))){
            return false;
        }
        break;
    default:break;
    }
    setchess(chess,coor);
    return true;
}

3.悔棋和撤销

定义了一个vector其中存储所有棋子的数据,同时定义一个一样结构的vector来备份棋盘的历史数据
定义了一个list来保存存入棋盘的棋子
定义了一个很棋盘一样的指针数组来保存当前棋盘数据
悔棋和撤销就是维护这个保存棋盘数据的vector,每走一步就将当前的棋盘数据存入,悔棋就从底部移除一个数据,撤销就是从备份中还原这个数据。

QVector<QList<chessman_data>> data;//棋谱
    QVector<QList<chessman_data>> databackup;//棋谱备份
    QList<chessman *> chesslist;//棋子指针列表
    chessman *chessboard_data[10][9];//定义棋盘数据
    
bool chessboard::regret(){//悔棋
    if(data.size()<=32){//因为初始化了32个棋子,这是代码中的逻辑
        return false;
    }
    data.removeLast();
    QList<chessman_data> mlist=data.last();
    updateboard(mlist);
    return true;
}
bool chessboard::back(){//撤销悔棋
    if(data.size()==databackup.size()){
        return false;
    }
    QList<chessman_data> mdatalist=databackup.at(data.size());
    data.append(mdatalist);
    updateboard(mdatalist);
    return true;
}
void chessboard::setdata(){
    QList<chessman_data> a;
    for(auto item:chesslist){
        chessman_data chessdata=item->getdata();
        a.append(chessdata);
    }
    data.append(a);
    databackup.clear();
    databackup=data;
}
/*
 * 2020.11.16
 * 从传入的棋子数据更新棋盘状态
 * 修改bug,更新棋盘数据时,只加载存活状态的棋子
 */
void chessboard::updateboard(QList<chessman_data> datalist){
    for(int i=0;i<10;i++){
        for(int j=0;j<9;j++){
            chessboard_data[i][j]=nullptr;
        }
    }
    for(int i=0;i<datalist.size();i++){
        chesslist.at(i)->setdata(datalist.at(i));
        chesslist.at(i)->update();
        chesslist.at(i)->realmove(coor2pos(chesslist.at(i)->getcoor()));
        if(chesslist.at(i)->isalive()){
        chessboard_data[chesslist.at(i)->getcoorY()][chesslist.at(i)->getcoorX()]=chesslist.at(i);
        }
    }
}

项目文件

 github百度网盘,提取码:6666