用Python制作五子棋人机对弈(人工智障版和升级AI版)
智障版截图:
智能版截图:
可能遇到的问题:
智障版源码:
背景:
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