五子棋

一、项目名称

  五子棋

二、项目思路

  1、搜集图片材料

  2、绘制棋盘

  3、实现鼠标下棋

  4、用dfs判断输赢

  5、实现双方下棋

  6、实现AI下棋

    (1)使用AI估分函数

    (2)设计AI计算深度

    (3)优化AI算法(α-β算法)

三、代码实现

  1 #include <stdio.h>
  2 //加载图形库文件
  3 #include <easyx.h>
  4 //加载windows库文件
  5 #include <Windows.h>
  6 //加载音乐文件
  7 #include <mmsystem.h>
  8 #include <conio.h>
  9 #include <stack>
 10 //加载winmm.lib
 11 #pragma comment(lib,"winmm.lib")
 12 //每个格子的宽度
 13 #define GRID_W 25
 14 //定义左和上的间隔宽度
 15 #define INTERVAL (4 * GRID_W)
 16 //棋盘长度
 17 #define ROW 15
 18 //棋盘宽度
 19 #define COL 15
 20 //存储棋盘状态
 21 using namespace std;
 22 int Chess[ROW][COL];
 23 //定义玩家和AI操作
 24 int f = 1;
 25 //定义状态种类
 26 enum class Status { None, Black, White };
 27 //定义玩家操作状态集合
 28 struct node {
 29     int x = 0;//当前操作目标横坐标
 30     int y = 0;//当前操作目标纵坐标
 31     bool isShow = false;//是否显示下棋框
 32     //鼠标当前所在数组对应的下标
 33     int row = 0;
 34     int col = 0;
 35     //棋手状态
 36     Status chessPlayer = Status::Black;
 37 }op;
 38 struct undo {
 39     int x;
 40     int y;
 41 };
 42 //定义悔棋栈区
 43 stack <undo> white;
 44 stack <undo> black;
 45 //定义AI操作状态集合
 46 struct aiNode{ int x;
 47 int y; 
 48 int ans; }aiOperation;
 49 //悔棋操作
 50 int popChess(stack <undo> &s)
 51 {
 52     if (s.empty()) {
 53         MessageBox(GetHWnd(), "您还没有下棋哦>0<", "", MB_OK);
 54         return 0;
 55     }
 56     else {
 57         undo v = s.top();
 58         s.pop();
 59         Chess[v.x][v.y] = int(Status::None);
 60         return 1;
 61     }
 62 }
 63 //清空栈区函数
 64 void clearStack(stack <undo>& s)
 65 {
 66     s = stack <undo>();
 67 }
 68 //判断棋盘满棋
 69 bool judgeFullchess()
 70 {
 71     for (int i = 0; i < ROW; i++)
 72         for (int j = 0; j < COL; j++)
 73             if (!Chess[i][j])
 74                 return false;
 75     return true;
 76 }
 77 //清空棋盘
 78 void flushCheckerBoard()
 79 {
 80     for (int i = 0; i < ROW; i++)
 81         for (int j = 0; j < COL; j++)
 82             Chess[i][j] = int(Status::None);
 83 }
 84 //判断赢棋
 85 bool judgeWinner(int sx, int sy)
 86 {
 87     int mx[8] = { 1,-1,1,-1,0,0,1,-1 };
 88     int my[8] = { -1,1,1,-1,1,-1,0,0 };
 89     for (int i = 0; i < 4; i++) {
 90         int cnt = 1;
 91         int dx = sx;
 92         int dy = sy;
 93         while (1) {
 94             dx = mx[i * 2] + dx;
 95             dy = my[i * 2] + dy;
 96             if (dx < 0 || dx >= 15 || dy < 0 || dy >= 15)
 97                 break;
 98             if (Chess[dx][dy] != int(op.chessPlayer))
 99                 break;
100             cnt++;
101         }
102         dx = sx;
103         dy = sy;
104         while (1) {
105             dx = mx[i * 2 + 1] + dx;
106             dy = my[i * 2 + 1] + dy;
107             if (dx < 0 || dx >= 15 || dy < 0 || dy >= 15)
108                 break;
109             if (Chess[dx][dy] != int(op.chessPlayer))
110                 break;
111             cnt++;
112         }
113         if (cnt >= 5)
114             return true;
115     }
116     return false;
117 }
118 //绘制棋盘
119 void drawCheckerBoard()
120 {
121     setlinecolor(BLACK);
122     setlinestyle(PS_SOLID, 1);
123     for (int i = 0; i < 15; i++) {
124         //绘制线条
125         line(INTERVAL, INTERVAL + i * GRID_W, GRID_W * 14 + INTERVAL, INTERVAL + i * GRID_W);
126         line(INTERVAL + i * GRID_W, INTERVAL, INTERVAL + i * GRID_W, GRID_W * 14 + INTERVAL);
127     }
128     //设置线条样式
129     setlinestyle(PS_SOLID, 3);
130     //绘制矩阵,加宽外框
131     rectangle(INTERVAL, INTERVAL, INTERVAL + GRID_W * 14, INTERVAL + GRID_W * 14);
132     //绘制下棋选择框
133     if (op.isShow) {//防止出棋盘还显示下棋框
134         setlinecolor(BLUE);
135         rectangle(op.x - 12, op.y - 12, op.x + 12, op.y + 12);
136     }
137     //绘制棋子
138     for (int i = 0; i < ROW; i++)
139         for (int j = 0; j < COL; j++) {
140             switch (Chess[i][j]) {
141             case 0:break;
142             case 1:
143                 setfillcolor(BLACK);
144                 solidcircle(i * GRID_W + INTERVAL, j * GRID_W + INTERVAL, 10);
145                 break;//矩阵坐标和实际坐标相反
146             case 2:
147                 setfillcolor(WHITE);
148                 solidcircle(i * GRID_W + INTERVAL, j * GRID_W + INTERVAL, 10);
149                 break;//矩阵坐标和实际坐标相反
150             default:
151                 break;
152             }
153         }
154 }
155 //鼠标操作
156 void mouseEvent()
157 {
158     //定义一个消息结构体变量
159     ExMessage msg;
160     //获取鼠标消息
161     if (peekmessage(&msg, EM_MOUSE)) {//EM_MOUSE只需要鼠标消息
162         //认输
163         if (msg.message == WM_LBUTTONDOWN) {
164             if (msg.x >= 460 && msg.x <= 485 && msg.y >= 250 && msg.y < 280) {
165                 //清空棋子区
166                 clearStack(white);
167                 clearStack(black);
168                 MessageBox(GetHWnd(), "Win", op.chessPlayer == Status::White ? "黑方" : "白方", MB_OK);
169                 MessageBox(GetHWnd(), "重新开始", "下轮黑方先手", MB_OK);
170                 //棋手状态转换
171                 op.chessPlayer = Status::Black;
172                 flushCheckerBoard();
173                 return;
174             }
175             else if(msg.x >= 460 && msg.x <= 485 && msg.y >= 285 && msg.y < 315) {
176                 //清空棋子区
177                 clearStack(white);
178                 clearStack(black);
179                 MessageBox(GetHWnd(), "重新开始", "平局", MB_OK);
180                 MessageBox(GetHWnd(), "", "下轮黑方先手", MB_OK);
181                 //棋手状态转换
182                 op.chessPlayer = Status::Black;
183                 flushCheckerBoard();
184                 return;
185             }
186             else if(msg.x >= 460 && msg.x <= 485 && msg.y >= 215 && msg.y < 245) {
187                 //判断是否悔棋
188                 int check = 0;
189                 if (op.chessPlayer == Status::Black)
190                     check = popChess(white);
191                 else
192                     check = popChess(black);
193                 //如果没有悔棋
194                 if (!check)
195                     return;
196                 //棋手状态转换
197                 op.chessPlayer = (op.chessPlayer == Status::Black ? Status::White : Status::Black);
198                 drawCheckerBoard();
199                 FlushBatchDraw();
200                 return;
201             }
202         }
203         //坐标校准(自动聚焦到棋盘上某个点的中心)
204         op.isShow = false;
205         int intervalGridx = msg.x % GRID_W;
206         int intervalGridy = msg.y % GRID_W;
207         int gridx, gridy;
208         //校准x坐标
209         if (intervalGridx <= GRID_W / 2)
210             gridx = (msg.x / GRID_W) * GRID_W;
211         else
212             gridx = (msg.x / GRID_W + 1) * GRID_W;
213         //校准y坐标
214         if (intervalGridy <= GRID_W / 2)
215             gridy = (msg.y / GRID_W) * GRID_W;
216         else
217             gridy = (msg.y / GRID_W + 1) * GRID_W;
218         //判断是否越出棋盘
219         if (gridx < INTERVAL || gridy < INTERVAL ||
220             gridx > INTERVAL + GRID_W * 14 || gridy > INTERVAL + GRID_W * 14)
221             return;
222         op.isShow = true;
223         op.row = (gridx - INTERVAL) / GRID_W;
224         op.col = (gridy - INTERVAL) / GRID_W;
225         op.x = gridx;
226         op.y = gridy;
227         //左击下棋
228         if (msg.message == WM_LBUTTONDOWN && Chess[op.row][op.col] == 0) {
229             //棋盘状态标记
230             Chess[op.row][op.col] = int(op.chessPlayer);
231             if (op.chessPlayer == Status::Black)
232                 black.push({ op.row,op.col });
233             else
234                 white.push({ op.row,op.col });
235             drawCheckerBoard();
236             FlushBatchDraw();
237             //判断赢棋
238             if (judgeWinner(op.row, op.col)) {
239                 MessageBox(GetHWnd(), "Win", op.chessPlayer == Status::White ? "白方" : "黑方", MB_OK);
240                 MessageBox(GetHWnd(), "重新开始", op.chessPlayer == Status::White ? "下轮黑方先手" : "下轮白方先手", MB_OK);
241                 //清空棋子栈区
242                 clearStack(white);
243                 clearStack(black);
244                 flushCheckerBoard();
245             }
246             //棋手状态转换
247             op.chessPlayer = (op.chessPlayer == Status::Black ? Status::White : Status::Black);
248         }
249         //满棋的情况
250         if (judgeFullchess()) {
251             MessageBox(GetHWnd(), "和棋", "重新开始", MB_OK);
252             //清空棋子栈区
253             clearStack(white);
254             clearStack(black);
255             flushCheckerBoard();
256         }
257     }
258 }
259 //背景音乐
260 void bkmusic()
261 {
262     mciSendString("open ./bkmusic.mp3 alias music", 0, 0, 0);
263     mciSendString("play music repeat", NULL, 0, NULL);
264 }
265 //背景图片
266 void bkimage()
267 {
268     //双缓冲绘图 让数据批量处理,不让图像闪烁
269     BeginBatchDraw();
270     IMAGE img_bk;//创建图片对象
271     loadimage(&img_bk, "./checkerboard.jpg", 0, 0);
272     putimage(0, 0, &img_bk);
273     setbkmode(TRANSPARENT);
274     settextcolor(BLUE);
275     settextstyle(30, 10, "楷体");
276     outtextxy(460, 200, "----");
277     outtextxy(460, 215, "悔棋");
278     outtextxy(495, 215, "|");
279     outtextxy(455, 215, "|");
280     outtextxy(460, 230, "----");
281 
282     outtextxy(460, 235, "----");
283     outtextxy(460, 250, "认输");
284     outtextxy(495, 250, "|");
285     outtextxy(455, 250, "|");
286     outtextxy(460, 265, "----");
287 
288     outtextxy(460, 270, "----");
289     outtextxy(460, 285, "求和");
290     outtextxy(495, 285, "|");
291     outtextxy(455, 285, "|");
292     outtextxy(460, 300, "----");
293 }
294 void menumusic()
295 {
296     mciSendString("open ./menumusic.mp3 alias music", 0, 0, 0);
297     mciSendString("play music repeat", NULL, 0, NULL);
298 }
299 //菜单背景图片
300 void menuImage()
301 {
302     IMAGE img_menubk;//创建图片对象
303     loadimage(&img_menubk, "./menubk.jpg", 0, 0);
304     putimage(0, 0, &img_menubk);
305 }
306 //对AI落子评分
307 void judgeRobotValue(int robotNum, int empty, int sx, int sy)
308 {
309     int cnt = 0;
310     //一连
311     if (robotNum == 0)
312         cnt = 5;
313     //二连
314     else if (robotNum == 1)
315         cnt = 10;
316     //三连
317     else if (robotNum == 2) {
318         //死三
319         if (empty == 1)
320             cnt = 25;
321         //活三
322         else if (empty == 2)
323             cnt = 50;
324     }
325     //连四
326     else if (robotNum == 3) {
327         //死四
328         if (empty == 1)
329             cnt = 55;
330         //活四
331         else if (empty == 2)
332             cnt = 300;
333     }
334     //连五
335     else if (robotNum == 4)
336         cnt = 30000;
337     if (cnt > aiOperation.ans) {
338         aiOperation.x = sx;
339         aiOperation.y = sy;
340         aiOperation.ans = cnt;
341     }
342 }
343 //对玩家落子评分
344 void judgePersonValue(int personNum,int empty,int sx,int sy)
345 {
346     int cnt = 0;
347     //连二
348     if (personNum == 1) {
349         cnt = 10;
350     }
351     //连三
352     else if (personNum == 2) {
353         //死三
354         if (empty == 1)
355             cnt = 30;
356         //活三
357         else if (empty == 2)
358             cnt = 40;
359     }
360     //连四
361     else if (personNum == 3) {
362         //死四
363         if (empty == 1)
364             cnt = 60;
365         //活四
366         else if (empty == 2)
367             cnt = 200;
368     }
369     //连五
370     else if (personNum == 4)
371         cnt = 20000;
372     if (cnt > aiOperation.ans) {
373         aiOperation.x = sx;
374         aiOperation.y = sy;
375         aiOperation.ans = cnt;
376     }
377 }
378 //初始化AI
379 void initAI()
380 {
381     aiOperation.ans = 0;
382     aiOperation.x = 0;
383     aiOperation.y = 0;
384     int mx[8] = { 1,-1,1,-1,0,0,1,-1 };
385     int my[8] = { -1,1,1,-1,1,-1,0,0 };
386     //对玩家进行评分 玩家是黑子
387     for(int i = 0;i < ROW;i++)
388         for (int j = 0; j < COL; j++) {
389             int sx = i, sy = j;
390             int empty,personNum,robotNum;
391             if (!Chess[sx][sy]) {
392                 int dx = sx, dy = sy;
393                 for (int k = 0; k < 4; k++) {
394                     personNum = 0;
395                     robotNum = 0;
396                     empty = 0;
397                     //判断玩家落子
398                     dx = sx, dy = sy;
399                     for (int z = 0; z < 5;z++) {
400                         dx = dx + mx[2 * k];
401                         dy = dy + my[2 * k];
402                         if (dx < 0 || dy < 0 || dx >= 15 || dy >= 15)
403                             break;
404                         //碰到AI棋子就退出
405                         if (Chess[dx][dy] == 2) {
406                             break;
407                         }
408                         //空格情况退出
409                         if (Chess[dx][dy] == 0) {
410                             empty++;
411                             break;
412                         }
413                         personNum++;
414                     }
415                     dx = sx,dy = sy;
416                     for (int z = 0; z < 5;z++) {
417                         dx = dx + mx[2 * k + 1];
418                         dy = dy + my[2 * k + 1];
419                         //越界情况退出
420                         if (dx < 0 || dy < 0 || dx >= 15 || dy >= 15)
421                             break;
422                         //碰到AI棋子就退出
423                         if (Chess[dx][dy] == 2) {
424                             break;
425                         }
426                         //碰到空格退出
427                         if (Chess[dx][dy] == 0) {
428                             empty++;
429                             break;
430                         }
431                         personNum++;
432                     }
433                     judgePersonValue(personNum,empty,sx,sy);
434                     //初始化搜索状态
435                     empty = 0;
436                     dx = sx,dy = sy;
437                     //判断AI落子
438                     for (int z = 0; z < 5;z++) {
439                         dx = dx + mx[2 * k];
440                         dy = dy + my[2 * k];
441                         if (dx < 0 || dy < 0 || dx >= 15 || dy >= 15)
442                             break;
443                         //碰到玩家棋子就退出
444                         if (Chess[dx][dy] == 1) {
445                             break;
446                         }
447                         //空格情况退出
448                         if (Chess[dx][dy] == 0) {
449                             empty++;
450                             break;
451                         }
452                         robotNum++;
453                     }
454                     dx = sx,dy = sy;
455                     for (int z = 0; z < 5;z++) {
456                         dx = dx + mx[2 * k + 1];
457                         dy = dy + my[2 * k + 1];
458                         //越界情况退出
459                         if (dx < 0 || dy < 0 || dx >= 15 || dy >= 15)
460                             break;
461                         //碰到玩家棋子就退出
462                         if (Chess[dx][dy] == 1) {
463                             break;
464                         }
465                         //碰到空格退出
466                         if (Chess[dx][dy] == 0) {
467                             empty++;
468                             break;
469                         }
470                         robotNum++;
471                     }
472                     judgeRobotValue(robotNum, empty, sx, sy);
473                 }
474             }
475         }
476 }
477 //AI操作
478 void aiEvent()
479 {
480     if (f == -1) {
481         initAI();
482         //AI选择框不显示
483         op.isShow = false;
484         op.row = aiOperation.x;
485         op.col = aiOperation.y;
486         op.x = aiOperation.x * GRID_W + INTERVAL;
487         op.y = aiOperation.y * GRID_W + INTERVAL;
488         //AI下棋
489         if (Chess[op.row][op.col] == 0) {
490             //棋盘状态标记
491             Chess[op.row][op.col] = 2;
492             drawCheckerBoard();
493             FlushBatchDraw();
494             //判断赢棋
495             if (judgeWinner(op.row, op.col)) {
496                 MessageBox(GetHWnd(), "Win", "AI", MB_OK);
497                 MessageBox(GetHWnd(), "重新开始", "下轮玩家先手", MB_OK);
498                 clearStack(white);
499                 clearStack(black);
500                 flushCheckerBoard();
501             }
502             //棋手状态转换
503             op.chessPlayer = Status::Black;
504             f = 1;
505         }
506         //满棋的情况
507         if (judgeFullchess()) {
508             MessageBox(GetHWnd(), "和棋", "重新开始", MB_OK);
509             clearStack(white);
510             clearStack(black);
511             op.chessPlayer = Status::Black;
512             f = 1;
513             flushCheckerBoard();
514         }
515     }
516     else {
517         //定义一个消息结构体变量
518         ExMessage msg;
519         //获取鼠标消息
520         if (peekmessage(&msg, EM_MOUSE)) {//EM_MOUSE只需要鼠标消息
521             if (msg.message == WM_LBUTTONDOWN) {
522                 if (msg.x >= 460 && msg.x <= 485 && msg.y >= 250 && msg.y < 280) {
523                     //清空棋子区
524                     clearStack(white);
525                     clearStack(black);
526                     MessageBox(GetHWnd(), "Win", "AI", MB_OK);
527                     MessageBox(GetHWnd(), "重新开始", "下轮玩家先手", MB_OK);
528                     //棋手状态转换
529                     op.chessPlayer = Status::Black;
530                     flushCheckerBoard();
531                     return;
532                 }
533                 else if (msg.x >= 460 && msg.x <= 485 && msg.y >= 285 && msg.y < 315) {
534                     //清空棋子区
535                     clearStack(white);
536                     clearStack(black);
537                     MessageBox(GetHWnd(), "重新开始", "平局", MB_OK);
538                     MessageBox(GetHWnd(), "", "下轮黑方先手", MB_OK);
539                     //棋手状态转换
540                     op.chessPlayer = Status::Black;
541                     flushCheckerBoard();
542                     return;
543                 }
544             }
545             //坐标校准(自动聚焦到棋盘上某个点的中心)
546             op.isShow = false;
547             int intervalGridx = msg.x % GRID_W;
548             int intervalGridy = msg.y % GRID_W;
549             int gridx, gridy;
550             //校准x坐标
551             if (intervalGridx <= GRID_W / 2)
552                 gridx = (msg.x / GRID_W) * GRID_W;
553             else
554                 gridx = (msg.x / GRID_W + 1) * GRID_W;
555             //校准y坐标
556             if (intervalGridy <= GRID_W / 2)
557                 gridy = (msg.y / GRID_W) * GRID_W;
558             else
559                 gridy = (msg.y / GRID_W + 1) * GRID_W;
560             //判断是否越出棋盘
561             if (gridx < INTERVAL || gridy < INTERVAL ||
562                 gridx > INTERVAL + GRID_W * 14 || gridy > INTERVAL + GRID_W * 14)
563                 return;
564             op.isShow = true;
565             op.row = (gridx - INTERVAL) / GRID_W;
566             op.col = (gridy - INTERVAL) / GRID_W;
567             op.x = gridx;
568             op.y = gridy;
569             //左击下棋
570             if (msg.message == WM_LBUTTONDOWN && Chess[op.row][op.col] == 0) {
571                 //棋盘状态标记
572                 Chess[op.row][op.col] = 1;
573                 drawCheckerBoard();
574                 FlushBatchDraw();
575                 //判断赢棋
576                 if (judgeWinner(op.row, op.col)) {
577                     MessageBox(GetHWnd(), "Win", "玩家", MB_OK);
578                     MessageBox(GetHWnd(), "重新开始", "下轮玩家先手", MB_OK);
579                     clearStack(white);
580                     clearStack(black);
581                     op.chessPlayer = Status::Black;
582                     f = 1;
583                     flushCheckerBoard();
584                     return;
585                 }
586                 //棋手状态转换
587                 op.chessPlayer = Status::White;
588                 f = -1;
589             }
590             //满棋的情况
591             if (judgeFullchess()) {
592                 MessageBox(GetHWnd(), "和棋", "重新开始", MB_OK);
593                 clearStack(white);
594                 clearStack(black);
595                 op.chessPlayer = Status::Black;
596                 f = 1;
597                 flushCheckerBoard();
598             }
599         }
600     }
601 }
602 //双人对战
603 void gameMode1()
604 {
605     bkmusic();
606     while (1)
607     {
608         //清屏
609         cleardevice();
610         //加载图片
611         bkimage();
612         //绘制棋盘
613         drawCheckerBoard();
614         //鼠标操作
615         mouseEvent();
616         //刷新
617         FlushBatchDraw();
618     }
619     //结束绘图
620     EndBatchDraw();
621 }
622 //人机对战
623 void gameMode2()
624 {
625     bkmusic();
626     while (1)
627     {
628         //清屏
629         cleardevice();
630         //加载图片
631         bkimage();
632         //绘制棋盘
633         drawCheckerBoard();
634         aiEvent();
635         //刷新
636         FlushBatchDraw();
637     }
638     //结束绘图
639     EndBatchDraw();
640 }
641 //游戏模式
642 void gameMode()
643 {//获取鼠标消息
644     ExMessage msg;
645     int gridx, gridy;
646     while (1) {
647         if (peekmessage(&msg, EM_MOUSE)) {//EM_MOUSE只需要鼠标消息
648             op.isShow = true;
649             if (msg.message == WM_LBUTTONDOWN) {
650                 gridx = msg.x;
651                 gridy = msg.y;
652                 if (gridx >= 170 && gridx <= 420 && gridy >= 170 && gridy < 220)
653                     gameMode1();
654                 else if (gridx >= 170 && gridx <= 420 && gridy >= 220 && gridy < 270)
655                     gameMode2();
656                 else if (gridx >= 170 && gridx <= 420 && gridy >= 270 && gridy < 320)
657                     return;
658             }
659         }
660     }
661 }
662 //开始菜单
663 void menu()
664 {
665     menuImage();
666     menumusic();
667     // 设置背景为透明
668     setbkmode(TRANSPARENT);
669     settextcolor(BLUE);
670     settextstyle(50, 15, "楷体");
671     outtextxy(170, 120, "  游戏菜单");
672     outtextxy(170, 170, "1.双人对战");
673     outtextxy(170, 220, "2.人机对战");
674     outtextxy(170, 270, "3.退出游戏");
675     gameMode();
676 }
677 int main()
678 {
679     initgraph(529, 529);//创建窗口
680     menu();
681     return 0;
682 }

四、项目展示

  五子棋

 

   五子棋

 

 五子棋

五、项目总结

  第一次写项目,很多函数不熟悉,也是边看教程边熟悉函数,难点在于估分函数的设置以及AI的α-β算法的剪枝算法,希望能够在这个项目的基础上再次优化一下AI的算法。

上一篇:P7486 「Stoi2031」彩虹


下一篇:为什么 ⌊lgN⌋=(N 的二进制表示的位数)-1