├── 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 |
--------------------------------------------------------------------------------