100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 用C++开发的双人对战五子棋

用C++开发的双人对战五子棋

时间:2019-03-12 11:17:00

相关推荐

用C++开发的双人对战五子棋

欢迎大家关注/订阅我的微信公众号Code Art Online,我会在我的公众号分享个人见闻,发现生活趣味;这里不仅有0和1,还有是诗和远方↓↓↓

前言:有了用C++开发象棋及其AI的经验后,我就萌生了再用C++开发五子棋及其AI的想法。有了想法还等什么?付诸实施呗!

首先明确一盘五子棋需要什么属性:一盘五子棋有黑棋有白棋,与象棋不同的是,五子棋的棋子数目不是确定的,而是随着下棋随着增加的,所以,需要一个存放整盘棋棋子数目的整型变量num_of_Stone。五子棋下棋的有两方,黑方和白方,所以需要一个存放当前该谁走的布尔型变量_bBlackTurn,黑棋先行,顾名思义,初始化时应该将它的值置为true。还需要一个存放棋盘上存在的所有棋子的数组_s,由于不确定棋子的数目,所以将这个数组的类型设置为QVector<Stone>。此外,为了更方便地处理棋盘信息,再引入存放棋盘信息的二维数组map[15][15],将横竖撇捺四个方向上线段化之后形成的一维数组的集合L。

给大家看一下棋盘类:

#ifndef BOARD_H#define BOARD_H#include <QFrame>#include "Stone.h"#include "Step.h"#include <QVector>#include <QMouseEvent>class Board : public QWidget{Q_OBJECTpublic://棋盘类的构造函数explicit Board(QWidget *parent = 0);//棋盘类的析构函数~Board();//棋子半径int _r;//棋子数目int num_of_stone;//存放所有棋子的数组QVector<Stone> _s;//是不是应该黑棋走bool _bBlackTurn;//棋盘地图int map[15][15];//线段化的棋盘信息struct{int line[15];}L[21+15+15+21];//画界面的函数void paintEvent(QPaintEvent *);//鼠标释放的响应函数void mouseReleaseEvent(QMouseEvent *);//点击屏幕上某个点的响应函数void click(QPoint pt);virtual void click(int id,int row,int col);//绘制棋子的函数void drawStone(QPainter& painter,int id);//判断行走是否合法的函数bool canMove(int row,int col);//走棋函数void moveStone(int moveid, int row, int col);//悔棋函数void reliveStone(int row,int col);//输入行列坐标获取棋子id的函数int getStoneId(int row,int col);//判断谁胜谁负的函数int whowin();//将棋盘信息线段化void turnLine();//保存棋子的行棋信息的函数void saveStep(int moveid, int row, int col, QVector<Step*>& steps);//获取用户点击位置的行列坐标的函数bool getRowCol(QPoint pt,int &row,int &col);//输入行列坐标返回像素坐标的函数QPoint center(int row,int col);//输入棋子的id 返回像素坐标QPoint center(int id);};#endif // BOARD_H

首先使用棋盘类的构造函数初始化棋盘信息:

Board::Board(QWidget *parent) : QWidget(parent){int i,j;//将棋子个数初始化为0num_of_stone=0;//黑棋先行_bBlackTurn=true;//初始化地图 二维数组的元素的值为-1代表该位置上没有棋子 值为0代表该位置上有黑棋 值为1代表该位置上有白棋for(i=0;i<15;i++)for(j=0;j<15;j++){map[i][j]=-1;}}

然后在paintEvent函数里绘制整个棋盘:

void Board::paintEvent(QPaintEvent *){//初始化画笔painterQPainter painter(this);//初始化棋子直径int d=30;//初始化棋子半径_r=d/2;//整盘棋局的胜负信息 flag=1时黑方胜利 flag=2时白方胜利int flag=whowin();if(flag==1){//将画笔painter的颜色设置为黑色painter.setPen(Qt::black);painter.drawText(rect(), Qt::AlignCenter, QStringLiteral("黑方胜利!"));}else if(flag==2){//将画笔painter的颜色设置为红色painter.setPen(Qt::red);painter.drawText(rect(), Qt::AlignCenter, QStringLiteral("白方胜利!"));}else{//画10条横线for(int i=1;i<=15;i++)painter.drawLine(QPoint(d,i*d),QPoint(15*d,i*d));//画9条竖线for(int i=1;i<=15;i++)painter.drawLine(QPoint(i*d,d),QPoint(i*d,15*d));//绘制棋子for(int i=0;i<num_of_stone;i++){drawStone(painter,i);}}}

五子棋的棋盘共15行15列。paintEvent函数是用来画界面的,每调用一次update函数就会被自动调用,paintEvent函数里也需要绘制棋子,因此在paintEvent函数里点用了drawStone函数,drawStone函数在下面介绍。此外,调用whowin函数是用来判断棋局是否已经分出胜负的,如果已经分出胜负当然要展示对局结果给用户看啦,whowin函数在下文中介绍。

另外,棋子也需要单独划分成一类,给大家看一下棋子类:

#ifndef STONE_H#define STONE_Hclass Stone{public:Stone();bool _black;int _row;int _col;int _id;void init(int id);};#endif // STONE_H

五子棋里的棋子类就比象棋的简单多了,棋子的属性基本上只有只有行列坐标和棋子属于哪一方。​​​​​​

棋子有个初始化函数init:

void Stone::init(int id){_id=id;if(_id%2==0){_black=true;}else{_black=false;}}

这里就有必要说一下对棋子类型的判断了。前面讲到了一个存放全盘棋子数目的变量num_of_Stone,这里就巧妙运用这个变量对棋子进行初始化,棋子的id就是该棋子放到棋盘上之前的num_of_Stone的值。举个例子,第一个下上去的棋子是黑棋,该棋子的id为该棋子放到棋盘上之前的num_of_Stone的值,也就是0,id除以2的余数为0,所以以id除以2的余数为标志,初始化棋子类型,余数为0的是黑棋。余数为1的是白棋;第二个下上去的棋子是白棋。该棋子的id便是1,id除以2的余数是1,也就初始化该枚棋子白棋。

上drawStone函数的源代码:

void Board::drawStone(QPainter& painter,int id){painter.setPen(Qt::black);if(_s[id]._black)painter.setBrush(QBrush(Qt::black));elsepainter.setBrush(QBrush(Qt::white));//画圆painter.drawEllipse(center(id),_r,_r);}

给大家看一下一系列鼠标事件响应函数的源代码:

void Board::mouseReleaseEvent(QMouseEvent *ev){if(ev->button() != Qt::LeftButton){return;}click(ev->pos());}

void Board::click(QPoint pt){int row, col;bool bClicked = getRowCol(pt, row, col);if(!bClicked) return;if(canMove(row,col)){int id=num_of_stone;click(id, row, col);}}

//获取用户点击位置的行列坐标 如果点击在合法范围内返回true 点击在合法范围外返回falsebool Board::getRowCol(QPoint pt,int &row,int &col){for(row=0;row<15;row++)for(col=0;col<15;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;}

getRowCol函数有两个引用的参数,因此该函数的功能不仅仅是返回了一个布尔类型的值,其主要功能还有确定用户点击位置所属的行列坐标。以所有行列坐标为圆心,假想一个个圆形区域,用户点击在圆形区域范围内便确定了一个行列坐标。

//输入行列坐标 返回像素坐标QPoint Board::center(int row,int col){QPoint ret;ret.rx()=(col+1)*_r*2;ret.ry()=(row+1)*_r*2;return ret;}

//输入棋子的id 返回像素坐标QPoint Board::center(int id){return center(_s[id]._row,_s[id]._col);}

形参为id的center函数在drawStone函数中有调用。

void Board::click(int id, int row, int col){moveStone(id,row,col);update();}

//行棋函数void Board::moveStone(int moveid, int row, int col){Stone p;p.init(moveid);//将新生成的棋子压入vector_s.append(p);_s[moveid]._row = row;_s[moveid]._col = col;//转换行棋的一方_bBlackTurn=!_bBlackTurn;//全盘棋子数目增加1num_of_stone++;//修改相应的地图信息map[row][col]=moveid%2;}

在moveStone函数里,要生成一个棋子并将其压入已存在的棋子的集合中,转换行棋方,将棋盘上已有的棋子数加1,最后修改相应的地图信息。

接下来,重点介绍将棋盘信息线段化的turnLine函数和判断棋局胜负的whowin函数,turnLine函数是whowin函数的基础。

turnLine函数的作用是在棋盘所有的可放棋子的位置个数大于等于5的横竖撇捺四个方向上分别形成单独的一维数组,并将所有的一维数组存放在board类的结构体数组L中。

//将棋盘坐标线段化void Board::turnLine(){int i,row,col,pos,start_row=0,start_col=10;int t;for(i=0;i<21+15+15+21;i++){//走捺if(i>=0&&i<10){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col++,pos++){L[i].line[pos]=map[row][col];}for(t=pos;t<15;t++){L[i].line[t]=-1;}start_col--;}//走捺else if(i<21){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col++,pos++)L[i].line[pos]=map[row][col];for(t=pos;t<15;t++){L[i].line[t]=-1;}start_row++;if(i==20){start_row=14;start_col=0;}}//走竖else if(i<21+15){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row--,pos++)L[i].line[pos]=map[row][col];for(t=pos;t<15;t++){L[i].line[t]=-1;}start_col++;if(i==21+14){start_row=14;start_col=14;}}//走横else if(i<21+15+15){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;col--,pos++)L[i].line[pos]=map[row][col];for(t=pos;t<15;t++){L[i].line[t]=-1;}start_row--;if(i==21+15+14){start_row=0;start_col=4;}}//走撇else if(i<21+15+15+10){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col--,pos++)L[i].line[pos]=map[row][col];for(t=pos;t<15;t++){L[i].line[t]=-1;}start_col++;}//走撇else if(i<21+15+15+21){pos=0;for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col--,pos++)L[i].line[pos]=map[row][col];for(t=pos;t<15;t++){L[i].line[t]=-1;}start_row++;}}}

一维数组中的元素值为-1代表没有棋子,值为0代表有黑子,值为1代表有白子。turnLine的过程就是初始化结构体数组L的过程。这就为判断胜负提供了遍历,判断胜负的时候只需要一行一行遍历结构体数组L,如果L中有形成五子连珠的情况则返回胜负信息并break。

上whowin函数的源代码:

//获取棋局胜负信息的函数int Board::whowin(){//初始化结构体数组LturnLine();int sum_black,sum_white;int i,j;//看看是否有黑棋五子连珠的情况for(i=0;i<21+15+15+21;i++){sum_black=0;for(j=0;j<15;j++){if(L[i].line[j]==0)sum_black++;else//计数器重新置零sum_black=0;if(sum_black>=5){return 1;}}}//看看是否有白棋五子连珠的情况for(i=0;i<21+15+15+21;i++){sum_white=0;for(j=0;j<15;j++){if(L[i].line[j]==1)sum_white++;else//计数器重新置零sum_white=0;if(sum_white>=5){return 2;}}}return 0;}

先调用turnLine函数初始化结构体数组L,再分别对黑白两方判断胜利与否。在判断的时候以行为单位,出现胜利的一方立刻跳出循环。若黑方胜利返回1;若白方胜利返回2;若两方都没有胜利,则最后返回0。

还有另外一种形式的whowin函数:

int Board::whowin(){int sum;int i,j,k;for(i=0;i<num_of_stone;i++){if(i%2==0){sum=0;for(j=_s[i]._row;j>=0&&j<15&&j<_s[i]._row+5;j++){if(getStoneId(j,_s[i]._col)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._row;j>=0&&j<15&&j>_s[i]._row-5;j--){if(getStoneId(j,_s[i]._col)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._col;j>=0&&j<15&&j<_s[i]._col+5;j++){if(getStoneId(_s[i]._row,j)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._col;j>=0&&j<15&&j>_s[i]._col-5;j--){if(getStoneId(_s[i]._row,j)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k<_s[i]._col+5;j++,k++){if(getStoneId(j,k)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k>_s[i]._col-5;j++,k--){if(getStoneId(j,k)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k<_s[i]._col+5;j--,k++){if(getStoneId(j,k)%2==0)sum++;}if(sum==5)return 1;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k>_s[i]._col-5;j--,k--){if(getStoneId(j,k)%2==0)sum++;}if(sum==5)return 1;}else{sum=0;for(j=_s[i]._row;j>=0&&j<15&&j<_s[i]._row+5&&j<15;j++){if(getStoneId(j,_s[i]._col)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._row;j>=0&&j<15&&j>_s[i]._row-5&&j<15;j--){if(getStoneId(j,_s[i]._col)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._col;j>=0&&j<15&&j<_s[i]._col+5&&j<15;j++){if(getStoneId(_s[i]._row,j)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._col;j>=0&&j<15&&j>_s[i]._col-5&&j<15;j--){if(getStoneId(_s[i]._row,j)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k<_s[i]._col+5;j++,k++){if(getStoneId(j,k)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k>_s[i]._col-5;j++,k--){if(getStoneId(j,k)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k<_s[i]._col+5;j--,k++){if(getStoneId(j,k)%2==1)sum++;}if(sum==5)return 2;sum=0;for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k>_s[i]._col-5;j--,k--){if(getStoneId(j,k)%2==1)sum++;}if(sum==5)return 2;}}return 0;}

该函数的函数体较为庞杂,因为它采用了一个棋子一个棋子判断的方法,遍历到一个棋子,那么由该棋子向其左上、左、左下、右上、右、右下延伸判断,出现五子连珠则跳出循环。

最后给大家看一下程序效果图:

大家如果有什么疑问欢迎email我。

email:freedom11235@

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