I am using python and am new to PyQt (and a bit new to python as well). I want to create a basic hangman game, and I have all of the controls figured out except I don't know how to draw the head, body, etc when a wrong letter is pressed. I tried using QPainter somehow but it doesn't work and upon research, some people say I have to use QPix map which I haven't heard of before so I am a bit confused. Here is the main bit of my code (I skipped me configuring the buttons for letters):
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QInputDialog, QWidget
from PyQt5.QtGui import QPainter, QBrush, QPen
from PyQt5.QtCore import Qt
import re
allowed = re.compile('[a-zA-Z ]')
class Ui_MainWindow(object):
answer = [] #list of correct charcters they have inputted
required = '' #string containing the solution
indicies = {} #indicies mapping the index of each correct character in solution to the actual character
wrong_count = 0
def setupUi(self, MainWindow):
### sets up all the buttons for letters of the alphabet, lines, etc. I have this figured out.
self.newgame = QtWidgets.QPushButton(self.centralwidget)
self.newgame.setGeometry(QtCore.QRect(800, 290, 231, 51))
font = QtGui.QFont()
font.setPointSize(20)
self.newgame.setFont(font)
self.newgame.setObjectName("newgame")
self.newgame.setText('New game')
self.newgame.clicked.connect(self.restart)
self.newgame.hide()
self.start = QtWidgets.QPushButton(self.centralwidget)
self.start.setGeometry(QtCore.QRect(480, 380, 231, 51))
font = QtGui.QFont()
font.setPointSize(20)
self.start.setFont(font)
self.start.setObjectName("start")
self.start.setText("Begin")
self.start.clicked.connect(self.get_name)
self.answerbox = QtWidgets.QLabel(self.centralwidget)
self.answerbox.setGeometry(QtCore.QRect(340, 480, 601, 91))
font = QtGui.QFont()
font.setPointSize(28)
self.answerbox.setFont(font)
self.answerbox.setText("")
self.answerbox.setObjectName("answerbox")
self.win = QtWidgets.QLabel(self.centralwidget)
self.win.setGeometry(QtCore.QRect(340, 400, 601, 91))
font = QtGui.QFont()
font.setPointSize(28)
self.win.setFont(font)
self.win.setText("")
self.win.setObjectName("win")
self.wrongtext = QtWidgets.QLabel(self.centralwidget)
self.wrongtext.setGeometry(QtCore.QRect(340, 500, 601, 91))
font = QtGui.QFont()
font.setPointSize(28)
self.wrongtext.setFont(font)
self.wrongtext.setText("Sorry, please type a valid string, without any special characters")
self.wrongtext.adjustSize()
self.wrongtext.setObjectName("wrongtext")
self.wrongtext.hide()
self.lost = QtWidgets.QLabel(self.centralwidget)
self.lost.setGeometry(QtCore.QRect(340, 500, 601, 91))
font = QtGui.QFont()
font.setPointSize(28)
self.lost.setFont(font)
self.lost.setText("Sorry, you lost")
self.lost.adjustSize()
self.lost.setObjectName("lost")
self.lost.hide()
def get_name(self):
text = App().text ##prompts user input for word
if text == '':
return
elif allowed.match(text):
Ui_MainWindow.required = text
self.answerbox.setText('_ '*len(text))
self.wrongtext.hide()
self.check_space(text)
self.answerbox.adjustSize()
self.start.hide()
else:
self.wrongtext.show()
def check_space(self, text):
list1 = list(text)
for ch in list1:
if ch == ' ':
self.revealer(ch)
Ui_MainWindow.answer.append(ch)
def clicked(self, ans): ### connected to letter buttons
if ans.lower() in Ui_MainWindow.required:
for _ in range(Ui_MainWindow.required.count(ans.lower())):
Ui_MainWindow.answer.append(ans.lower())
self.revealer(ans.lower())
self.checker()
else:
Ui_MainWindow.wrong_count += 1
self.paintEvent()
def paintEvent(self): ##### this is what doesn't work but I want it to be something like this
if Ui_MainWindow.wrong_count == 1: ### face
painter = QPainter(self)
painter.setPen(QPen(Qt.black, 10, Qt.SolidLine))
painter.drawEllipse(250, 320, 20, 20)
return
if Ui_MainWindow.wrong_count == 2: ### body
painter = QPainter(self)
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
painter.drawLine(250, 340, 250, 360)
return
if Ui_MainWindow.wrong_count == 3: ###legs
painter = QPainter(self)
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
painter.drawLine(250, 360, 260, 370)
return
if Ui_MainWindow.wrong_count == 4:
painter = QPainter(self)
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
painter.drawLine(250, 360, 240, 370)
return
if Ui_MainWindow.wrong_count == 5: ####arms
painter = QPainter(self)
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
painter.drawLine(250, 350, 260, 340)
return
if Ui_MainWindow.wrong_count == 6:
painter = QPainter(self)
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
painter.drawLine(250, 350, 240, 340)
self.lost.show()
self.newgame.show()
def revealer(self, ans):
index = Ui_MainWindow.required.index(ans) #find the index of a character from answer list in required list
Ui_MainWindow.indicies[index*2] = ans #assigns index to the character, to be used for displayed
try:
while True: ##checks if clicked character appears multiple times in the answer string and appends all of them
index = Ui_MainWindow.required.index(ans, index+1)
Ui_MainWindow.indicies[index*2] = ans
except:
pass
displayed = '_ '*len(Ui_MainWindow.required)
temp_list = list(displayed)
for index, ch in Ui_MainWindow.indicies.items():
temp_list[index] = ch
str2 = ''
displayed = str2.join(temp_list)
self.answerbox.setText(displayed)
def checker(self):
if sorted(Ui_MainWindow.answer) == sorted(list(Ui_MainWindow.required)):
self.win.setText("You won!")
self.newgame.show()
def restart(self):
Ui_MainWindow.answer = []
Ui_MainWindow.required = ''
Ui_MainWindow.indicies = {}
Ui_MainWindow.wrong_count = 1
self.win.setText("")
self.answerbox.setText("")
self.newgame.hide()
self.lost.hide()
self.start.show()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 input dialogs - pythonspot.com'
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(380, 280, 640, 480)
self.get_name()
def get_name(self):
text, ok = QInputDialog.getText(self, 'Text Input Dialog', 'Enter your word:')
self.text = text
if text and ok:
return str(text)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Sorry it's quite long. Basically when I run it everything works the way I want it, except if I press the wrong letter, nothing happens. It doesn't throw an error or anything. I tried using just QPainter separately to see how it works, and I made it draw a circle, line, etc, but not one by one the way I want. Thanks for reading and I'd appreciate any answers. Sorry if my layout for my question was bad, I only started using this website yesterday, so am not familiar with how things work just yet.
paintEvent
should be part of your QMainWindow
instance, not of the object you use for setting up the ui. In fact, most of the methods in Ui_MainWindow
really belong to your MainWindow
instance, so probably the easiest way to fix this is to just make Ui_MainWindow
a subclass of QMainWindow
, i.e.
class Ui_MainWindow(QtWidgets.QMainWindow):
# You could consider making these instance variables instead of class variables
answer = [] #list of correct charcters they have inputted
required = '' #string containing the solution
indicies = {} #indicies mapping the index of each correct character in solution to the actual character
wrong_count = 0
def __init__(self):
super().__init__()
self.centralwidget = QWidget(self)
self.setCentralWidget(self.centralwidget)
self.setupUi(self)
def paintEvent(self, event): # <-- paintEvent receives the paint event as an input parameter
....
# all other methods as before
if __name__ == "__main__":
app = QtWidgets.QApplication([])
MainWindow = Ui_MainWindow()
MainWindow.show()
MainWindow.resize(800,600)
app.exec()
This will at least call the paintEvent
, but there are still a number of issues with the implementation of paintEvent
. For one, painEvent
is called automatically every time the window needs redrawing. You shouldn't try to call this function yourself. If you think your widget needs redrawing you could use update
instead.
Also, paintEvent
is meant just for painting a widget and nothing else. This means that everything not-paint related like showing or hiding widgets should be moved elsewhere. In your case, the lines
self.lost.show()
self.newgame.show()
should be removed from paintEvent
and moved to for example clicked
instead, e.g.
def clicked(self, ans): ### connected to letter buttons
if ans.lower() in Ui_MainWindow.required:
for _ in range(Ui_MainWindow.required.count(ans.lower())):
Ui_MainWindow.answer.append(ans.lower())
self.revealer(ans.lower())
self.checker()
else:
Ui_MainWindow.wrong_count += 1
if self.wrong_count >= 6:
self.lost.show()
self.newgame.show()
Finally, since you only check for equality of wrong_count
in paintEvent
, at most one limb will be drawn at any time. This can be fixed by checking if wrong_count
is larger than a certain value instead of equal to, e.g.
def paintEvent(self, event):
painter = QPainter(self)
painter.begin(self)
if Ui_MainWindow.wrong_count >= 1: ### face
painter.setPen(QPen(Qt.black, 10, Qt.SolidLine))
painter.drawEllipse(250, 320, 20, 20)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.black)
if Ui_MainWindow.wrong_count >= 2: ### body
painter.drawLine(250, 340, 250, 360)
if Ui_MainWindow.wrong_count >= 3: ###legs
painter.drawLine(250, 360, 260, 370)
if Ui_MainWindow.wrong_count >= 4:
painter.drawLine(250, 360, 240, 370)
if Ui_MainWindow.wrong_count >= 5: ####arms
painter.drawLine(250, 350, 260, 340)
if Ui_MainWindow.wrong_count >= 6:
painter.drawLine(250, 350, 240, 340)
painter.end()