700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 基于Qt开发的中国象棋 (1) 双人对战

基于Qt开发的中国象棋 (1) 双人对战

时间:2021-11-29 08:51:22

相关推荐

基于Qt开发的中国象棋 (1) 双人对战

开发工具 Qt5.9

前置知识 , QPainter , paintEvent, mouseReleaseEvent

本次实现的是双人对战的象棋。需要实现的功能如下

棋盘的显示棋子的生成棋子的移动走棋轮流下棋判断胜利

文章目录

棋盘的显示棋子的生成棋子的移动走棋轮流下棋判断胜利

棋盘的显示

象棋的棋盘一般如上图所示, 包括9条竖线,10条横线和两个九宫格。两边的竖线是从上到下,其余竖线要分两次画分别画上面和下面。下面开始画

竖线和横线的代码

// 这里 _d 是一个小格子的长度_d = std::min(this->width(), this->height())/11;int d = _d;// 10条横线for(int i=1; i<=10; i++){painter.drawLine(QPoint(d, i*d), QPoint(9*d, i*d));}// 9条竖线for(int i=1; i<=9; 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));}}

九宫格的代码

// 九宫格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, 10*d), QPoint(6*d, 8*d));painter.drawLine(QPoint(6*d, 10*d), QPoint(4*d, 8*d));

这样一个简易的棋盘就画好了

棋子的生成

棋盘画好了,下面需要画棋子,我们先把棋子类创建出来,先想一想棋子大致有什么属性。棋子需要知道自己在棋盘上面的位置,棋子需要一个类型(车马炮…),棋子有颜色之分,棋子有可能被吃。为了方便操作棋子还需要一个编号,所以创建棋子类如下

class Stone{public:enum TYPE {CHE, MA, XIANG, SHI, JIANG, PAO, BING};Stone();void init(int id);int row() {return _row; }int col() {return _col; }QString getText();public:int _col; // 棋子所在列int _row; // 棋子所在行int _id; // 棋子编号TYPE _type; // 棋子类型bool _red; // 棋子颜色bool _dead; // 棋子是否被吃};

象棋是有初始局面的,所以每个棋子需要初始化,初始化函数 init 如下。因为棋盘是对称的,象棋初始摆盘的位置数组只要 16就可以了。

// 初始化棋子void Stone::init(int id){struct {int row, col;Stone::TYPE type;} pos[16] = {{0, 0, Stone::CHE},{0, 1, Stone::MA},{0, 2, Stone::XIANG},{0, 3, Stone::SHI},{0, 4, Stone::JIANG},{0, 5, Stone::SHI},{0, 6, Stone::XIANG},{0, 7, Stone::MA},{0, 8, Stone::CHE},{2, 1, Stone::PAO},{2, 7, Stone::PAO},{3, 0, Stone::BING},{3, 2, Stone::BING},{3, 4, Stone::BING},{3, 6, Stone::BING},{3, 8, Stone::BING}};_id = id;_dead = false;_red = id<16;if(id < 16){_row = pos[id].row;_col = pos[id].col;_type = pos[id].type;}else{_row = 9-pos[id-16].row;_col = 8-pos[id-16].col;_type = pos[id-16].type;}}

现在每一个棋子都初始化好了,接下来是将棋子画到棋盘上面。QPainter提供了画圆的函数,void QPainter::drawEllipse(const QPoint &center, int rx, int ry) 需要一个圆心。我们每个棋子都有行列的属性,这里的center是像素位置。所以我们需要一个转换函数将 行列坐标转换成 像素点。

QPoint Board::center(int col, int row){int x = (col+1)*_d;int y = (row+1)*_d;return QPoint(x, y);}

画好圆后,我们填充上颜色并写入文本。一个棋子就好了,代码如下

QPoint c = center(_stone[id].col(), _stone[id].row());//qDebug() <<"draw" << _stone[id]._row << " : " << _stone[id]._col;p->setPen(Qt::black);if(true == _stone[id]._red)p->setPen(Qt::red);p->drawEllipse(c, _d/2, _d/2);p->setFont(QFont("system", _d/2, 700));// 画字QRect rect = QRect(c.x()-_d/2, c.y()-_d/2, _d, _d);p->drawText(rect, _stone[id].getText(), QTextOption(Qt::AlignCenter));

棋子的移动

棋子的移动,其实就是棋子坐标的改变。当我们点击某个棋子后,然后再点击另一个位置。棋子的 col 和 row 值更新一下。然后重新绘一下棋盘,就实现了棋子的移动。 Qt提供了 mouseReleaseEvent() 事件,这个函数在鼠标点击后被调用,提供了 点击的坐标位置。我们重写这个函数,获取每次点击的位置,然后判断是否落在棋盘上,若落在棋盘上,将像素坐标转换成 col 和 row。 然后判断是否点击到某个棋子并记录该棋子的Id(Id 必 >= 0)。因为一次移动要点击两个位置,所以我们需要记住两个位置,一个是当前选中的棋子 _selectId,一个是当前点击的位置clickId。 每次判断之前是否有选中棋子,若有则 更新棋子的row 和 col,如果目标位置也有棋子(clickId > 0)就将 clickId 的那颗棋子的属性_dead 设置为 true。 若之前未选中棋子,则将 clickId 赋值给 selectId。 上述步骤做好以后,更新界面。

思路想好了,开始动手实现。 这里的 CanMove函数是判断可不可以移动。下一节说,先注释掉。 update() 函数会自动调用绘图事件。

// 鼠标点击释放事件void Board::mouseReleaseEvent(QMouseEvent *e){QPoint curPos = e->pos();int col, row; // 当前点击位置// 判断点是否落在棋盘bool ret = getCurPos(curPos, col, row);if(!ret) return; // 落在棋盘外,忽略int clickId = -1; // 本次点击的棋子// 获取本次点击的棋子clickId = getStoneId(row, col);// 如果是第一次点击到棋子if(_selectId == -1){// 设置 _selectId_selectId = clickId;}else {// 如果之前点击了,实现移动和吃子逻辑if(clickId == -1){// 可不可以移动// if(canMove(_selectId, row, col, clickId)){_stone[_selectId]._row = row;_stone[_selectId]._col = col;_redRound = !_redRound; // 切换回合}_selectId = -1;}else {if(_stone[_selectId]._red == _stone[clickId]._red){_selectId = clickId;}else {// 可不可以吃// if(canMove(_selectId, row, col, clickId)){_stone[_selectId]._row = row;_stone[_selectId]._col = col;_stone[clickId]._dead = true;_redRound = !_redRound;}_selectId = -1;}}}update();}

判断是否落在棋盘上 getCurPos 代码

// 当前点击是否有效bool Board::getCurPos(QPoint pos, int &col, int &row){for(int i=0; i<10; i++){for(int j=0; j<9; j++){QPoint c = center(j, i);if(pos.x() < c.x()+_d/2 && pos.x() > c.x()-_d/2){if(pos.y() < c.y()+_d/2 && pos.y() > c.y() - _d/2){col = j;row = i;return true;}}}}return false;}

获取本次点击的棋子Id getStoneId 代码

//判断该行列位置有没没棋子,有就返回ID,没有就返回-1int Board::getStoneId(int row, int col){for (int i = 0; i < 32; i++)if (row == _stone[i]._row && col == _stone[i]._col && !_stone[i]._dead)return i; //有棋子,返回棋子IDreturn -1; //该行列位置没棋子}

走棋

到目前为止,我们的象棋已经可以移动了,接下来就是象棋的规则,比如马走日,相飞田什么的。这部分代码贴在下面,不解释。有兴趣自己看

// 走棋逻辑bool Board::canMove(int id, int row, int col, int clickId){switch (_stone[id]._type) {case Stone::JIANG:if (col > 2 && col < 6 &&((_stone[id]._red && row < 3 || !_stone[id]._red && row > 6) && (abs(row - _stone[id]._row) + abs(col - _stone[id]._col) == 1)) //0+1=1|| countAtLine(_stone[4]._row, _stone[4]._col, _stone[20]._row, _stone[20]._col) == 0)return true;break;case Stone::CHE:if (countAtLine(row, col, _stone[id]._row, _stone[id]._col) == 0)return true;break;case Stone::MA:if ( (abs(row-_stone[id]._row)==1 && abs(col-_stone[id]._col)==2 //左右跳&& getStoneId(_stone[id]._row,(col+_stone[id]._col)>>1) == -1 //没拐脚) || (abs(row-_stone[id]._row)==2 && abs(col-_stone[id]._col)==1 //上下跳&& getStoneId((row+_stone[id]._row)>>1,_stone[id]._col) == -1)) //没拐脚return true;break;case Stone::PAO:if (getStoneId(row, col) == -1 && countAtLine(row, col, _stone[id]._row, _stone[id]._col) == 0 //移动|| getStoneId(row, col) != -1 && countAtLine(row, col, _stone[id]._row, _stone[id]._col) == 1) //吃子return true;break;case Stone::XIANG:if ((_stone[id]._red && row < 5 || !_stone[id]._red && row > 4) //没过河&& abs(row - _stone[id]._row) == 2 && abs(col - _stone[id]._col) == 2 //象步&& getStoneId((row+_stone[id]._row)>>1, (col+_stone[id]._col)>>1) == -1 ) //没象眼return true;break;case Stone::SHI:if (col > 2 && col < 6 && (_stone[id]._red && row < 3 || !_stone[id]._red && row > 6)&& abs(row - _stone[id]._row) == 1 && abs(col - _stone[id]._col) == 1)return true;break;case Stone::BING:if (abs(row - _stone[id]._row) + abs(col - _stone[id]._col) != 1)break;if (_stone[id]._red) {//红棋在上if (row < _stone[id]._row) break;if (_stone[id]._row <= 4 && row == _stone[id]._row) break;} else {//黑棋在下if (row > _stone[id]._row) break;if (_stone[id]._row >= 5 && row == _stone[id]._row) break;}return true;}return false;}//统计直线上棋子个数int Board::countAtLine(int row1, int col1, int row2, int col2){int min, max, cnt = 0;if (row1 != row2 && col1 != col2)return -1;if (row1 == row2) {if (col1 < col2) {min = col1;max = col2;} else {min = col2;max = col1;}for (int col = min+1; col < max; col++)if (getStoneId(row1, col) >= 0)cnt++;} else if (col1 == col2) {if (row1 < row2) {min = row1;max = row2;} else {min = row2;max = row1;}for (int row = min+1; row < max; row++)if (getStoneId(row, col1) >= 0)cnt++;}return cnt;}

轮流下棋

轮流下棋其实很简单,就是轮到红(黑)方的时候,点击黑(红)棋没有效果。 我们在棋盘类中加一个属性 _redRound 判断当前是否是红方,初始化为红方。这个值的修改在 mouseReleaseEvent这个函数里面修改。 如果当前点击的棋的颜色和当前回合不一样,这次的 鼠标点击事件不处理,任何一方走棋完成后,将_redRound的值取反。 修改后的 mouseReleaseEvent 代码如下

checkWin函数就是判断是否胜利,这个函数还没有写,下一节说。

// 鼠标点击释放事件void Board::mouseReleaseEvent(QMouseEvent *e){QPoint curPos = e->pos();int col, row; // 当前点击位置// 判断点是否落在棋盘bool ret = getCurPos(curPos, col, row);if(!ret) return; // 落在棋盘外,忽略int clickId = -1; // 本次点击的棋子// 获取本次点击的棋子clickId = getStoneId(row, col);// 如果是第一次点击到棋子if(_selectId == -1){// 不是当前回合if(clickId != -1 && _stone[clickId]._red != _redRound) return;// 设置 _selectId_selectId = clickId;}else {// 如果之前点击了,实现移动和吃子逻辑if(clickId == -1){// 可不可以移动if(canMove(_selectId, row, col, clickId)){_stone[_selectId]._row = row;_stone[_selectId]._col = col;_redRound = !_redRound; // 切换回合}_selectId = -1;}else {if(_stone[_selectId]._red == _stone[clickId]._red){_selectId = clickId;}else {// 可不可以吃if(canMove(_selectId, row, col, clickId)){_stone[_selectId]._row = row;_stone[_selectId]._col = col;_stone[clickId]._dead = true;_redRound = !_redRound;}_selectId = -1;}}}update();// checkWin();}

判断胜利

判断胜利就是判断,红方老将和黑方老将有没有被吃,被吃则对方赢。代码如下

void Board::checkWin(){if (_stone[4]._dead) {QMessageBox::information(this, "结束啦", "黑胜");init();} else if (_stone[20]._dead) {QMessageBox::information(this, "结束啦", "红胜");init();}}

这样一个双人对战的中国象棋就完成了。还比较简陋,没有悔棋和棋谱残局等功能。后面会添加人机对战和网络对战功能,至于悔棋和棋谱,残局这些功能也许会添加吧。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。