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


Posted in Python onMarch 24, 2020

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

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

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

第一步,收集素材

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

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

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

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

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

第二步,五子棋的逻辑类

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

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

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

chessboard.py

# ----------------------------------------------------------------------
# 定义棋子类型,输赢情况
# ----------------------------------------------------------------------
EMPTY = 0
BLACK = 1
WHITE = 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 ChessBoard
from ai import searcher

WIDTH = 540
HEIGHT = 540
MARGIN = 22
GRID = (WIDTH - 2 * MARGIN) / (15 - 1)
PIECE = 34
EMPTY = 0
BLACK = 1
WHITE = 2


import sys
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMessageBox
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QIcon, QPalette, QPainter
from 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的五子棋编程(人机对弈)

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

Python 相关文章推荐
python获取beautifulphoto随机某图片代码实例
Dec 18 Python
使用Python内置的模块与函数进行不同进制的数的转换
Mar 12 Python
Python的requests网络编程包使用教程
Jul 11 Python
django开发之settings.py中变量的全局引用详解
Mar 29 Python
python 统计代码行数简单实例
May 04 Python
Windows上使用Python增加或删除权限的方法
Apr 24 Python
Python实现的求解最小公倍数算法示例
May 03 Python
python矩阵转换为一维数组的实例
Jun 05 Python
python 列表推导式使用详解
Aug 29 Python
对python pandas中 inplace 参数的理解
Jun 27 Python
python Polars库的使用简介
Apr 21 Python
python爬虫之利用selenium模块自动登录CSDN
Apr 22 Python
Python制作exe文件简单流程
Jan 24 #Python
PyQt5实现类似别踩白块游戏
Jan 24 #Python
实例讲解Python脚本成为Windows中运行的exe文件
Jan 24 #Python
python随机在一张图像上截取任意大小图片的方法
Jan 24 #Python
Python爬虫实战之12306抢票开源
Jan 24 #Python
python+pyqt5实现24点小游戏
Jan 24 #Python
python中实现控制小数点位数的方法
Jan 24 #Python
You might like
Zend Studio for Eclipse的java.lang.NullPointerException错误的解决方法
2008/12/06 PHP
jQuery+CSS 实现的超Sexy下拉菜单
2010/01/17 Javascript
js对象继承之原型链继承实例
2015/01/10 Javascript
简单的jQuery入门指引
2015/07/28 Javascript
js+css实现超简洁的二级下拉菜单效果代码
2015/09/07 Javascript
JavaScript自定义文本框光标
2017/03/05 Javascript
微信小程序开发之toast提示插件使用示例
2017/06/08 Javascript
js禁止浏览器页面后退功能的实例(推荐)
2017/09/01 Javascript
js构造函数创建对象是否加new问题
2018/01/22 Javascript
JS实现对json对象排序并删除id相同项功能示例
2018/04/18 Javascript
简述Vue中容易被忽视的知识点
2019/12/09 Javascript
Node.js Domain 模块实例详解
2020/03/18 Javascript
小程序自定义导航栏兼容适配所有机型(附完整案例)
2020/04/26 Javascript
Openlayers显示瓦片网格信息的方法
2020/09/28 Javascript
python发送伪造的arp请求
2014/01/09 Python
python的pip安装以及使用教程
2018/09/18 Python
基于Python的OCR实现示例
2020/04/03 Python
基于python实现查询ip地址来源
2020/06/02 Python
使用phonegap创建联系人的实现方法
2017/03/30 HTML / CSS
赫里福德的一家乡村零售商店:Philip Morris & Son
2017/06/25 全球购物
澳大利亚领先的在线礼品网站:Gifts Australia
2020/08/15 全球购物
园长自我鉴定
2013/10/06 职场文书
行政管理专业推荐信
2013/11/02 职场文书
专营店会计助理岗位职责
2013/11/29 职场文书
实习生自我鉴定范文
2013/12/05 职场文书
青年文明号事迹材料
2014/01/18 职场文书
反邪教宣传工作方案
2014/05/07 职场文书
社区党员公开承诺书
2014/08/30 职场文书
杭州黄龙洞导游词
2015/02/10 职场文书
大学军训通讯稿
2015/07/18 职场文书
信息技术教研组工作总结
2015/08/13 职场文书
关于办理居住证的介绍信模板
2019/11/27 职场文书
详解PHP服务器如何在有限的资源里最大提升并发能力
2021/05/25 PHP
Java Spring Boot请求方式与请求映射过程分析
2022/06/25 Java/Android
MySQL 原理优化之Group By的优化技巧
2022/08/14 MySQL
MySQL 原理与优化之Limit 查询优化
2022/08/14 MySQL