本文通过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!