1. 程式人生 > >基於PyQt5的五子棋程式設計(人機對弈)

基於PyQt5的五子棋程式設計(人機對弈)

這篇部落格主要是為了學習Python和PyQt,因為對棋類遊戲比較熱衷,所以從規則較簡單的五子棋入手,利用PyQt5實現圖形介面,做一個可以進行人機對弈的指令碼,最後打包成應用程式。AI的演算法打算用神經網路來完成,正在苦學TensorFlow中。

本來我以為五子棋規則很簡單,不就像小學時候玩的那樣,五個棋子連在一起就贏了嘛,但是後來發現事情並沒有那麼簡單,現在的五子棋有禁手這個規則 ,“三三禁手” 、“四四禁手”、“長連禁手”等等,都是為了限制現行一方必勝。我也不是職業的棋手,對吧,所以禁手什麼的就不考慮了,弄個簡單的成品出來就很滿足了。

程式碼全是邊學習邊寫的,有瑕疵的地方歡迎提出。

第一步,收集素材

主要就是棋子、棋盤的圖片,還有下棋的音效

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

音效與程式碼一起在最後給出

第二步,五子棋的邏輯類

收集完素材後,不著急介面的編寫,先將五子棋的邏輯寫好,介面和邏輯要分開,這很重要。

先想想在五子棋的邏輯類裡要有哪些東西。

首先是棋盤,棋盤用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和PyQt,因為對棋類遊戲比較熱衷,所以從規則較簡單的五子棋入手,利用PyQt5實現圖形介面,做一個可以進行人機對弈的指令碼,最後打包成應用程式。AI的演算法打算用神經網路來完成,正在苦學TensorFlow中。 本來我以為

VS2013 MFC基於對話方塊程式設計建立工程

一、新建MFC專案 選擇:基於對話方塊MFC的使用:  在共享DLL中使用MFC (程式執行需要dll)  在靜態庫中使用MFC (程式較大,執行時不需要dll) 設定MFC程式主框架樣式和標題  高階功能  若是用不上“ActiveX控制元件”可以去掉勾選,若要涉及

基於TCP的伺服器端/客戶端---------網路程式設計Linux----C

基於TCP的伺服器端/客戶端(二)---網路程式設計(Linux--C) 在基於TCP的伺服器端/客戶端(一)中的回聲客戶端存在的問題: 下列是echo_client.c中的程式碼: write(so

android與C# WebService基於ksoap通信C#篇

ldo art fadein length col scripts append hid ldoc 1.打開VS 2013新建項目>>ASP.NET空WEB應用程序(我用的是.net 4.0) 2.在剛建立的項目上加入新建項(Web

清華大學視頻課件:基於Linux的C++自主模式

清華大學 視頻課件 基於linux的c++基於Linux的C++(自主模式)課程簡介Linux操作系統開源的特性使得其獲得越來越重要的地位,而Linux系統編程也向C++程序設計者提出了更高的要求。本課程由C/C++語言的共性與特性出發,在深入學習程序設計語言的基礎上,進一步強調程序設計語言的適用性,並與Li

現代軟體工程第二次結對程式設計統計詞頻總結

作業要求及Github連結 作業要求:文字檔案中英語單詞的頻率 專案原始碼:統計詞頻 合作方式 有了第一次結對程式設計的經驗,我們這次有意識的採取了多種合作方式: 結對程式設計,我和隊友共用一臺顯示器和電腦完成了最簡單的-c -f標籤的處理和輸入輸出統一。 各自獨立程式設計,我和隊友各自獨立

網路程式設計UDP/TCP+JAVA學習筆記-DAY26

26.01_網路程式設計(網路程式設計概述)(瞭解) A:計算機網路 是指將地理位置不同的具有獨立功能的多臺計算機及其外部裝置,通過通訊線路連線起來,在網路作業系統,網路管理軟體及網路通訊協議的管理和協調下,實現資源共享和資訊傳遞的計算機系統。

JavaEE程式設計實驗 實驗1 Java常用工具類程式設計未完成

1.使用String類分割split將字串“Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide,available for a

基於Apache POI匯出百萬級大資料量Excel的實現

POI匯出大資料量excel (注:專案原始碼及後續更新請點選) 1、ExcelUtils類: package Utils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObje

AOP切面程式設計動態代理

面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。 怎麼算是面向切面?在不改變原始碼的情況下嵌入一塊其他的程式碼塊兒。 水平有限也解釋不太好。還是舉例說明。 原來我們有一個介面 public interface StudentInfoService

H5+C3+JS實現雙人對戰五子棋遊戲UI篇

本篇文章實現的是雙人對戰模式,主要是實現除人機AI演算法和判斷輸贏之外的其他功能,下一篇將會發布AI 框架搭建 前端精品教程:百度網盤下載 ? 1

安全程式設計十六- 楊輝三角

1.簡單介紹           楊輝三角大家應該都不陌生,我就不多說了! 1 1 1 1 2 1

安全程式設計十五- finally程式碼塊和Exception物件

1.異常處理的時候,finally程式碼塊的作用是什麼?         無論是否丟擲異常,fianlly程式碼塊總會被執行!就算沒有catch的情況下丟擲異常,finally任會被執行。finally程式碼塊的作用是釋放資源,比如I/O緩衝區,資料庫的連

安全程式設計十四- Java中throw和throws的區別

1.粗淺來說         throw是一個語句丟擲異常,throws是一個方法丟擲異常;         throw不是和try-catch-finally配套使用就是和throws配套使用,而throws可

安全程式設計十二 - static

1.引言         java中被static修飾的成員叫做靜態成員或類成員。她屬於整個類所有,而非某一個物件所有,即被類的所以物件所共享。靜態成員可以使用類名直接訪問,也可以使用物件名進行訪問。大多時候推薦使用類名進行訪問。   &nb

安全程式設計十一- ArrayList,Vector,LinkedList

1.三者介紹 1.1ArrayList     1.1.1簡單介紹:         ArrayList是Java集合中常用的資料結構,繼承自AbstractList,實現了List介面,是一個動態陣列,支援大小的改變,可以靈活

安全程式設計八 . 二- 有序陣列的實現

1.引言         有序陣列:陣列儲存有序的元素; 2.實現(暫不考慮重複元素)         (1).程式主體: public class OrderArray { priva

安全程式設計十七- Java的getter 和 setter

1.前言         我們在學習時,參考別人專案時會碰到像這樣的一些程式碼: package cn.edu.nuist.testSystem.beans; import java.io.Serializable; /**資料庫User表的bea

解決Linux下網路程式設計sendto send 出現 SIGPIPE 訊號導致程式異常終止的問題

引言 最近在Linux下網路程式設計時,出現SIGPIPE 訊號導致程式異常終止,本文記錄下解決的方法以及相應的知識。 SIGPIPE 訊號資料 什麼時候出現此訊號,APUE中有關此訊號的解釋如下: Linux man手冊有關此訊號的解釋: man 7 signal SI

前端練級之路——面向物件程式設計閉包

今天,我們來一起學習學習閉包吧。閉包是JavaScript中的一個重點,也是一個難點,很多高階應用都要依靠閉包實現。最重要的是,在前端面試中十家有八家都會問到閉包的問題。很多人面試的時候就被閉包虐了無數次,面試官們總是喜歡換著花樣通過閉包來虐求職者,真懷疑是不是面試官都有一點虐待傾向。不過面試官們既