侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

PyQt5实现五子棋游戏(人机对弈)

Python  /  管理员 发布于 5年前   232

这篇博客主要是为了学习Python和PyQt,因为对棋类游戏比较热衷,所以从规则较简单的五子棋入手,利用PyQt5实现图形界面,做一个可以进行人机对弈的脚本,最后打包成应用程序。AI的算法打算用神经网络来完成,正在苦学TensorFlow中。

本来我以为五子棋规则很简单,不就像小学时候玩的那样,五个棋子连在一起就赢了嘛,但是后来发现事情并没有那么简单,现在的五子棋有禁手这个规则 ,“三三禁手” 、“四四禁手”、“长连禁手”等等,都是为了限制现行一方必胜。我也不是职业的棋手,对吧,所以禁手什么的就不考虑了,弄个简单的成品出来就很满足了。

代码全是边学习边写的,有瑕疵的地方欢迎提出。

第一步,收集素材

主要就是棋子、棋盘的图片,还有下棋的音效

音效与代码一起在最后给出

第二步,五子棋的逻辑类

收集完素材后,不着急界面的编写,先将五子棋的逻辑写好,界面和逻辑要分开,这很重要。

先想想在五子棋的逻辑类里要有哪些东西。

首先是棋盘,棋盘用15*15的数组表示
然后是棋子,黑棋用1表示,白棋用2表示,空白就用0表示
再然后还要获取指定点的坐标,获取指定点的方向等等。
最重要的也是稍微有点难度的部分就是判断输赢。结合网上的方法和我自己的理解,下面贴出我写的代码,仅供参考。

chessboard.py

# ----------------------------------------------------------------------# 定义棋子类型,输赢情况# ----------------------------------------------------------------------EMPTY = 0BLACK = 1WHITE = 2# ----------------------------------------------------------------------# 定义棋盘类,绘制棋盘的形状,切换先后手,判断输赢等# ----------------------------------------------------------------------class ChessBoard(object): def __init__(self):  self.__board = [[EMPTY for n in range(15)] for m in range(15)]  self.__dir = [[(-1, 0), (1, 0)], [(0, -1), (0, 1)], [(-1, 1), (1, -1)], [(-1, -1), (1, 1)]]  #    (左  右)  (上  下)  (左下  右上)  (左上  右下) def board(self): # 返回数组对象  return self.__board def draw_xy(self, x, y, state): # 获取落子点坐标的状态  self.__board[x][y] = state def get_xy_on_logic_state(self, x, y): # 获取指定点坐标的状态  return self.__board[x][y] def get_next_xy(self, point, direction): # 获取指定点的指定方向的坐标  x = point[0] + direction[0]  y = point[1] + direction[1]  if x < 0 or x >= 15 or y < 0 or y >= 15:   return False  else:   return x, y def get_xy_on_direction_state(self, point, direction): # 获取指定点的指定方向的状态  if point is not False:   xy = self.get_next_xy(point, direction)   if xy is not False:    x, y = xy    return self.__board[x][y]  return False def anyone_win(self, x, y):  state = self.get_xy_on_logic_state(x, y) # 当前落下的棋是黑棋还是白棋,它的状态存储在state中  for directions in self.__dir: # 对米字的4个方向分别检测是否有5子相连的棋   count = 1 # 初始记录为1,因为刚落下的棋也算   for direction in directions: # 对落下的棋子的同一条线的两侧都要检测,结果累积    point = (x, y) # 每次循环前都要刷新    while True:     if self.get_xy_on_direction_state(point, direction) == state:      count += 1      point = self.get_next_xy(point, direction)     else:      break   if count >= 5:    return state  return EMPTY def reset(self): # 重置  self.__board = [[EMPTY for n in range(15)] for m in range(15)]

将上面的代码放在chessboard.py里面就完成了最基本的操作了。

第三步,利用PyQt5实现图形界面

先想好思路。

1.目标是做一个简易的五子棋的界面,主窗口只需要一个Widget就可以了

2.Widget的背景设置为棋盘图片

3.鼠标每点击一次空白区域,该区域就添加一个标签,在标签中插入棋子图片

4.因为是人机对弈,玩家执黑棋,所以可以将鼠标变成黑棋图片(这一点比较复杂,需要重写标签类)

5.整体逻辑是:鼠标点击一次―->换算坐标(UI坐标到棋盘坐标)―->判断坐标是否合理―->黑棋落在棋盘上―->判断是否赢棋―->电脑思考―->电脑下白棋―->判断是否赢棋……

6.因为AI思考需要时间,所以还需要加一个线程,单独让它计算AI的走法

7.一些细节问题: 赢棋和输棋怎么处理(对话框)、和棋怎么办(这个先不考虑)、游戏后期棋子非常多的时候容易眼花,不知道AI走到哪怎么办(加一个指示箭头)、音效怎么插入(用QSound)等等

下面给出整体代码:

gobangGUI.py

from chessboard import ChessBoardfrom ai import searcherWIDTH = 540HEIGHT = 540MARGIN = 22GRID = (WIDTH - 2 * MARGIN) / (15 - 1)PIECE = 34EMPTY = 0BLACK = 1WHITE = 2import sysfrom PyQt5 import QtCore, QtGuifrom PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMessageBoxfrom PyQt5.QtCore import Qtfrom PyQt5.QtGui import QPixmap, QIcon, QPalette, QPainterfrom PyQt5.QtMultimedia import QSound# ----------------------------------------------------------------------# 定义线程类执行AI的算法# ----------------------------------------------------------------------class AI(QtCore.QThread): finishSignal = QtCore.pyqtSignal(int, int) # 构造函数里增加形参 def __init__(self, board, parent=None):  super(AI, self).__init__(parent)  self.board = board # 重写 run() 函数 def run(self):  self.ai = searcher()  self.ai.board = self.board  score, x, y = self.ai.search(2, 2)  self.finishSignal.emit(x, y)# ----------------------------------------------------------------------# 重新定义Label类# ----------------------------------------------------------------------class LaBel(QLabel): def __init__(self, parent):  super().__init__(parent)  self.setMouseTracking(True) def enterEvent(self, e):  e.ignore()class GoBang(QWidget): def __init__(self):  super().__init__()  self.initUI() def initUI(self):  self.chessboard = ChessBoard() # 棋盘类  palette1 = QPalette() # 设置棋盘背景  palette1.setBrush(self.backgroundRole(), QtGui.QBrush(QtGui.QPixmap('img/chessboard.jpg')))  self.setPalette(palette1)  # self.setStyleSheet("board-image:url(img/chessboard.jpg)") # 不知道这为什么不行  self.setCursor(Qt.PointingHandCursor) # 鼠标变成手指形状  self.sound_piece = QSound("sound/luozi.wav") # 加载落子音效  self.sound_win = QSound("sound/win.wav") # 加载胜利音效  self.sound_defeated = QSound("sound/defeated.wav") # 加载失败音效  self.resize(WIDTH, HEIGHT) # 固定大小 540*540  self.setMinimumSize(QtCore.QSize(WIDTH, HEIGHT))  self.setMaximumSize(QtCore.QSize(WIDTH, HEIGHT))  self.setWindowTitle("GoBang") # 窗口名称  self.setWindowIcon(QIcon('img/black.png')) # 窗口图标  # self.lb1 = QLabel('   ', self)  # self.lb1.move(20, 10)  self.black = QPixmap('img/black.png')  self.white = QPixmap('img/white.png')  self.piece_now = BLACK # 黑棋先行  self.my_turn = True # 玩家先行  self.step = 0 # 步数  self.x, self.y = 1000, 1000  self.mouse_point = LaBel(self) # 将鼠标图片改为棋子  self.mouse_point.setScaledContents(True)  self.mouse_point.setPixmap(self.black) #加载黑棋  self.mouse_point.setGeometry(270, 270, PIECE, PIECE)  self.pieces = [LaBel(self) for i in range(225)] # 新建棋子标签,准备在棋盘上绘制棋子  for piece in self.pieces:   piece.setVisible(True) # 图片可视   piece.setScaledContents(True) #图片大小根据标签大小可变  self.mouse_point.raise_() # 鼠标始终在最上层  self.ai_down = True # AI已下棋,主要是为了加锁,当值是False的时候说明AI正在思考,这时候玩家鼠标点击失效,要忽略掉 mousePressEvent  self.setMouseTracking(True)  self.show() def paintEvent(self, event): # 画出指示箭头  qp = QPainter()  qp.begin(self)  self.drawLines(qp)  qp.end() def mouseMoveEvent(self, e): # 黑色棋子随鼠标移动  # self.lb1.setText(str(e.x()) + ' ' + str(e.y()))  self.mouse_point.move(e.x() - 16, e.y() - 16) def mousePressEvent(self, e): # 玩家下棋  if e.button() == Qt.LeftButton and self.ai_down == True:   x, y = e.x(), e.y() # 鼠标坐标   i, j = self.coordinate_transform_pixel2map(x, y) # 对应棋盘坐标   if not i is None and not j is None: # 棋子落在棋盘上,排除边缘    if self.chessboard.get_xy_on_logic_state(i, j) == EMPTY: # 棋子落在空白处     self.draw(i, j)     self.ai_down = False     board = self.chessboard.board()     self.AI = AI(board) # 新建线程对象,传入棋盘参数     self.AI.finishSignal.connect(self.AI_draw) # 结束线程,传出参数     self.AI.start() # run def AI_draw(self, i, j):  if self.step != 0:   self.draw(i, j) # AI   self.x, self.y = self.coordinate_transform_map2pixel(i, j)  self.ai_down = True  self.update() def draw(self, i, j):  x, y = self.coordinate_transform_map2pixel(i, j)  if self.piece_now == BLACK:   self.pieces[self.step].setPixmap(self.black) # 放置黑色棋子   self.piece_now = WHITE   self.chessboard.draw_xy(i, j, BLACK)  else:   self.pieces[self.step].setPixmap(self.white) # 放置白色棋子   self.piece_now = BLACK   self.chessboard.draw_xy(i, j, WHITE)  self.pieces[self.step].setGeometry(x, y, PIECE, PIECE) # 画出棋子  self.sound_piece.play() # 落子音效  self.step += 1 # 步数+1  winner = self.chessboard.anyone_win(i, j) # 判断输赢  if winner != EMPTY:   self.mouse_point.clear()   self.gameover(winner) def drawLines(self, qp): # 指示AI当前下的棋子  if self.step != 0:   pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)   qp.setPen(pen)   qp.drawLine(self.x - 5, self.y - 5, self.x + 3, self.y + 3)   qp.drawLine(self.x + 3, self.y, self.x + 3, self.y + 3)   qp.drawLine(self.x, self.y + 3, self.x + 3, self.y + 3) def coordinate_transform_map2pixel(self, i, j):  # 从 chessMap 里的逻辑坐标到 UI 上的绘制坐标的转换  return MARGIN + j * GRID - PIECE / 2, MARGIN + i * GRID - PIECE / 2 def coordinate_transform_pixel2map(self, x, y):  # 从 UI 上的绘制坐标到 chessMap 里的逻辑坐标的转换  i, j = int(round((y - MARGIN) / GRID)), int(round((x - MARGIN) / GRID))  # 有MAGIN, 排除边缘位置导致 i,j 越界  if i < 0 or i >= 15 or j < 0 or j >= 15:   return None, None  else:   return i, j def gameover(self, winner):  if winner == BLACK:   self.sound_win.play()   reply = QMessageBox.question(self, 'You Win!', 'Continue?',           QMessageBox.Yes | QMessageBox.No, QMessageBox.No)  else:   self.sound_defeated.play()   reply = QMessageBox.question(self, 'You Lost!', 'Continue?',           QMessageBox.Yes | QMessageBox.No, QMessageBox.No)  if reply == QMessageBox.Yes: # 复位   self.piece_now = BLACK   self.mouse_point.setPixmap(self.black)   self.step = 0   for piece in self.pieces:    piece.clear()   self.chessboard.reset()   self.update()  else:   self.close()if __name__ == '__main__': app = QApplication(sys.argv) ex = GoBang() sys.exit(app.exec_())

简要说明一下

class AI(QtCore.QThread): finishSignal = QtCore.pyqtSignal(int, int) # 构造函数里增加形参 def __init__(self, board, parent=None):  super(AI, self).__init__(parent)  self.board = board # 重写 run() 函数 def run(self):  self.ai = searcher()  self.ai.board = self.board  score, x, y = self.ai.search(2, 2)  self.finishSignal.emit(x, y)

这里加了一个线程执行AI的计算,前面有个 from ai import searcher ,ai还没有写,先从网上找了一个博弈的算法。searcher()就是AI类。该线程传入参数是 board 就是棋盘状态。调用self.ai.search(2, 2),第一个2是博弈树的深度,值越大AI越聪明,但是计算时间也越长。第二个2是说电脑执白棋,如果为1则是黑棋。线程结束后传入参数 x, y 就是AI计算后线程传出的参数。

class LaBel(QLabel): def __init__(self, parent):  super().__init__(parent)  self.setMouseTracking(True) def enterEvent(self, e):  e.ignore()

重新定义Label类是为了让黑棋图片随着鼠标的移动而移动。如果直接用QLabel的话不能达到预期的效果,具体为什么自己去摸索吧。

最后是所有的脚本代码,在这之后还会继续学习,将脚本打包成可执行文件,并且加入神经网络的算法。

基于PyQt5的五子棋编程(人机对弈)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    在numpy矩阵中令小于0的元素改为0的实例
    下一条:
    PyQt5实现类似别踩白块游戏
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客