这个项目是暑假在校比赛时,参考B站教程编写的,没有像一般的游戏进行导入图片的简单方法来进行游戏界面布局,而是采用画笔来进行操作,包括棋盘绘制和棋子的绘制前期只做了简单的走棋,后来有时间将会尝试联网功能和人工智能走棋操作。本软件版本为QT版本5.14
希望大家能够加个关注,收藏。以后会完善这个项目的。代码如果编译不过,可以私信我分享源文件。
有QT编程基础的应该会如果不会的话参考一下链接-------->QT项目创建
通过画笔实现象棋界面的绘制,其中每一个采用了枚举类,实现相似棋子的定义。采用像素点转化的方法来绘制棋盘。就是划线的方法采用drawLine方法,后来又用画圆的方法绘制棋子,后来再用调整汉字大小的方法来得到汉字。
主要包含了棋盘绘制函数的实现以及走棋代码函数的定义。
#ifndef BOARD_H
#define BOARD_H
#include
#include
#include "stone.h"
#include "step.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Board; }
QT_END_NAMESPACE
class Board : public QWidget
{
Q_OBJECT
public:
Board(QWidget *parent = nullptr);
~Board();
//棋盘初始点坐标
QPoint origin;
//格子边长
int d;
//棋子
stone s[32];
//棋子半径
int r;
//记录棋子,是否选中
int isSelect;
//返回象棋棋盘行列对应的坐标
QPoint Center(int row,int col);
QPoint Center(int id);
//交换走棋
bool isTurnRed;
//绘制棋子
void DrawStone(QPainter &painter,int id);
//棋盘绘制(绘图事件)
void paintEvent(QPaintEvent *);
//绘制兵炮停靠点
void drawAngle(QPainter &p,QPoint point);
//走棋(鼠标点击事件)
void mouseReleaseEvent(QMouseEvent *);
//获得棋盘的行列,是否在棋盘内
bool getRowCol(QPoint pt,int &row,int &col);
//判读是否能够走棋
bool canMove(int moveid,int row,int col,int killid);
bool canMoveCar(int moveid,int row,int col);
bool canMoveGun(int moveid,int row,int col);
bool canMoveHorse(int moveid,int row,int col);
bool canMoveElephant(int moveid,int row,int col);
bool canMoveMandarins(int moveid,int row,int col);
bool canMoveGenerals(int moveid,int row,int col);
bool canMovePawns(int moveid,int row,int col);
//勾股定理计算棋子应该移动范围
int Gougu(int moveid,int row,int col);
//判断某行某位置有没有棋子
int Havestone(int row,int col);
//统计直线上棋子个数
int CountnumLine(int row, int col ,int Row,int Col);
//人工智能走棋
void click(QPoint pt);
void click(int id,int row,int col);
//获取棋子id
int getStonID(int row, int col);
private:
Ui::Board *ui;
};
#endif // BOARD_H
主要程序界面包含棋盘绘制函数的实现,以及象棋初始界面的定义。走棋的详细操作,走棋遵循传统的中国象棋的规则。
#include "board.h"
#include "ui_board.h"
#include
#include
#include "QDebug"
Board::Board(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Board)
{
setWindowTitle("中国象棋");
origin = QPoint(60,60);
d=80;
r=d/2;
resize(origin.x()*3+d*8,origin.y()+d*9);
for (int i=0;i<32;i++) {
s[i].init(i);
}
isSelect = -1;
isTurnRed = true;
ui->setupUi(this);
}
Board::~Board()
{
delete ui;
}
//将坐标轴转换为像素坐标点
QPoint Board::Center(int row, int col)
{
return QPoint(origin.x()+d*col,origin.y()+d*row);
}
QPoint Board::Center(int id)
{
return Center(s[id].row,s[id].col);
}
//绘制棋子
void Board::DrawStone(QPainter &painter, int id)
{
QPen pen;
pen.setWidth(1);
painter.setPen(pen);
//求中心点
QPoint c = Center(id);
//构造图像
QRect rect = QRect(c.rx()-r,c.ry()-r,r*2,r*2);
//棋子不存在
if(s[id].isdead)
return;
//如果棋子被选中
if(id == isSelect)
painter.setBrush(QBrush(Qt::gray));
else
painter.setBrush(QBrush(QColor(234,173,26)));
//绘制椭圆函数DrawEllipse
painter.drawEllipse(Center(s[id].row,s[id].col),r, r);
painter.drawEllipse(Center(s[id].row,s[id].col),r-3, r-3);
//绘制棋子颜色
if(s[id].isblack)
{
painter.setPen(Qt::black);
}
else painter.setPen(Qt::red);
//写字,参数说明draw(对象,字,对齐方式)
painter.setFont(QFont("楷体",r,700));
painter.drawText(rect,s[id].getclass(),QTextOption(Qt::AlignCenter));
}
//创建棋盘雏形,利用坐标点原理
void Board::paintEvent(QPaintEvent *)
{
QPainter painter(this);
//设置画笔
QPen pen;
pen.setWidth(3);
painter.setPen(pen);
//绘制棋盘外围边框
painter.drawRect(QRect(origin.x()-4,origin.y()-4,d*8+10,d*9+10));
//绘制棋盘内线
pen.setWidth(1);
painter.setPen(pen);
painter.drawRect(QRect(origin,Center(9,8)));
// //画10条横线
// for (int i=0;i<11;++i)
// {
// //drawline函数就是两点之间画直线,电脑左上角为坐标轴起点,QPoint(d,d)点坐标
// painter.drawLine(QPoint(d,i*d),QPoint(9*d,i*d));
// }
// //画9条竖线
// for (int i=0;i<10;i++)
// {
// //楚河汉界空白处不划线
// if(i==1 || i==9)
// painter.drawLine(QPoint(i*d,d),QPoint(i*d,10*d));
// else
// {
// painter.drawLine(QPoint(i*d,d),QPoint(i*d,5*d));
// painter.drawLine(QPoint(i*d,6*d),QPoint(i*d,10*d));
// }
// }
//绘制8横7竖
for (int i=1;i<=8;i++)
{
//drawline函数就是两点之间画直线,电脑左上角为坐标轴起点,QPoint(d,d)点坐标
painter.drawLine(Center(i,0),Center(i,8));
if(i<8)
{
painter.drawLine(Center(0,i),Center(4,i));
painter.drawLine(Center(5,i),Center(9,i));
}
}
//九宫格
// painter.drawLine(QPoint(4*d,d),QPoint(6*d,3*d));
// painter.drawLine(QPoint(6*d,d),QPoint(4*d,3*d));
// painter.drawLine(QPoint(4*d,8*d),QPoint(6*d,10*d));
// painter.drawLine(QPoint(6*d,8*d),QPoint(4*d,10*d));
painter.drawLine(Center(0,3),Center(2,5));
painter.drawLine(Center(2,3),Center(0,5));
painter.drawLine(Center(7,3),Center(9,5));
painter.drawLine(Center(9,3),Center(7,5));
//绘制楚河汉界
// QTransform transfrom;
// transfrom.rotate(180);
// painter.setTransform(transfrom);
QPainterPath s;
painter.setPen(Qt::gray);
painter.setFont(QFont("楷体",35,r));
painter.drawText(QRect(Center(4,1),Center(5,3)),"楚河",QTextOption(Qt::AlignCenter));
painter.drawText(QRect(Center(4,5),Center(5,7)),"漢界",QTextOption(Qt::AlignCenter));
//绘制棋子炮的停靠点
drawAngle(painter,Center(2,1));
drawAngle(painter,Center(2,7));
drawAngle(painter,Center(7,1));
drawAngle(painter,Center(7,7));
//中间6个兵停靠点
drawAngle(painter,Center(3,2));
drawAngle(painter,Center(3,4));
drawAngle(painter,Center(3,6));
drawAngle(painter,Center(6,2));
drawAngle(painter,Center(6,4));
drawAngle(painter,Center(6,6));
drawAngle(painter,Center(3,0));
drawAngle(painter,Center(3,8));
drawAngle(painter,Center(6,0));
drawAngle(painter,Center(6,8));
//绘制32个棋子
for (int i=0;i<32;i++)
{
DrawStone(painter,i);
}
}
void Board::drawAngle(QPainter &p, QPoint point)
{
QPen pen;
pen.setWidth(2);
p.setPen(pen);
int X = point.x();
int Y = point.y();
float R = d/9.5;
if(point.x() == 60)
{
p.drawLine(X+R,Y-R,X+2*R,Y-R);
p.drawLine(X+R,Y+R,X+2*R,Y+R);
p.drawLine(X+R,Y-2*R,X+R,Y-R);
p.drawLine(X+R,Y+R,X+R,Y+2*R);
}
else if(point.x() == 700)
{
p.drawLine(X-2*R,Y-R,X-R,Y-R);
p.drawLine(X-2*R,Y+R,X-R,Y+R);
p.drawLine(X-R,Y-2*R,X-R,Y-R);
p.drawLine(X-R,Y+R,X-R,Y+2*R);
}
else
{
p.drawLine(X-2*R,Y-R,X-R,Y-R);
p.drawLine(X-2*R,Y+R,X-R,Y+R);
p.drawLine(X+R,Y-R,X+2*R,Y-R);
p.drawLine(X+R,Y+R,X+2*R,Y+R);
p.drawLine(X-R,Y-2*R,X-R,Y-R);
p.drawLine(X-R,Y+R,X-R,Y+2*R);
p.drawLine(X+R,Y-2*R,X+R,Y-R);
p.drawLine(X+R,Y+R,X+R,Y+2*R);
}
}
void Board::mouseReleaseEvent(QMouseEvent *ev)
{
QPoint pt = ev->pos();
//将pt转换成象棋的行列值
//判断行列值上面有没有棋子
int row,col;
bool bret = getRowCol(pt,row,col);
if(bret == false) return; //点在棋盘外
int i;
int clickid = -1;
for(i=0;i<32;++i)
{
if(s[i].row == row && s[i].col == col && s[i].isdead == false)
{
break;
}
}
if(i<32)
{
clickid = i;
}
//
if(isSelect == -1)
{
if(clickid != -1)
{
if(isTurnRed == !s[clickid].isblack)
isSelect = clickid;
update();
}
}
else
{
//走棋
if(canMove(isSelect,row,col,clickid))
{
s[isSelect].row = row;
s[isSelect].col =col;
if(clickid != -1)
{
s[clickid].isdead = true;
}
isSelect = -1;
isTurnRed = !isTurnRed;
update();
}
}
}
bool Board::getRowCol(QPoint pt, int &row, int &col)
{
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;
}
}
return false;
}
bool Board::canMove(int moveid, int row, int col, int killid)
{
if((s[moveid].isblack == s[killid].isblack)&&killid != -1)
{
isSelect = killid;
update();
return false;
}
switch(s[moveid].type)
{
case stone::Car: return canMoveCar(moveid, row, col); break;
case stone::Gun: return canMoveGun(moveid, row, col);break;
case stone::Horse: return canMoveHorse(moveid, row, col); break;
case stone::Elephant: return canMoveElephant(moveid, row, col);break;
case stone::Premier: return canMoveElephant(moveid, row, col);break;
case stone::Mandarins: return canMoveMandarins(moveid, row, col);break;
case stone::Soldier: return canMoveMandarins(moveid, row, col);break;
case stone::Generals: return canMoveGenerals(moveid, row, col);break;
case stone::King:return canMoveGenerals(moveid, row, col);break;
case stone::Pawns: return canMovePawns(moveid, row, col); break;
case stone::Arms: return canMovePawns(moveid, row, col);break;
}
return true;
}
//車直行
bool Board::canMoveCar(int moveid, int row, int col)
{
if(CountnumLine(row,col,s[moveid].row,s[moveid].col) == 0)
{
return true;
}
return false;
}
//隔山打牛炮
bool Board::canMoveGun(int moveid, int row, int col)
{
//移动
if(Havestone(row,col) == -1 && CountnumLine(row,col,s[moveid].row,s[moveid].col)==0)
{
return true;
}
//隔山打牛吃子
if(Havestone(row,col) != -1 && CountnumLine(row,col,s[moveid].row,s[moveid].col)==1)
{
return true;
}
return false;
}
//马走日
bool Board::canMoveHorse(int moveid, int row, int col)
{
//勾股定理
int dis = Gougu(moveid, row, col);
if(dis == 12 && Havestone(s[moveid].row,(col+s[moveid].col)>>1)==-1) //没有拐脚
{
return true;
}
else if(dis == 21 && Havestone((row+s[moveid].row)>>1,s[moveid].col)==-1)
{
return true;
}
return false;
}
//士的移动范围在九宫格内,移动的步长是斜线
bool Board::canMoveMandarins(int moveid, int row, int col)
{
//黑棋 士
if(s[moveid].isblack)
{
if(row > 2)
return false;
}
//红棋 士
else
{
if(row < 7)
return false;
}
if(col < 3)
return false;
if(col >5)
return false;
//勾股定理
int dis = Gougu(moveid, row, col);
if(dis == 11)
{
return true;
}
return false;
}
//相或者象的移动范围楚河汉界的一边不能越界,象飞田
bool Board::canMoveElephant(int moveid, int row, int col)
{
//黑棋 象
if(s[moveid].isblack)
{
if(row > 4)
return false;
}
//红棋 相
else
{
if(row < 5)
return false;
}
//勾股定理
int dis = Gougu(moveid, row, col);
if(dis == 22 && Havestone((row+s[moveid].row)>>1,(col+s[moveid].col)>>1) == -1)
{
return true;
}
return false;
}
//将军的移动范围在九宫格内,移动的步长是一个格子
bool Board::canMoveGenerals(int moveid, int row, int col)
{
//黑棋 将
if(s[moveid].isblack)
{
if(row > 2)
return false;
}
//红棋 帅
else
{
if(row < 7)
return false;
}
if(col < 3)
return false;
if(col >5)
return false;
//勾股定理
int dis = Gougu(moveid, row, col);
if(dis == 1 || dis ==10)
{
return true;
}
return false;
}
//小兵的移动是一直向前,移动的步长是一个格子
bool Board::canMovePawns(int moveid, int row, int col)
{
//黑棋 卒
if(s[moveid].isblack)
{
if(s[moveid].row>row)
return false;
// else if(s[moveid].row<4)
// return false;
}
//红棋 兵
else
{
if(s[moveid].row5)
// return false;
}
//勾股定理
int dis = Gougu(moveid, row, col);
if(dis ==10)
{
return true;
}
else if(dis == 1&&s[moveid].isblack&&s[moveid].row>4)
{
return true;
}
else if(dis == 1&&!s[moveid].isblack&&s[moveid].row<5)
{
return true;
}
return false;
}
//勾股定理计算棋子应该移动范围
int Board::Gougu(int moveid, int row, int col)
{
int Row = s[moveid].row-row;
int Col = s[moveid].col-col;
//勾股定理
return abs(Row)*10+abs(Col);
}
//判断某行某位置有没有棋子
int Board::Havestone(int row, int col)
{
for (int i =0;i < 32;i++)
{
if((row == s[i].row && col ==s[i].col)&&!s[i].isdead)
{
return i;
}
}
return -1;
}
//统计直线上棋子个数
int Board::CountnumLine(int row, int col, int Row, int Col)
{
int max,min,count=0;
if(row != Row && col !=Col)
{
return -1;
}
if(row == Row)
{
if(col < Col)
{
max = Col;
min = col;
}
else
{
max = col;
min = Col;
}
for (int i = min+1;i < max;i++)
{
if(Havestone(row,i)>=0)
{
count++;
}
}
}
else if (col == Col)
{
if(row < Row)
{
max = Row;
min = row;
}
else
{
max = row;
min = Row;
}
for (int i = min+1;i < max;i++)
{
if(Havestone(i,col)>=0)
{
count++;
}
}
}
return count;
}
//点击坐标点
void Board::click(QPoint pt)
{
int row,col;
bool isClicked = getRowCol(pt,row,col);
if(!isClicked)
{
return ;
}
int id = getStonID(row,col);
// click(id,row,col);
}
int Board::getStonID(int row, int col)
{
for(int i=0;i<32;i++)
{
if(s[i].row==row && s[i].col == col)
{
return i;
}
else
return 0;
}
}
#ifndef STONE_H
#define STONE_H
#include
class stone
{
public:
stone();
enum TYPE{Car,Gun,Horse,Premier,Elephant,Soldier,Mandarins,Arms,Pawns,King,Generals};
int row; //横坐标
int col; //纵坐标
int id; //编号
bool isdead; //是否死亡
bool isblack; //颜色
TYPE type; //种类
//初始化棋子
void init(int ID)
{
struct
{
int row, col;
stone::TYPE type;
}
chess[32]=
{
//黑棋
{0,0,stone::Car},
{0,1,stone::Horse},
{0,2,stone::Elephant},
{0,3,stone::Mandarins},
{0,4,stone::Generals},
{0,5,stone::Mandarins},
{0,6,stone::Elephant},
{0,7,stone::Horse},
{0,8,stone::Car},
{2,1,stone::Gun},
{2,7,stone::Gun},
{3,0,stone::Pawns},
{3,2,stone::Pawns},
{3,4,stone::Pawns},
{3,6,stone::Pawns},
{3,8,stone::Pawns},
//红棋
{9,0,stone::Car},
{9,1,stone::Horse},
{9,2,stone::Premier},
{9,3,stone::Soldier},
{9,4,stone::King},
{9,5,stone::Soldier},
{9,6,stone::Premier},
{9,7,stone::Horse},
{9,8,stone::Car},
{7,1,stone::Gun},
{7,7,stone::Gun},
{6,0,stone::Arms},
{6,2,stone::Arms},
{6,4,stone::Arms},
{6,6,stone::Arms},
{6,8,stone::Arms},
};
id = ID;
isdead = false;
if(ID<16)
{
isblack = true;
}
else isblack = false;
if(ID<16){
row = chess[ID].row;
col = chess[ID].col;
type = chess[ID].type;
}
else{
row = chess[ID].row;
col = chess[ID].col;
type = chess[ID].type;
}
}
//判断棋子的种类
QString getclass()
{
switch (this->type)
{
case Car: return "車";
case Gun: return "炮";
case Horse: return "马";
case Premier: return "相";
case Elephant: return "象";
case Soldier: return "仕";
case Mandarins: return "士";
case Arms: return "兵";
case Pawns: return "卒";
case King: return "帥";
case Generals: return "将";
}
return "ERROR";
}
};
#endif // STONE_H
#include "stone.h"
stone::stone()
{
}
#ifndef SINGLEGAME_H
#define SINGLEGAME_H
#include "board.h"
class SingleGame:public Board
{
public:
//纯虚函数与抽象类,纯虚函数没有函数体,只有函数名,因此不能被调用,徒有其名,而无其实留在派生类中带定义,只有派生类定义后才能使用
virtual void click(int id, int row, int col);
//电脑走棋
void computerMove();
void getAllstep();
};
#endif // SINGLEGAME_H
#include "singlegame.h"
void SingleGame::click(int id, int row, int col)
{
// Board::click(id,row,col);
// if(this->isTurnRed)
// {
// computerMove();
// }
}
void SingleGame::computerMove()
{
//1.看看有哪些步骤可以走
//2.试着走一下
//3.评估走的结果
//4.取最好结果作为参考
}
#ifndef STEP_H
#define STEP_H
#include
class Step : public QObject
{
Q_OBJECT
public:
explicit Step(QObject *parent = nullptr);
~Step();
int _moveid;
int _killid;
int _rowFrom;
int _colFrom;
int _rowTo;
int _colTo;
signals:
public slots:
};
#endif // STEP_H
#include "step.h"
Step::Step(QObject *parent) : QObject(parent)
{
}
Step::~Step()
{
}
#include "board.h"
#include "singlegame.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
SingleGame w;
w.show();
return a.exec();
}
QPainter类在窗口和其他绘制设备上执行低级绘制。
QPainter提供高度优化的功能来完成大多数图形用户界面程序所需的工作。它能画出从简单线条到复杂形状如饼图和弦等一切图形。它还可以绘制对齐的文本和像素图。通常,它绘制一个“自然”坐标系,但它也可以进行视图和世界变换。QPainter可以对继承QPaintDevice类的任何对象进行操作。
QPainter的常见用法是在窗口的绘制事件中:构造和自定义(例如设置笔或画笔)绘制器。然后画。记住在绘制后销毁QPainer对象。例如:
QPainter的核心功能是绘图。但是类还提供了几个功能,允许您自定义QPainter的设置及其渲染质量,以及其他启用剪辑的功能。此外,还可以通过指定绘制者的合成模式来控制不同形状合并在一起的方式。
函数QPainter::isActive()的作用是:指示绘制程序是否处于活动状态。painter由begin()函数和接受QPaintDevice参数的构造函数激活,end()函数和析构函数将停用它。
与QPaintDevice和QPaintEngine类一起,QPainter构成了qt绘图系统的基础。QPainter是用于执行绘图操作的类。QPaintDevice表示可以使用QPainter绘制的设备。QPaintEngine提供了一个接口,绘图使用它来绘制不同类型的设备。如果绘图处于活动状态,则device()返回及绘图在其上进行绘图的绘图设备,paintEngine()返回绘图当前正在使用的绘图引擎。有关详细信息,请参见绘图系统。
QPainer支持一个静态函数来执行此操作,setRedirected()。
警告:当paintdevice是一个窗口时,QPainter只能在paintEvent()函数或paintEvent()调用的函数中使用。
设置属性
可以根据自己的喜好自定义多个设置以使QPainer绘制:
font()用于绘制文本的字体。如果painter为isActive(),则可以分别使用fontInfo()和fontMetrics()函数检索有关当前设置的字体及其度量的信息。
brush()定义用于填充形状的颜色或图案。
pen()定义用于绘制线条或边界的颜色或点画。
backgroundMode()定义是否有background(),即它是Qt::OpaqueMode或Qt::TransparentMode。
background()是Qt::OpaqueMode,pen()是点画时,background()才适用。在这种情况下,它描述点画中背景像素的颜色。
brushOrigin()定义平铺画笔的原点,通常是小部件背景的原点。
viewport()、window()、worldTransform()构成了painter的坐标转换系统。有关详细信息,请参见“坐标变换”部分和坐标系文档。
hasClipping()告诉画师是否剪辑。(绘制设备也会被保存)如果painter被截取,它会被保存到clipRegion()。
layoutDirection()定义绘制文本时画家使用的布局方向。
worldMatrixEnabled()指示是否启用世界转换。
viewTransformEnabled()指示是否启用视图转换。