QT小游戏——中国象棋
- 前言
- 项目整体结构
- 棋子类
- 棋盘类
- 几个重要方法
- 1.坐标转化
- 2.移动规则
- 3.悔棋和撤销
- 项目文件
前言
最近用qt做了个中国象棋的小游戏。
目前只能左右互博,支持悔棋。效果图如下,
项目整体结构
如下,就三个类,棋子类棋盘类和主窗口
棋子类
棋子类继承自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);
}
}
}
项目文件