├── Gobang.pro ├── Gobang.pro.user ├── README.md ├── chessai.cpp ├── chessai.h ├── gamewidget.cpp ├── gamewidget.h ├── gamewidget.ui ├── main.cpp ├── widget.cpp ├── widget.h └── widget.ui /Gobang.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | DEFINES += QT_NO_WARNING_OUTPUT\ QT_NO_DEBUG_OUTPU 13 | 14 | # You can also make your code fail to compile if it uses deprecated APIs. 15 | # In order to do so, uncomment the following line. 16 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 17 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 18 | 19 | SOURCES += \ 20 | chessai.cpp \ 21 | gamewidget.cpp \ 22 | main.cpp \ 23 | widget.cpp 24 | 25 | HEADERS += \ 26 | chessai.h \ 27 | gamewidget.h \ 28 | widget.h 29 | 30 | FORMS += \ 31 | gamewidget.ui \ 32 | widget.ui 33 | 34 | # Default rules for deployment. 35 | qnx: target.path = /tmp/$${TARGET}/bin 36 | else: unix:!android: target.path = /opt/$${TARGET}/bin 37 | !isEmpty(target.path): INSTALLS += target 38 | -------------------------------------------------------------------------------- /Gobang.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {23f03cbf-2d2c-4d6d-878b-a98c762a2796} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | true 62 | 63 | 64 | 65 | ProjectExplorer.Project.Target.0 66 | 67 | Desktop Qt 5.12.0 MinGW 64-bit 68 | Desktop Qt 5.12.0 MinGW 64-bit 69 | qt.qt5.5120.win64_mingw73_kit 70 | 0 71 | 0 72 | 0 73 | 74 | E:/Qt creator/build-Gobang-Desktop_Qt_5_12_0_MinGW_64_bit-Debug 75 | 76 | 77 | true 78 | qmake 79 | 80 | QtProjectManager.QMakeBuildStep 81 | true 82 | 83 | false 84 | false 85 | false 86 | 87 | 88 | true 89 | Make 90 | 91 | Qt4ProjectManager.MakeStep 92 | 93 | false 94 | 95 | 96 | false 97 | 98 | 2 99 | Build 100 | 101 | ProjectExplorer.BuildSteps.Build 102 | 103 | 104 | 105 | true 106 | Make 107 | 108 | Qt4ProjectManager.MakeStep 109 | 110 | true 111 | clean 112 | 113 | false 114 | 115 | 1 116 | Clean 117 | 118 | ProjectExplorer.BuildSteps.Clean 119 | 120 | 2 121 | false 122 | 123 | Debug 124 | Debug 125 | Qt4ProjectManager.Qt4BuildConfiguration 126 | 2 127 | true 128 | 129 | 130 | E:/Qt creator/build-Gobang-Desktop_Qt_5_12_0_MinGW_64_bit-Release 131 | 132 | 133 | true 134 | qmake 135 | 136 | QtProjectManager.QMakeBuildStep 137 | false 138 | 139 | false 140 | false 141 | true 142 | 143 | 144 | true 145 | Make 146 | 147 | Qt4ProjectManager.MakeStep 148 | 149 | false 150 | 151 | 152 | false 153 | 154 | 2 155 | Build 156 | 157 | ProjectExplorer.BuildSteps.Build 158 | 159 | 160 | 161 | true 162 | Make 163 | 164 | Qt4ProjectManager.MakeStep 165 | 166 | true 167 | clean 168 | 169 | false 170 | 171 | 1 172 | Clean 173 | 174 | ProjectExplorer.BuildSteps.Clean 175 | 176 | 2 177 | false 178 | 179 | Release 180 | Release 181 | Qt4ProjectManager.Qt4BuildConfiguration 182 | 0 183 | true 184 | 185 | 186 | E:/Qt creator/build-Gobang-Desktop_Qt_5_12_0_MinGW_64_bit-Profile 187 | 188 | 189 | true 190 | qmake 191 | 192 | QtProjectManager.QMakeBuildStep 193 | true 194 | 195 | false 196 | true 197 | true 198 | 199 | 200 | true 201 | Make 202 | 203 | Qt4ProjectManager.MakeStep 204 | 205 | false 206 | 207 | 208 | false 209 | 210 | 2 211 | Build 212 | 213 | ProjectExplorer.BuildSteps.Build 214 | 215 | 216 | 217 | true 218 | Make 219 | 220 | Qt4ProjectManager.MakeStep 221 | 222 | true 223 | clean 224 | 225 | false 226 | 227 | 1 228 | Clean 229 | 230 | ProjectExplorer.BuildSteps.Clean 231 | 232 | 2 233 | false 234 | 235 | Profile 236 | Profile 237 | Qt4ProjectManager.Qt4BuildConfiguration 238 | 0 239 | true 240 | 241 | 3 242 | 243 | 244 | 0 245 | 部署 246 | 247 | ProjectExplorer.BuildSteps.Deploy 248 | 249 | 1 250 | Deploy Configuration 251 | 252 | ProjectExplorer.DefaultDeployConfiguration 253 | 254 | 1 255 | 256 | 257 | false 258 | false 259 | 1000 260 | 261 | true 262 | 263 | false 264 | false 265 | false 266 | false 267 | true 268 | 0.01 269 | 10 270 | true 271 | 1 272 | 25 273 | 274 | 1 275 | true 276 | false 277 | true 278 | valgrind 279 | 280 | 0 281 | 1 282 | 2 283 | 3 284 | 4 285 | 5 286 | 6 287 | 7 288 | 8 289 | 9 290 | 10 291 | 11 292 | 12 293 | 13 294 | 14 295 | 296 | 2 297 | 298 | Gobang 299 | 300 | Qt4ProjectManager.Qt4RunConfiguration:E:/Qt creator/Gobang/Gobang.pro 301 | Gobang.pro 302 | 303 | 3768 304 | false 305 | true 306 | true 307 | false 308 | false 309 | true 310 | 311 | E:/Qt creator/build-Gobang-Desktop_Qt_5_12_0_MinGW_64_bit-Debug 312 | 313 | 1 314 | 315 | 316 | 317 | ProjectExplorer.Project.TargetCount 318 | 1 319 | 320 | 321 | ProjectExplorer.Project.Updater.FileVersion 322 | 20 323 | 324 | 325 | Version 326 | 20 327 | 328 | 329 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gobang-ai 2 | 极大极小搜索和α-β剪枝的无禁手ai,深度可达8层。 3 | 添加了简单的算杀模块。 4 | 5 | 我写了较为详细的教程。算杀的思想我可能理解有点问题,还有一些小bug,后续应该还会修改。 6 | 7 | [五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(一)引言和界面设计](https://blog.csdn.net/livingsu/article/details/104536005) 8 | [五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(二)贪心算法和评估函数](https://blog.csdn.net/livingsu/article/details/104539741) 9 | [五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(三)极大极小搜索和α-β剪枝算法](https://blog.csdn.net/livingsu/article/details/104544562) 10 | [五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(四)算杀模块的简单实现](https://blog.csdn.net/livingsu/article/details/104655537) 11 | -------------------------------------------------------------------------------- /chessai.cpp: -------------------------------------------------------------------------------- 1 | #include "chessai.h" 2 | 3 | chessAi::chessAi() 4 | { 5 | init_tuple6type(); 6 | qDebug()<<"初始化ai"; 7 | } 8 | 9 | bool chessAi::checkBound(int x,int y){ 10 | if(x>=0&&x<15&&y>=0&&y<15)return true; 11 | else return false; 12 | } 13 | QPoint chessAi::getXY(int row, int col, int dir, int rel){ 14 | QPoint p; 15 | if(dir==RIGHT){ 16 | p.setX(row); 17 | p.setY(col+rel); 18 | }else if(dir==UP){ 19 | p.setX(row-rel); 20 | p.setY(col); 21 | }else if(dir==UPRIGHT){ 22 | p.setX(row-rel); 23 | p.setY(col+rel); 24 | }else if(dir==UPLEFT){ 25 | p.setX(row-rel); 26 | p.setY(col-rel); 27 | } 28 | return p; 29 | } 30 | int chessAi::calcOnePosGreedy(int board[15][15],int row, int col, int C_ME){ 31 | int sum=0; 32 | for(int i=0;i<4;++i){//四个方向 33 | for(int j=0;j<5;++j){//每个方向上最多5个五元组 34 | QPoint start=getXY(row,col,RIGHT+i,j-4);//五元组顶点位置 35 | QPoint end=getXY(start.x(),start.y(),RIGHT+i,4);//五元组最远位置 36 | if(checkBound(start.x(),start.y())&&checkBound(end.x(),end.y())){//若五元组下标均合法 37 | int blackChess=0; 38 | int whiteChess=0; 39 | for(int k=0;k<5;++k){//对五元组5个位置考察 40 | QPoint tmp=getXY(start.x(),start.y(),RIGHT+i,k); 41 | if(board[tmp.x()][tmp.y()]==C_BLACK)blackChess++; 42 | if(board[tmp.x()][tmp.y()]==C_WHITE)whiteChess++; 43 | } 44 | sum+=tupleScoreGreedy(blackChess,whiteChess,C_ME); 45 | } 46 | } 47 | } 48 | return sum; 49 | } 50 | int chessAi::tupleScoreGreedy(int black, int white, int C_ME){ 51 | //连5 52 | if(C_ME==C_BLACK&&black==5)return 9999999; 53 | if(C_ME==C_WHITE&&white==5)return 9999999; 54 | //全空 55 | if(black==0&&white==0)return 7; 56 | //polluted 57 | else if(black>=1&&white>=1)return 0; 58 | else if(C_ME==C_BLACK){ 59 | if(black==1&&white==0)return 35; 60 | else if(black==2&&white==0)return 800; 61 | else if(black==3&&white==0)return 15000; 62 | else if(black==4&&white==0)return 800000; 63 | else if(black==0&&white==1)return 15; 64 | else if(black==0&&white==2)return 400; 65 | else if(black==0&&white==3)return 1800; 66 | else return 100000; 67 | }else{ 68 | if(black==1&&white==0)return 15; 69 | else if(black==2&&white==0)return 400; 70 | else if(black==3&&white==0)return 1800; 71 | else if(black==4&&white==0)return 100000; 72 | else if(black==0&&white==1)return 35; 73 | else if(black==0&&white==2)return 800; 74 | else if(black==0&&white==3)return 15000; 75 | else return 800000; 76 | } 77 | } 78 | QPoint chessAi::findBestMoveGreedy(int C_ME){ 79 | int bestScore=0; 80 | int bestRow=0,bestCol=0; 81 | for(int i=0;i<15;++i){ 82 | for(int j=0;j<15;++j){ 83 | if(chesses[i][j]==C_NONE){//空的位置 84 | int score=calcOnePosGreedy(chesses,i,j,C_ME); 85 | if(bestScore0)eval.result=R_WHITE; 356 | //黑赢 357 | else if(eval.STAT[LOSE]>0)eval.result=R_BLACK; 358 | 359 | eval.score=score; 360 | return eval; 361 | } 362 | 363 | POINTS chessAi::seekPoints(int board[15][15]){ 364 | bool B[15][15];//标记数组 365 | int worth[15][15]; 366 | POINTS best_points; 367 | 368 | memset(B,0,sizeof (B)); 369 | for(int i=0;i<15;++i){//每个非空点附近8个方向延伸3个深度,若不越界则标记为可走 370 | for(int j=0;j<15;++j){ 371 | if(board[i][j]!=C_NONE){ 372 | for(int k=-3;k<=3;++k){ 373 | if(i+k>=0&&i+k<15){ 374 | B[i+k][j]=true; 375 | if(j+k>=0&&j+k<15)B[i+k][j+k]=true; 376 | if(j-k>=0&&j-k<15)B[i+k][j-k]=true; 377 | } 378 | if(j+k>=0&&j+k<15)B[i][j+k]=true; 379 | } 380 | } 381 | } 382 | } 383 | 384 | for(int i=0;i<15;++i){ 385 | for(int j=0;j<15;++j){ 386 | worth[i][j]=-INT_MAX; 387 | if(board[i][j]==C_NONE&&B[i][j]==true){ 388 | //board[i][j]=C_BLACK; 389 | worth[i][j]=calcOnePosGreedy(board,i,j,C_WHITE); 390 | //worth[i][j]=evaluate(board).score; 391 | //board[i][j]=C_NONE; 392 | } 393 | } 394 | } 395 | 396 | int w; 397 | for(int k=0;k<20;++k){ 398 | w=-INT_MAX; 399 | for(int i=0;i<15;++i){ 400 | for(int j=0;j<15;++j){ 401 | if(worth[i][j]>w){ 402 | w=worth[i][j]; 403 | QPoint tmp(i,j); 404 | best_points.pos[k]=tmp; 405 | } 406 | } 407 | } 408 | 409 | int x=best_points.pos[k].x(),y=best_points.pos[k].y(); 410 | board[x][y]=C_WHITE; 411 | best_points.score[k]=evaluate(board).score; 412 | board[x][y]=C_NONE; 413 | 414 | worth[best_points.pos[k].x()][best_points.pos[k].y()]=-INT_MAX;//清除掉上一点,计算下一点的位置和分数 415 | } 416 | return best_points; 417 | } 418 | 419 | void chessAi::copyBoard(int (*A)[15], int (*B)[15]){ 420 | for(int i=0;i<15;++i){ 421 | for(int j=0;j<15;++j){ 422 | if(A[i][j]==C_NONE)B[i][j]=C_NONE; 423 | else if(A[i][j]==C_BLACK)B[i][j]=C_BLACK; 424 | else B[i][j]=C_WHITE; 425 | } 426 | } 427 | } 428 | void chessAi::reverseBoard(int (*A)[15], int (*B)[15]){ 429 | for(int i=0;i<15;++i){ 430 | for(int j=0;j<15;++j){ 431 | if(A[i][j]==C_NONE)B[i][j]=C_NONE; 432 | else if(A[i][j]==C_BLACK)B[i][j]=C_WHITE; 433 | else B[i][j]=C_BLACK; 434 | } 435 | } 436 | } 437 | 438 | int chessAi::analyse(int (*board)[15], int depth,int alpha, int beta){ 439 | EVALUATION EVAL=evaluate(board); 440 | if(depth==0||EVAL.result!=R_DRAW){//抵达最深层/如果模拟落子可以分出输赢,那么直接返回结果,不需要再搜索 441 | nodeNum+=1; 442 | if(depth==0){ 443 | POINTS P; 444 | P=seekPoints(board); 445 | 446 | return P.score[0];//返回最佳位置对应的最高分 447 | }else return EVAL.score; 448 | }else if(depth%2==0){//max层,我方(白)决策 449 | //qDebug()<<"白方决策!"; 450 | 451 | POINTS P=seekPoints(board); 452 | 453 | for(int i=0;i<10;++i){ 454 | //qDebug()<<"白方模拟下"<alpha){ 461 | alpha=a; 462 | if(depth==6){ 463 | qDebug()<<"set decision:"< pointList=seek_kill_points(board);//产生杀棋点 531 | 532 | if(pointList.length()==0)return false;//没有杀棋点 533 | for(auto i:pointList){ 534 | //qDebug()<<"白方模拟下"< chessAi::seek_kill_points(int (*board)[15]){ 567 | QList pointList; 568 | 569 | POINTS P=seekPoints(board);//一般来说,能冲4或者活3的必在评分前20的点内 570 | 571 | int sameBoard[15][15]; 572 | copyBoard(board,sameBoard); 573 | 574 | for(int i=0;i<20;++i){ 575 | sameBoard[P.pos[i].x()][P.pos[i].y()]=C_WHITE;//模拟落子 576 | if(evaluate(sameBoard).STAT[WIN]>0){//产生连5 577 | pointList.append(P.pos[i]); 578 | }else if(evaluate(sameBoard).STAT[FLEX4]>evaluate(board).STAT[FLEX4]){//产生新活4 579 | pointList.append(P.pos[i]); 580 | }else if(evaluate(sameBoard).STAT[BLOCK4]>evaluate(board).STAT[BLOCK4]){//产生新冲4 581 | pointList.append(P.pos[i]); 582 | }else if(evaluate(sameBoard).STAT[FLEX3]>evaluate(board).STAT[FLEX3]){//产生新活3 583 | pointList.append(P.pos[i]); 584 | } 585 | sameBoard[P.pos[i].x()][P.pos[i].y()]=C_NONE;//还原落子 586 | } 587 | return pointList; 588 | } 589 | 590 | -------------------------------------------------------------------------------- /chessai.h: -------------------------------------------------------------------------------- 1 | #ifndef CHESSAI_H 2 | #define CHESSAI_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define C_NONE 0//棋子:黑子,白子,无子 9 | #define C_BLACK 1 10 | #define C_WHITE 2 11 | 12 | #define RIGHT 0//方向:向右、向上、右上、左上 13 | #define UP 1 14 | #define UPRIGHT 2 15 | #define UPLEFT 3 16 | 17 | //各个棋型的代号 /权重 18 | #define OTHER 0//其他棋型 19 | #define WIN 1//1000000,白赢 20 | #define LOSE 2//-10000000 21 | #define FLEX4 3//50000,白活4 22 | #define flex4 4//-100000 23 | #define BLOCK4 5//400 24 | #define block4 6//-100000 25 | #define FLEX3 7//400 26 | #define flex3 8//-8000 27 | #define BLOCK3 9//20 28 | #define block3 10//-40 29 | #define FLEX2 11//20 30 | #define flex2 12//-40 31 | #define BLOCK2 13//1 32 | #define block2 14//-2 33 | #define FLEX1 15//1 34 | #define flex1 16//-2 35 | 36 | enum gameMode{PLAYER,AI}; 37 | enum gameStatus{UNDERWAY,FINISH}; 38 | enum gameTurn{T_BLACK,T_WHITE};//轮到谁下 39 | enum gameResult{R_BLACK,R_WHITE,R_DRAW};//黑棋赢,白棋赢,和棋 40 | 41 | struct EVALUATION{ 42 | int score; 43 | gameResult result; 44 | int STAT[8];//储存部分棋形的个数,下标WIN=1为白连5,LOSE=2为黑连5,FLEX4=3为白活4,BLOCK4=5为白冲4,FLEX3=7为白活3 45 | }; 46 | struct POINTS{//最佳落子位置,[0]分数最高,[19]分数最低 47 | QPoint pos[20]; 48 | int score[20];//此处落子的局势分数 49 | }; 50 | struct DECISION{ 51 | QPoint pos;//位置 52 | int eval;//对分数的评估 53 | }; 54 | 55 | class chessAi 56 | { 57 | public: 58 | int chesses[15][15];//棋盘 59 | DECISION decision;//储存极大极小搜索得到的要走的位置 60 | 61 | int nodeNum=0; 62 | 63 | private: 64 | int tuple6type[4][4][4][4][4][4];//棋型辨识数组,0无子,1黑子,2白子,3边界 65 | POINTS points;//最佳落子位置 66 | 67 | public://贪心算法部分 68 | chessAi(); 69 | int calcOnePosGreedy(int board[15][15],int row,int col,int C_ME);//计算某一个位置对于me的分数(贪心) 70 | int tupleScoreGreedy(int black,int white,int C_ME);//计算黑白子给定的五元组对于me的分数(贪心算法) 71 | QPoint getXY(int row,int col,int dir,int rel);//对于给定点、方向和偏移量,得到实际坐标 72 | bool checkBound(int x,int y);//检查边界 73 | QPoint findBestMoveGreedy(int C_ME);//贪心算法只看一步,效果还不错,但是目光短浅 74 | 75 | public://博弈树搜索部分 76 | void init_tuple6type();//对棋型判断数组赋初值 77 | POINTS seekPoints(int board[15][15]);//生成对于白子的最佳20个落子位置及分数 78 | void copyBoard(int A[15][15],int B[15][15]);//将A棋盘复制到B棋盘 79 | void reverseBoard(int A[15][15],int B[15][15]);//将A棋盘黑白子颠倒结果传给B棋盘 80 | EVALUATION evaluate(int board[15][15],bool needPrint=false);//对棋局board的黑子的局势估值函数,还可以判断输赢 81 | int analyse(int board[15][15],int depth,int alpha,int beta);//博弈树极大极小搜索加ab剪枝 82 | 83 | bool analyse_kill(int board[15][15],int depth);//计算杀棋,若找到杀棋则返回true 84 | QList seek_kill_points(int board[15][15]);//找白棋的连5,活4,冲4,活3的杀棋位置 85 | 86 | }; 87 | 88 | #endif // CHESSAI_H 89 | -------------------------------------------------------------------------------- /gamewidget.cpp: -------------------------------------------------------------------------------- 1 | #include "gamewidget.h" 2 | #include "ui_gamewidget.h" 3 | 4 | gameWidget::gameWidget(QWidget *parent) : 5 | QWidget(parent), 6 | ui(new Ui::gameWidget) 7 | { 8 | ui->setupUi(this); 9 | 10 | for(int i=0;i<15;++i){//棋盘左上角点为(20,20),每格间距为30 11 | for(int j=0;j<15;++j){ 12 | chessboard[i][j].setX(20+30*i); 13 | chessboard[i][j].setY(20+30*j); 14 | } 15 | } 16 | connect(this->ui->btn_return,SIGNAL(clicked(bool)),this,SLOT(returnPush())); 17 | 18 | setMouseTracking(true);//不用点击鼠标也一直追踪 19 | initializeGame(); 20 | 21 | } 22 | gameWidget::~gameWidget() 23 | { 24 | delete ui; 25 | } 26 | void gameWidget::returnPush(){ 27 | emit returnSignal(); 28 | this->hide(); 29 | initializeGame(); 30 | } 31 | 32 | void gameWidget::initializeGame(){ 33 | qDebug()<<"游戏重新初始化"; 34 | if(mode==PLAYER)qDebug()<<"玩家模式"; 35 | else qDebug()<<"人机模式"; 36 | 37 | for(int i=0;i<15;++i) 38 | for(int j=0;j<15;++j) 39 | ai.chesses[i][j]=C_NONE; 40 | 41 | status=UNDERWAY; 42 | turn=T_BLACK; 43 | cursorRow=-1; 44 | cursorCol=-1; 45 | } 46 | 47 | void gameWidget::paintEvent(QPaintEvent *event){ 48 | 49 | QPainter painter(this); 50 | painter.setRenderHint(QPainter::Antialiasing); 51 | //画棋盘 52 | painter.setPen(Qt::black); 53 | for(int i=0;i<15;++i){ 54 | painter.drawLine(chessboard[0][i],chessboard[14][i]); 55 | painter.drawLine(chessboard[i][0],chessboard[i][14]); 56 | } 57 | if(turn==T_BLACK)painter.setBrush(Qt::black); 58 | else painter.setBrush(Qt::white); 59 | //画鼠标光标 60 | if(cursorRow!=-1&&cursorCol!=-1){ 61 | //8为光标边长 62 | QRect rec(chessboard[cursorCol][cursorRow].x()-8/2,chessboard[cursorCol][cursorRow].y()-8/2,8,8); 63 | painter.drawRect(rec); 64 | } 65 | 66 | //画棋子 67 | for(int i=0;i<15;++i){ 68 | for(int j=0;j<15;++j){ 69 | if(ai.chesses[i][j]!=C_NONE){ 70 | if(ai.chesses[i][j]==C_BLACK)painter.setBrush(Qt::black); 71 | else painter.setBrush(Qt::white); 72 | painter.drawEllipse(chessboard[j][i].x()-20/2,chessboard[j][i].y()-20/2,20,20); 73 | 74 | } 75 | } 76 | } 77 | } 78 | 79 | void gameWidget::mouseMoveEvent(QMouseEvent *event){ 80 | if(event->x()>=5&&event->x()<=455&&event->y()>=5&&event->y()<=455){//5=20-15,455=20+14*30+15 81 | setCursor(Qt::BlankCursor); 82 | for(int i=0;i<15;++i) 83 | for(int j=0;j<15;++j){ 84 | float x=event->x(),y=event->y(); 85 | //判断鼠标落在哪一个点附近(正方形范围) 86 | if((x>=(chessboard[i][j].x()-15))&&(x<(chessboard[i][j].x()+15))&& 87 | (y>=(chessboard[i][j].y()-15))&&(y<(chessboard[i][j].y()+15))){ 88 | cursorRow=j; 89 | cursorCol=i; 90 | if(ai.chesses[cursorRow][cursorCol]!=C_NONE) 91 | setCursor(Qt::ForbiddenCursor); 92 | 93 | //展示图标坐标 94 | QString str="坐标:"; 95 | str+=QString::number(j); 96 | str+=","; 97 | str+=QString::number(i); 98 | if(turn==T_BLACK)ui->lb_black_position->setText(str); 99 | else ui->lb_white_position->setText(str); 100 | break; 101 | } 102 | } 103 | } 104 | else setCursor(Qt::ArrowCursor); 105 | update(); 106 | } 107 | 108 | void gameWidget::mouseReleaseEvent(QMouseEvent *event){ 109 | if(mode==PLAYER){ 110 | if(chessOneByPlayer()){ 111 | if(status==FINISH)initializeGame(); 112 | } 113 | }else{ 114 | if(chessOneByPlayer()){ 115 | if(status==UNDERWAY){ 116 | chessOneByAi(); 117 | if(status==FINISH)initializeGame(); 118 | } 119 | else initializeGame(); 120 | } 121 | } 122 | } 123 | 124 | bool gameWidget::chessOneByPlayer(){ 125 | if(ai.chesses[cursorRow][cursorCol]==C_NONE){ 126 | qDebug()<<"player chess"; 127 | oneChessMove(cursorRow,cursorCol); 128 | 129 | // qDebug()<<"局势得分:"<lcd_black->display(score_black); 181 | ui->lcd_write->display(score_write); 182 | } 183 | else if(isDeadGame()){ 184 | status=FINISH; 185 | msg.setText("平局"); 186 | msg.exec(); 187 | } 188 | update(); 189 | } 190 | 191 | void gameWidget::chessOneByAi(){ 192 | qDebug()<<"ai chess"; 193 | 194 | //QPoint p=ai.findBestMove(T_BLACK); 195 | 196 | struct timeval tpstart,tpend; 197 | float timeuse;//ai计算耗时 198 | gettimeofday(&tpstart,NULL); 199 | 200 | //QPoint p=ai.findBestMoveGreedy(C_BLACK); 201 | ai.nodeNum=0; 202 | 203 | if(!ai.analyse_kill(ai.chesses,16)){ 204 | qDebug()<<"没找到杀棋"; 205 | ai.analyse(ai.chesses,6,-INT_MAX,INT_MAX); 206 | 207 | }else{ 208 | qDebug()<<"找到了杀棋"; 209 | } 210 | 211 | QPoint p=ai.decision.pos; 212 | 213 | qDebug()<<"ai落子:"<ui->lb_timeuse->setText(text); 227 | 228 | text="ai叶结点数:"+QString::number(ai.nodeNum); 229 | this->ui->lb_nodeNum->setText(text); 230 | 231 | text="ai局面估分:"+QString::number(ai.evaluate(ai.chesses).score); 232 | this->ui->lb_eval->setText(text); 233 | } 234 | -------------------------------------------------------------------------------- /gamewidget.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMEWIDGET_H 2 | #define GAMEWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "chessai.h" 16 | using namespace std; 17 | 18 | 19 | namespace Ui { 20 | class gameWidget; 21 | } 22 | 23 | class gameWidget : public QWidget 24 | { 25 | Q_OBJECT 26 | public: 27 | explicit gameWidget(QWidget *parent = nullptr); 28 | ~gameWidget()override; 29 | void initializeGame(); 30 | void setGameMode(gameMode m){mode=m;} 31 | bool chessOneByPlayer(); 32 | void oneChessMove(int row,int col);//给出要走子的地址,走一步 33 | bool isDeadGame();//判断是否是僵局 34 | bool isLegalMove(int row,int col);//判断下子位置是否合法 35 | 36 | protected: 37 | void paintEvent(QPaintEvent *event) override; 38 | void mouseMoveEvent(QMouseEvent *event) override; 39 | void mouseReleaseEvent(QMouseEvent *event) override;//实际落子 40 | 41 | signals: 42 | void returnSignal(); 43 | 44 | public slots: 45 | void returnPush(); 46 | void chessOneByAi(); 47 | 48 | private: 49 | Ui::gameWidget *ui; 50 | QPoint chessboard[15][15];//第一个下标是列,第二个是行 51 | chessAi ai;//棋盘放在ai当中 52 | int cursorRow;//光标位置 53 | int cursorCol; 54 | 55 | gameMode mode; 56 | gameTurn turn; 57 | gameStatus status; 58 | 59 | int score_black; 60 | int score_write; 61 | 62 | }; 63 | 64 | #endif // GAMEWIDGET_H 65 | -------------------------------------------------------------------------------- /gamewidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | gameWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 635 10 | 460 11 | 12 | 13 | 14 | Gobang_by_livingsu 15 | 16 | 17 | 18 | 19 | 520 20 | 410 21 | 75 22 | 23 23 | 24 | 25 | 26 | 返回 27 | 28 | 29 | 30 | 31 | 32 | 510 33 | 21 34 | 91 35 | 91 36 | 37 | 38 | 39 | 40 | 41 | 42 | 白棋 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 510 62 | 160 63 | 91 64 | 91 65 | 66 | 67 | 68 | 69 | 70 | 71 | 黑棋 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 460 91 | 270 92 | 151 93 | 30 94 | 95 | 96 | 97 | ai计算耗时: 98 | 99 | 100 | 101 | 102 | 103 | 460 104 | 300 105 | 151 106 | 30 107 | 108 | 109 | 110 | ai叶结点数: 111 | 112 | 113 | 114 | 115 | 116 | 460 117 | 330 118 | 151 119 | 30 120 | 121 | 122 | 123 | ai局面估分: 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "gamewidget.h" 3 | #include "ui_widget.h" 4 | 5 | Widget::Widget(QWidget *parent) 6 | : QWidget(parent) 7 | , ui(new Ui::Widget) 8 | { 9 | ui->setupUi(this); 10 | 11 | game=new gameWidget(); 12 | game->hide(); 13 | connect(this->ui->btn_player,SIGNAL(clicked(bool)),this,SLOT(playerButtonPush())); 14 | connect(this->ui->btn_ai,SIGNAL(clicked(bool)),this,SLOT(aiButtonPush())); 15 | connect(game,&gameWidget::returnSignal,this,&Widget::show); 16 | } 17 | 18 | Widget::~Widget() 19 | { 20 | delete ui; 21 | } 22 | 23 | void Widget::playerButtonPush(){ 24 | this->hide(); 25 | game->show(); 26 | game->setGameMode(PLAYER); 27 | } 28 | 29 | void Widget::aiButtonPush(){ 30 | this->hide(); 31 | game->show(); 32 | game->setGameMode(AI); 33 | } 34 | -------------------------------------------------------------------------------- /widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include "gamewidget.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class Widget; } 9 | QT_END_NAMESPACE 10 | 11 | class Widget : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | Widget(QWidget *parent = nullptr); 17 | ~Widget(); 18 | 19 | private slots: 20 | void playerButtonPush(); 21 | void aiButtonPush(); 22 | 23 | private: 24 | Ui::Widget *ui; 25 | gameWidget *game; 26 | }; 27 | #endif // WIDGET_H 28 | -------------------------------------------------------------------------------- /widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 635 10 | 460 11 | 12 | 13 | 14 | Gobang_by_livingsu 15 | 16 | 17 | 18 | 19 | 180 20 | 300 21 | 75 22 | 23 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 30 | 31 | 32 | 玩家模式 33 | 34 | 35 | 36 | 20 37 | 20 38 | 39 | 40 | 41 | 42 | 43 | 44 | 390 45 | 300 46 | 75 47 | 23 48 | 49 | 50 | 51 | 52 | 0 53 | 0 54 | 55 | 56 | 57 | ai模式 58 | 59 | 60 | 61 | 62 | 63 | 310 64 | 150 65 | 36 66 | 16 67 | 68 | 69 | 70 | 五子棋 71 | 72 | 73 | Qt::AlignCenter 74 | 75 | 76 | 77 | 78 | 79 | 80 | --------------------------------------------------------------------------------