用Python制作五子棋人机对弈(人工智障版和升级AI版)

智障版截图:

在这里插入图片描述

智能版截图:

在这里插入图片描述

在这里插入图片描述

可能遇到的问题:

No module named ‘pyqt5‘解决办法

智障版源码:

背景:

muzm.jpg

在这里插入图片描述

window.py

  1 from PyQt5.QtWidgets import QMainWindow, QMessageBox
  2 from PyQt5.QtGui import QPainter, QPen, QColor, QPalette, QBrush, QPixmap, QRadialGradient
  3 from PyQt5.QtCore import Qt, QPoint, QTimer
  4 import traceback
  5 from game import Gomoku
  6 from corner_widget import CornerWidget
  7 
  8 
  9 def run_with_exc(f):
 10     """游戏运行出现错误时,用messagebox把错误信息显示出来"""
 11 
 12     def call(window, *args, **kwargs):
 13         try:
 14             return f(window, *args, **kwargs)
 15         except Exception:
 16             exc_info = traceback.format_exc()
 17             QMessageBox.about(window, '错误信息', exc_info)
 18     return call
 19 
 20 
 21 class GomokuWindow(QMainWindow):
 22 
 23     def __init__(self):
 24         super().__init__()
 25         self.init_ui()  # 初始化游戏界面
 26         self.g = Gomoku()  # 初始化游戏内容
 27 
 28         self.last_pos = (-1, -1)
 29         self.res = 0  # 记录那边获得了胜利
 30         self.operate_status = 0  # 游戏操作状态。0为游戏中(可操作),1为游戏结束闪烁过程中(不可操作)
 31 
 32     def init_ui(self):
 33         """初始化游戏界面"""
 34         # 1. 确定游戏界面的标题,大小和背景颜色
 35         self.setObjectName('MainWindow')
 36         self.setWindowTitle('五子棋')
 37         self.setFixedSize(650, 650)
 38         # self.setStyleSheet('#MainWindow{background-color: green}')
 39         palette = QPalette()
 40         palette.setBrush(QPalette.Window, QBrush(QPixmap('imgs/muzm.jpg')))
 41         self.setPalette(palette)
 42         # 2. 开启鼠标位置的追踪。并在鼠标位置移动时,使用特殊符号标记当前的位置
 43         self.setMouseTracking(True)
 44         # 3. 鼠标位置移动时,对鼠标位置的特殊标记
 45         self.corner_widget = CornerWidget(self)
 46         self.corner_widget.repaint()
 47         self.corner_widget.hide()
 48         # 4. 游戏结束时闪烁的定时器
 49         self.end_timer = QTimer(self)
 50         self.end_timer.timeout.connect(self.end_flash)
 51         self.flash_cnt = 0  # 游戏结束之前闪烁了多少次
 52         self.flash_pieces = ((-1, -1), )  # 哪些棋子需要闪烁
 53         # 5. 显示初始化的游戏界面
 54         self.show()
 55 
 56     @run_with_exc
 57     def paintEvent(self, e):
 58         """绘制游戏内容"""
 59 
 60         def draw_map():
 61             """绘制棋盘"""
 62             qp.setPen(QPen(QColor(0, 0, 0), 2, Qt.SolidLine))  # 棋盘的颜色为黑色
 63             # 绘制横线
 64             for x in range(15):
 65                 qp.drawLine(40 * (x + 1), 40, 40 * (x + 1), 600)
 66             # 绘制竖线
 67             for y in range(15):
 68                 qp.drawLine(40, 40 * (y + 1), 600, 40 * (y + 1))
 69             # 绘制棋盘中的黑点
 70             qp.setBrush(QColor(0, 0, 0))
 71             key_points = [(4, 4), (12, 4), (4, 12), (12, 12), (8, 8)]
 72             for t in key_points:
 73                 qp.drawEllipse(QPoint(40 * t[0], 40 * t[1]), 5, 5)
 74 
 75         def draw_pieces():
 76             """绘制棋子"""
 77             # 绘制黑棋子
 78             qp.setPen(QPen(QColor(0, 0, 0), 1, Qt.SolidLine))
 79             # qp.setBrush(QColor(0, 0, 0))
 80             for x in range(15):
 81                 for y in range(15):
 82                     if self.g.g_map[x][y] == 1:
 83                         if self.flash_cnt % 2 == 1 and (x, y) in self.flash_pieces:
 84                             continue
 85                         radial = QRadialGradient(40 * (x + 1), 40 * (y + 1), 15, 40 * x + 35, 40 * y + 35)  # 棋子的渐变效果
 86                         radial.setColorAt(0, QColor(96, 96, 96))
 87                         radial.setColorAt(1, QColor(0, 0, 0))
 88                         qp.setBrush(QBrush(radial))
 89                         qp.drawEllipse(QPoint(40 * (x + 1), 40 * (y + 1)), 15, 15)
 90             # 绘制白棋子
 91             qp.setPen(QPen(QColor(160, 160, 160), 1, Qt.SolidLine))
 92             # qp.setBrush(QColor(255, 255, 255))
 93             for x in range(15):
 94                 for y in range(15):
 95                     if self.g.g_map[x][y] == 2:
 96                         if self.flash_cnt % 2 == 1 and (x, y) in self.flash_pieces:
 97                             continue
 98                         radial = QRadialGradient(40 * (x + 1), 40 * (y + 1), 15, 40 * x + 35, 40 * y + 35)  # 棋子的渐变效果
 99                         radial.setColorAt(0, QColor(255, 255, 255))
100                         radial.setColorAt(1, QColor(160, 160, 160))
101                         qp.setBrush(QBrush(radial))
102                         qp.drawEllipse(QPoint(40 * (x + 1), 40 * (y + 1)), 15, 15)
103 
104         if hasattr(self, 'g'):  # 游戏还没开始的话,就不用画了
105             qp = QPainter()
106             qp.begin(self)
107             draw_map()  # 绘制棋盘
108             draw_pieces()  # 绘制棋子
109             qp.end()
110 
111     @run_with_exc
112     def mouseMoveEvent(self, e):
113         # 1. 首先判断鼠标位置对应棋盘中的哪一个格子
114         mouse_x = e.windowPos().x()
115         mouse_y = e.windowPos().y()
116         if 25 <= mouse_x <= 615 and 25 <= mouse_y <= 615 and (mouse_x % 40 <= 15 or mouse_x % 40 >= 25) and (mouse_y % 40 <= 15 or mouse_y % 40 >= 25):
117             game_x = int((mouse_x + 15) // 40) - 1
118             game_y = int((mouse_y + 15) // 40) - 1
119         else:  # 鼠标当前的位置不对应任何一个游戏格子,将其标记为(01, 01
120             game_x = -1
121             game_y = -1
122 
123         # 2. 然后判断鼠标位置较前一时刻是否发生了变化
124         pos_change = False  # 标记鼠标位置是否发生了变化
125         if game_x != self.last_pos[0] or game_y != self.last_pos[1]:
126             pos_change = True
127         self.last_pos = (game_x, game_y)
128         # 3. 最后根据鼠标位置的变化,绘制特殊标记
129         if pos_change and game_x != -1:
130             self.setCursor(Qt.PointingHandCursor)
131         if pos_change and game_x == -1:
132             self.setCursor(Qt.ArrowCursor)
133         if pos_change and game_x != -1:
134             self.corner_widget.move(25 + game_x * 40, 25 + game_y * 40)
135             self.corner_widget.show()
136         if pos_change and game_x == -1:
137             self.corner_widget.hide()
138 
139     @run_with_exc
140     def mousePressEvent(self, e):
141         """根据鼠标的动作,确定落子位置"""
142         if not (hasattr(self, 'operate_status') and self.operate_status == 0):
143             return
144         if e.button() == Qt.LeftButton:
145             # 1. 首先判断按下了哪个格子
146             mouse_x = e.windowPos().x()
147             mouse_y = e.windowPos().y()
148             if (mouse_x % 40 <= 15 or mouse_x % 40 >= 25) and (mouse_y % 40 <= 15 or mouse_y % 40 >= 25):
149                 game_x = int((mouse_x + 15) // 40) - 1
150                 game_y = int((mouse_y + 15) // 40) - 1
151             else:  # 鼠标点击的位置不正确
152                 return
153             self.g.move_1step(True, game_x, game_y)
154 
155             # 2. 根据操作结果进行一轮游戏循环
156             res, self.flash_pieces = self.g.game_result(show=True)  # 判断游戏结果
157             if res != 0:  # 如果游戏结果为“已经结束”,则显示游戏内容,并退出主循环
158                 self.repaint(0, 0, 650, 650)
159                 self.game_restart(res)
160                 return
161             self.g.ai_move_1step()  # 电脑下一步
162             res, self.flash_pieces = self.g.game_result(show=True)
163             if res != 0:
164                 self.repaint(0, 0, 650, 650)
165                 self.game_restart(res)
166                 return
167             self.repaint(0, 0, 650, 650)  # 在游戏还没有结束的情况下,显示游戏内容,并继续下一轮循环
168 
169     @run_with_exc
170     def end_flash(self):
171         # 游戏结束时的闪烁操作
172         if self.flash_cnt <= 5:
173             # 执行闪烁
174             self.flash_cnt += 1
175             self.repaint()
176         else:
177             # 闪烁完毕,执行重新开始的操作
178             self.end_timer.stop()
179             # 1. 显示游戏结束的信息
180             if self.res == 1:
181                 QMessageBox.about(self, '游戏结束', '玩家获胜!')
182             elif self.res == 2:
183                 QMessageBox.about(self, '游戏结束', '电脑获胜!')
184             elif self.res == 3:
185                 QMessageBox.about(self, '游戏结束', '平局!')
186             else:
187                 raise ValueError('当前游戏结束的标志位为' + self.res + '. 而游戏结束的标志位必须为1, 2 或 3')
188             # 2. 游戏重新开始的操作
189             self.res = 0
190             self.operate_status = 0
191             self.flash_cnt = 0
192             self.g = Gomoku()  # 重新初始化游戏内容
193             self.repaint(0, 0, 650, 650)  # 重新绘制游戏界面
194 
195     def game_restart(self, res):
196         """游戏出现开始"""
197         self.res = res  # 标记谁获胜了
198         self.operate_status = 1  # 游戏结束时的闪烁过程中,不可操作
199         self.end_timer.start(300)  # 开始结束时闪烁的计时器

 

corner_widget.py

 1 from PyQt5.QtGui import QPainter, QPen
 2 from PyQt5.QtWidgets import QWidget
 3 from PyQt5.QtCore import Qt
 4 
 5 
 6 class CornerWidget(QWidget):
 7 
 8     def __init__(self, parent):
 9         super().__init__(parent=parent)
10         self.setFixedSize(30, 30)
11 
12     def paintEvent(self, e):
13         qp = QPainter()
14         qp.begin(self)
15         pen = QPen(Qt.red, 3, Qt.SolidLine)
16         qp.setPen(pen)
17         qp.drawLine(0, 8, 0, 0)
18         qp.drawLine(0, 0, 8, 0)
19         qp.drawLine(22, 0, 28, 0)
20         qp.drawLine(28, 0, 28, 8)
21         qp.drawLine(28, 22, 28, 28)
22         qp.drawLine(28, 28, 20, 28)
23         qp.drawLine(8, 28, 0, 28)
24         qp.drawLine(0, 28, 0, 22)

 

game.py

  1 class Gomoku:
  2 
  3     def __init__(self):
  4         self.g_map = [[0 for y in range(15)] for x in range(15)]  # 当前的棋盘
  5         self.cur_step = 0  # 步数
  6 
  7     def move_1step(self, input_by_window=False, pos_x=None, pos_y=None):
  8         """
  9         玩家落子
 10         :param input_by_window: 是否从图形界面输入
 11         :param pos_x: 从图形界面输入时,输入的x坐标为多少
 12         :param pos_y: 从图形界面输入时,输入的y坐标为多少
 13         """
 14         while True:
 15             try:
 16                 if not input_by_window:
 17                     pos_x = int(input('x: '))  # 接受玩家的输入人
 18                     pos_y = int(input('y: '))
 19                 if 0 <= pos_x <= 14 and 0 <= pos_y <= 14:  # 判断这个格子能否落子
 20                     if self.g_map[pos_x][pos_y] == 0:
 21                         self.g_map[pos_x][pos_y] = 1
 22                         self.cur_step += 1
 23                         return
 24             except ValueError:  # 玩家输入不正确的情况(例如输入了‘A’)
 25                 continue
 26 
 27     def game_result(self, show=False):
 28         """判断游戏的结局。0为游戏进行中,1为玩家获胜,2为电脑获胜,3为平局"""
 29         # 1. 判断是否横向连续五子
 30         for x in range(11):
 31             for y in range(15):
 32                 if self.g_map[x][y] == 1 and self.g_map[x + 1][y] == 1 and self.g_map[x + 2][y] == 1 and self.g_map[x + 3][y] == 1 and self.g_map[x + 4][y] == 1:
 33                     if show:
 34                         return 1, [(x0, y) for x0 in range(x, x + 5)]
 35                     else:
 36                         return 1
 37                 if self.g_map[x][y] == 2 and self.g_map[x + 1][y] == 2 and self.g_map[x + 2][y] == 2 and self.g_map[x + 3][y] == 2 and self.g_map[x + 4][y] == 2:
 38                     if show:
 39                         return 2, [(x0, y) for x0 in range(x, x + 5)]
 40                     else:
 41                         return 2
 42 
 43         # 2. 判断是否纵向连续五子
 44         for x in range(15):
 45             for y in range(11):
 46                 if self.g_map[x][y] == 1 and self.g_map[x][y + 1] == 1 and self.g_map[x][y + 2] == 1 and self.g_map[x][y + 3] == 1 and self.g_map[x][y + 4] == 1:
 47                     if show:
 48                         return 1, [(x, y0) for y0 in range(y, y + 5)]
 49                     else:
 50                         return 1
 51                 if self.g_map[x][y] == 2 and self.g_map[x][y + 1] == 2 and self.g_map[x][y + 2] == 2 and self.g_map[x][y + 3] == 2 and self.g_map[x][y + 4] == 2:
 52                     if show:
 53                         return 2, [(x, y0) for y0 in range(y, y + 5)]
 54                     else:
 55                         return 2
 56 
 57         # 3. 判断是否有左上-右下的连续五子
 58         for x in range(11):
 59             for y in range(11):
 60                 if self.g_map[x][y] == 1 and self.g_map[x + 1][y + 1] == 1 and self.g_map[x + 2][y + 2] == 1 and self.g_map[x + 3][y + 3] == 1 and self.g_map[x + 4][y + 4] == 1:
 61                     if show:
 62                         return 1, [(x + t, y + t) for t in range(5)]
 63                     else:
 64                         return 1
 65                 if self.g_map[x][y] == 2 and self.g_map[x + 1][y + 1] == 2 and self.g_map[x + 2][y + 2] == 2 and self.g_map[x + 3][y + 3] == 2 and self.g_map[x + 4][y + 4] == 2:
 66                     if show:
 67                         return 2, [(x + t, y + t) for t in range(5)]
 68                     else:
 69                         return 2
 70 
 71         # 4. 判断是否有右上-左下的连续五子
 72         for x in range(11):
 73             for y in range(11):
 74                 if self.g_map[x + 4][y] == 1 and self.g_map[x + 3][y + 1] == 1 and self.g_map[x + 2][y + 2] == 1 and self.g_map[x + 1][y + 3] == 1 and self.g_map[x][y + 4] == 1:
 75                     if show:
 76                         return 1, [(x + t, y + 4 - t) for t in range(5)]
 77                     else:
 78                         return 1
 79                 if self.g_map[x + 4][y] == 2 and self.g_map[x + 3][y + 1] == 2 and self.g_map[x + 2][y + 2] == 2 and self.g_map[x + 1][y + 3] == 2 and self.g_map[x][y + 4] == 2:
 80                     if show:
 81                         return 2, [(x + t, y + 4 - t) for t in range(5)]
 82                     else:
 83                         return 2
 84 
 85         # 5. 判断是否为平局
 86         for x in range(15):
 87             for y in range(15):
 88                 if self.g_map[x][y] == 0:  # 棋盘中还有剩余的格子,不能判断为平局
 89                     if show:
 90                         return 0, [(-1, -1)]
 91                     else:
 92                         return 0
 93 
 94         if show:
 95             return 3, [(-1, -1)]
 96         else:
 97             return 3
 98 
 99     def ai_move_1step(self):
100         """电脑落子"""
101         for x in range(15):
102             for y in range(15):
103                 if self.g_map[x][y] == 0:
104                     self.g_map[x][y] = 2
105                     self.cur_step += 1
106                     return
107 
108     def show(self, res):
109         """显示游戏内容"""
110         for y in range(15):
111             for x in range(15):
112                 if self.g_map[x][y] == 0:
113                     print('  ', end='')
114                 elif self.g_map[x][y] == 1:
115                     print('', end='')
116                 elif self.g_map[x][y] == 2:
117                     print('×', end='')
118 
119                 if x != 14:
120                     print('-', end='')
121             print('\n', end='')
122             for x in range(15):
123                 print('|  ', end='')
124             print('\n', end='')
125 
126         if res == 1:
127             print('玩家获胜!')
128         elif res == 2:
129             print('电脑获胜!')
130         elif res == 3:
131             print('平局!')
132 
133     def play(self):
134         while True:
135             self.move_1step()  # 玩家下一步
136             res = self.game_result()  # 判断游戏结果
137             if res != 0:  # 如果游戏结果为“已经结束”,则显示游戏内容,并退出主循环
138                 self.show(res)
139                 return
140             self.ai_move_1step()  # 电脑下一步
141             res = self.game_result()
142             if res != 0:
143                 self.show(res)
144                 return
145             self.show(0)  # 在游戏还没有结束的情况下,显示游戏内容,并继续下一轮循环

 

main.py

 1 from PyQt5.QtWidgets import QApplication
 2 from window import GomokuWindow
 3 from game import Gomoku
 4 import sys
 5 
 6 
 7 def main():
 8     # g = Gomoku()
 9     # g.play()
10     app = QApplication(sys.argv)
11     ex = GomokuWindow()
12     sys.exit(app.exec_())
13 
14 
15 if __name__ == '__main__':
16     main()

 

运行main.py就可以领略智障版的风采了,包你百战百胜哦!

升级AI版:

升级AI版使用了C++版的AI脚本,源码和文件都在这里,感兴趣的可以下载学习。

链接:https://pan.baidu.com/s/1lR1yjbjuhDCp68nr-4OkcA
提取码:lbp3

在这里插入图片描述

 

posted @ 2021-02-19 12:50  BugMiaowu2021  阅读(1191)  评论(0编辑  收藏  举报