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操作SQLite简明教程
Jul 10 Python
浅谈Python中数据解析
May 05 Python
Python 爬虫的工具列表大全
Jan 31 Python
Python编程产生非均匀随机数的几种方法代码分享
Dec 13 Python
火车票抢票python代码公开揭秘!
Mar 08 Python
Python中的pack和unpack的使用
Mar 12 Python
Python numpy实现二维数组和一维数组拼接的方法
Jun 05 Python
Python使用pickle模块报错EOFError Ran out of input的解决方法
Aug 16 Python
PHP统计代码行数的小代码
Sep 19 Python
Python调用.net动态库实现过程解析
Jun 05 Python
使用Python获取爱奇艺电视剧弹幕数据的示例代码
Jan 12 Python
Python爬虫回测股票的实例讲解
Jan 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
解析web文件操作常见安全漏洞(目录、文件名检测漏洞)
2013/06/29 PHP
php pki加密技术(openssl)详解
2013/07/01 PHP
php redis实现文章发布系统(用户投票系统)
2017/03/04 PHP
Yii2框架配置文件(Application属性)与调试技巧实例分析
2019/05/27 PHP
Javascript 判断 object 的特定类转载
2007/02/01 Javascript
在网站上应该用的30个jQuery插件整理
2011/11/03 Javascript
Js base64 加密解密介绍
2013/10/11 Javascript
addEventListener()第三个参数useCapture (Boolean)详细解析
2013/11/07 Javascript
使用JavaScript实现连续滚动字幕效果的方法
2015/07/07 Javascript
jQuery绑定事件on()与弹窗的简要概述
2016/04/27 Javascript
工厂模式在JS中的实践
2017/01/18 Javascript
Vue 2中ref属性的使用方法及注意事项
2017/06/12 Javascript
Three.js如何用轨迹球插件(trackball)增加对模型的交互功能详解
2017/09/25 Javascript
jquery+ajaxform+springboot控件实现数据更新功能
2018/01/22 jQuery
JavaScript类数组对象转换为数组对象的方法实例分析
2018/07/24 Javascript
node.js使用fs读取文件出错的解决方案
2019/10/23 Javascript
vue prop传值类型检验方式
2020/07/30 Javascript
python中正则的使用指南
2016/12/04 Python
关于反爬虫的一些简单总结
2017/12/13 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
2018/07/04 Python
Selenium控制浏览器常见操作示例
2018/08/13 Python
python日期相关操作实例小结
2019/06/24 Python
利用python Selenium实现自动登陆京东签到领金币功能
2019/10/31 Python
python socket 聊天室实例代码详解
2019/11/14 Python
python实现实时视频流播放代码实例
2020/01/11 Python
Python3+Selenium+Chrome实现自动填写WPS表单
2020/02/12 Python
解决springboot yml配置 logging.level 报错问题
2020/02/21 Python
利用Python实现Excel的文件间的数据匹配功能
2020/06/16 Python
python 从list中随机取值的方法
2020/11/16 Python
JD Sports法国:英国篮球和运动时尚的领导者
2017/09/28 全球购物
分公司负责人任命书
2014/06/04 职场文书
开展读书活动总结
2014/06/30 职场文书
小学“向国旗敬礼”网上签名寄语活动总结
2014/09/27 职场文书
职工培训工作总结
2015/08/10 职场文书
2016年安全月活动总结
2016/04/06 职场文书
微软发布Windows 11今年最大更新22H2(附 ISO 镜像官方下载)
2022/09/23 数码科技