溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PyQt5實現五子棋游戲(人機對弈)

發布時間:2020-09-03 17:37:32 來源:腳本之家 閱讀:463 作者:ColinFred 欄目:開發技術

這篇博客主要是為了學習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的五子棋編程(人機對弈)

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女