1. 程式人生 > >PyQt GUI程式設計——猜數字

PyQt GUI程式設計——猜數字

本文通過PyQt GUI實現一個簡單的小遊戲,猜數字。

QTDesinger 和PyUIC的安裝和使用參見前文《從零開始 使用PyQt5

一、  在QTDesigner中完成介面設計

0A0B 4個Label 顯示當前猜測結果中,有幾個數字和位置都正確(A),幾個僅數字正確(B);

0 Times 顯示目前為止本局已經猜了多少次;

中間四個 LineEdit  是玩家輸入數字區;

輸入區下面 Label 顯示對使用者的提示;

Guess按鈕  判決猜數字結果。

 

二、 使用PyUIC將上面的介面轉換為py檔案

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'GuessNumberMainWindow11.ui'
#
# Created by: PyQt5 UI code generator 5.11.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(519, 343)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(120, 130, 291, 71))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.lineEdit = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth())
        self.lineEdit.setSizePolicy(sizePolicy)
        self.lineEdit.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.lineEdit.setObjectName("lineEdit")
        self.horizontalLayout.addWidget(self.lineEdit)
        self.lineEdit_2 = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lineEdit_2.sizePolicy().hasHeightForWidth())
        self.lineEdit_2.setSizePolicy(sizePolicy)
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.horizontalLayout.addWidget(self.lineEdit_2)
        self.lineEdit_3 = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lineEdit_3.sizePolicy().hasHeightForWidth())
        self.lineEdit_3.setSizePolicy(sizePolicy)
        self.lineEdit_3.setObjectName("lineEdit_3")
        self.horizontalLayout.addWidget(self.lineEdit_3)
        self.lineEdit_4 = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lineEdit_4.sizePolicy().hasHeightForWidth())
        self.lineEdit_4.setSizePolicy(sizePolicy)
        self.lineEdit_4.setObjectName("lineEdit_4")
        self.horizontalLayout.addWidget(self.lineEdit_4)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(220, 250, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(210, 80, 121, 31))
        self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget_2)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
        self.label_2.setSizePolicy(sizePolicy)
        self.label_2.setAlignment(QtCore.Qt.AlignCenter)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_2.addWidget(self.label_2)
        self.label = QtWidgets.QLabel(self.horizontalLayoutWidget_2)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.horizontalLayout_2.addWidget(self.label)
        self.label_3 = QtWidgets.QLabel(self.horizontalLayoutWidget_2)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
        self.label_3.setSizePolicy(sizePolicy)
        self.label_3.setAlignment(QtCore.Qt.AlignCenter)
        self.label_3.setObjectName("label_3")
        self.horizontalLayout_2.addWidget(self.label_3)
        self.label_4 = QtWidgets.QLabel(self.horizontalLayoutWidget_2)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth())
        self.label_4.setSizePolicy(sizePolicy)
        self.label_4.setAlignment(QtCore.Qt.AlignCenter)
        self.label_4.setObjectName("label_4")
        self.horizontalLayout_2.addWidget(self.label_4)
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(-300, 400, 54, 12))
        self.label_5.setObjectName("label_5")
        self.label_hints = QtWidgets.QLabel(self.centralwidget)
        self.label_hints.setGeometry(QtCore.QRect(160, 220, 211, 16))
        self.label_hints.setAlignment(QtCore.Qt.AlignCenter)
        self.label_hints.setObjectName("label_hints")
        self.horizontalLayoutWidget_3 = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget_3.setGeometry(QtCore.QRect(420, 150, 131, 31))
        self.horizontalLayoutWidget_3.setObjectName("horizontalLayoutWidget_3")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_3)
        self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.label_guesstimes = QtWidgets.QLabel(self.horizontalLayoutWidget_3)
        self.label_guesstimes.setAlignment(QtCore.Qt.AlignCenter)
        self.label_guesstimes.setObjectName("label_guesstimes")
        self.horizontalLayout_3.addWidget(self.label_guesstimes)
        self.label_7 = QtWidgets.QLabel(self.horizontalLayoutWidget_3)
        self.label_7.setObjectName("label_7")
        self.horizontalLayout_3.addWidget(self.label_7)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 519, 23))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionshuoming = QtWidgets.QAction(MainWindow)
        self.actionshuoming.setObjectName("actionshuoming")
        self.actionhistroy = QtWidgets.QAction(MainWindow)
        self.actionhistroy.setObjectName("actionhistroy")
        self.menu.addSeparator()
        self.menu.addAction(self.actionshuoming)
        self.menu.addSeparator()
        self.menu.addAction(self.actionhistroy)
        self.menu.addSeparator()
        self.menubar.addAction(self.menu.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "GuessNumber"))
        self.pushButton.setText(_translate("MainWindow", "Guess"))
        self.label_2.setText(_translate("MainWindow", "0"))
        self.label.setText(_translate("MainWindow", "A"))
        self.label_3.setText(_translate("MainWindow", "0"))
        self.label_4.setText(_translate("MainWindow", "B"))
        self.label_5.setText(_translate("MainWindow", "TextLabel"))
        self.label_hints.setText(_translate("MainWindow", "The number is waiting for you :)"))
        self.label_guesstimes.setText(_translate("MainWindow", "0"))
        self.label_7.setText(_translate("MainWindow", "Times"))
        self.menu.setTitle(_translate("MainWindow", "Menu"))
        self.actionshuoming.setText(_translate("MainWindow", "Rules"))
        self.actionhistroy.setText(_translate("MainWindow", "Histroy"))

三、 啟動遊戲介面

新建Guess.py如下,from GuessNumberMainWindow import Ui_MainWindow匯入剛才生成的遊戲介面。

# -*- coding: utf-8 -*-
"""猜數字"""

from PyQt5 import QtWidgets   # 匯入PyQt5部件
import sys
from GuessNumberMainWindow import Ui_MainWindow

app = QtWidgets.QApplication(sys.argv)  # 建立application物件

first_window = Ui_MainWindow()  # 建立窗體物件

first_window.show()  # 顯示窗體

sys.exit(app.exec())  # 執行程式

直接執行Guess.py,提示錯誤 AttributeError: 'Ui_MainWindow' object has no attribute 'show'  

這是因為 第二步生成的GuessNumberMainWindow.py只定義了介面元素,沒有定義自己為主介面,也沒有初始化自己。

解決方法,修改GuessNumberMainWindow.py 如下,

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Ui_MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super(Ui_MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.retranslateUi(self)

    def setupUi(self, MainWindow):

……

再次執行Guess.py,成功顯示遊戲介面

Good Job!

 

四、約束使用者輸入

猜數字遊戲中,使用者輸入的是0-9的整數,且每個LineEdit中只能輸入一個數字。

然後我們要把輸入的數字顯示的大一點並且在LineEdit中間位置這樣看起來舒服一些。這項工作本來應該在QTDesigner中完成,不過在程式碼中也很簡單。

1)約束使用者只能輸入0-9的整數中的一個,使用QtGui.QIntValidator

int_validator = QtGui.QIntValidator() # validator for lineEdit

int_validator.setRange(0, 9)

self.lineEdit.setValidator(int_validator)

self.lineEdit.setMaxLength(1)  # 只能輸入一個數字

2)輸入的數字顯示的大一點(QtGui.QFont),並顯示在LineEdit中間位置

lineEdit_font = QtGui.QFont()
lineEdit_font.setPixelSize(24)
self.lineEdit.setFont(lineEdit_font)
self.lineEdit.setAlignment(QtCore.Qt.AlignCenter) #數字顯示在LineEdit中間位置

以上程式碼新增到GuessNumberMainWindow.py的retranslateUi方法中。

四個LineEdit全部如此處理。執行Guess.py 輸入數字,介面如下

五、 Guess按鈕被點選後,首先進一步校驗使用者輸入

猜數字遊戲中,使用者輸入的四個數字中不能出現重複,這種情況下判為無效,要求使用者重新輸入。

修改GuessNumberMainWindow.py檔案

1)定義校驗函式

def judge_guess(self):
    # 數字依次加入陣列(連結串列)
    print('judge_guess')
    array = [0, 0, 0, 0]
    array[0] = int(self.lineEdit.text())
    array[1] = int(self.lineEdit_2.text())
    array[2] = int(self.lineEdit_3.text())
    array[3] = int(self.lineEdit_4.text())

    # 去除陣列中的相同元素,根據資料長度判斷是否存在相同數字
    duplicated_array = list(set(array))
    # print(duplicated_array, duplicated_array.__len__())
    if duplicated_array.__len__() != array.__len__():
        # 重複數字的輸入不判斷猜測結果,提示使用者重新輸入
        self.label_hints.setText('Do not input duplicated numbers')
        self.label_hints.setStyleSheet("color:red")

2)繫結Guess按鈕的clicked訊號到judge_guess方法

def signal_slot(self, MainWindow):
        self.pushButton.clicked.connect(self.judge_guess)

     初始化函式中進行訊號槽繫結

def __init__(self, parent=None):
    super(Ui_MainWindow, self).__init__(parent)
    self.setupUi(self)
    self.retranslateUi(self)
    generate_aim_array()  # 生成目標資料
    self.signal_slot(self)

執行Guess.py 輸入相同數字並點選Guess按鈕,介面如下

六、 完成數字比較

新建Rules.py檔案實現如下功能

1)自動生成一組四個不同的數字作為目標。

2)比較使用者輸入的數字和目標數字,得出xAyB的匹配結果,有幾個數字和位置都正確(A),幾個僅數字正確(B);

# -*- coding: utf-8 -*-
import random
global aim_array, a_num, b_num
aim_array = [0, 0, 0, 0]
a_num = 0
b_num = 0


def generate_aim_array():
   global aim_array, a_num, b_num
   aim_array = random.sample(range(0, 9), 4)  # 0-9之間隨機生成四個不相同的數
   a_num = 0
   b_num = 0
   print(aim_array)


def compare_and_judge(array):
   # print('compare_and_judge')
   global a_num
   global b_num
   # print('range(len(aim_array):', range(len(aim_array)))
   # print('range(len(array):', range(len(array)))
   for aim_array_index in range(len(aim_array)):
      for array_index in range(len(array)):
         # print('aim_array:', aim_array_index, aim_array[aim_array_index])
         # print('array:', array_index, array[array_index])
         if aim_array[aim_array_index] == array[array_index]:
            if aim_array_index == array_index:
               a_num = a_num + 1
            else:
               b_num = b_num + 1
            break
   # print('a_num = ', a_num, 'b_num = ', b_num)


def getA():
   global a_num
   return a_num


def setA(a):
   global a_num
   a_num = a


def getB():
   global b_num
   return b_num


def setB(b):
   global b_num
   b_num = b

七、 完成遊戲

修改GuessNumberMainWindow.py檔案

1)第一次進入遊戲時自動生成一組四個不同的數字作為目標。

def __init__(self, parent=None):
……
    self.retranslateUi(self)
    generate_aim_array()  # 生成目標數字
    self.signal_slot(self)

2)點選Guess按鈕時判斷結果,當比較結果為4A0B時使用者輸入數字完全正確,本局遊戲結束,Guess按鈕上的文字變為New Game,玩家點選按鈕可以開始下一局遊戲。如果比較結果不為4A0B,玩家繼續猜數字,直到結果正確。

def judge_guess(self):
……
    if duplicated_array.__len__() != array.__len__():
……
    else:
        # 統計本局猜測的次數
        self.label_guesstimes.setText(str(int(self.label_guesstimes.text()) + 1))
        self.label_hints.setStyleSheet("color:black")

        # 判斷數字是否與結果相同,並給出對應提示
        compare_and_judge(array)
        self.label_2.setText(str(getA()))
        self.label_3.setText(str(getB()))
        if getA() == 4:  # 所有數字及位置都正確
            self.label_hints.setText('Good Job!')
            self.pushButton.setText('New Game')
            self.pushButton.disconnect()
            self.pushButton.clicked.connect(self.new_guess)
        else:
            self.label_hints.setText('Come on,you can make it!')
        setA(0)
        setB(0)

def new_guess(self):
    generate_aim_array()
    self.pushButton.setText('Guess')
    self.pushButton.disconnect()
    self.pushButton.clicked.connect(self.judge_guess)
    self.label_guesstimes.setText('0')
    self.label_2.setText('0')
    self.label_3.setText('0')
    self.lineEdit.clear()
    self.lineEdit_2.clear()
    self.lineEdit_3.clear()
    self.lineEdit_4.clear()

3)顯示玩家目前為止猜測的次數。程式碼包含在第二步。

到此遊戲就實現完畢了,執行Guess.py,玩一下吧。

八、一點點小優化

玩遊戲的時候發現,每次輸入數字的時候都要滑鼠去點一下對應方框才行,不大舒適。我們把它改成輸入一個數字後,游標自動跳轉到下一個EditLine,這樣玩家就可以連續輸入數字了。

修改GuessNumberMainWindow.py檔案

1)定義游標跳轉方法

def move_cursor(self):
    if self.sender() == self.lineEdit:
        self.lineEdit_2.setFocus()
    elif self.sender() == self.lineEdit_2:
        self.lineEdit_3.setFocus()
    elif self.sender() == self.lineEdit_3:
        self.lineEdit_4.setFocus()

2)繫結editLine輸入內容改變(訊號)與move_cursor(槽)

def signal_slot(self, MainWindow):
……
        self.lineEdit.textEdited.connect(self.move_cursor)
        self.lineEdit_2.textEdited.connect(self.move_cursor)
        self.lineEdit_3.textEdited.connect(self.move_cursor)

-------------------------------------------------------------------------------------------------------------------------

That's all.Thank you!