PyQt5 を使った足し算ドリルをさらに改良しました

はじめに

PyQt5の勉強がてらに以前の計算ドリルをさらに改良しました。
touch-sp.hatenablog.com

新たに学習したこと

QHBoxLayout、QVBoxLayout、QGridLayout

以前は一つ一つ位置を指定してラベルやボタンを配置していました。Layoutを使うと非常に簡単でした。

画像のスケーリングの自動化

以前はラベルサイズを考慮して一つ一つ画像をスケーリングしていましたがそれを自動化しました。

def set_image_into_label(self, image, label):
    w = label.width()
    h = label.height()
    label.setPixmap(image.scaled(w-2, h-2, Qt.KeepAspectRatio, Qt.SmoothTransformation))

経過時間を表示

def startTimer(self):
    self.time1 = QTime.currentTime()
    delta = datetime.timedelta(0)
    self.timer_label.setText(str(delta))
    self.timer.start(1000)

def stopTimer(self):
    self.timer.stop()

def on_timeout(self):
    time2 = QTime(self.time1).secsTo(QTime.currentTime())
    delta = datetime.timedelta(seconds=time2)
    self.timer_label.setText(str(delta))

最終的なPythonスクリプト

import random
import datetime
import vlc

import sys
from PyQt5.QtCore import Qt, QTime, QTimer, QEventLoop
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QFont, QKeySequence

############################################
q1_max_num = 4
q2_max_num = 4
############################################

player0 = vlc.MediaListPlayer()
player1 = vlc.MediaListPlayer()
player2 = vlc.MediaListPlayer()

mediaList0 = vlc.MediaList(['./sound/dummy.wav'])
mediaList1 = vlc.MediaList(['./sound/ok.wav'])
mediaList2 = vlc.MediaList(['./sound/ok.wav', './sound/yattane.wav'])

player0.set_media_list(mediaList0)
player1.set_media_list(mediaList1)
player2.set_media_list(mediaList2)

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
        self.question = True
        self.q1 = 0
        self.q2 = 0
        self.real_answer = 0
        self.num_button_ready = False
        self.start_button_ready = True
        self.ok_count = 0
        self.time1 = None
        
    def initUI(self):
        
        self.donut_pixmap = QPixmap('./sound/star.png')
        self.num_pixmap = [QPixmap('./sound/%d.png'%i) for i in range(10)]
        self.plus_pixmap = QPixmap('./sound/plus.png')
        ### header ###
        top = QFrame()
        top.setFrameShape(QFrame.StyledPanel)
        header_layout = QHBoxLayout()
        self.star_label = [QLabel() for i in range(10)]
        for i in range(10):
            self.star_label[i].setFrameStyle(QFrame.Box | QFrame.Plain)
            self.star_label[i].setAlignment(Qt.AlignCenter)
            header_layout.addWidget(self.star_label[i])
        top.setLayout(header_layout)
        ### header ###

        ### main ###
        main = QFrame()
        main.setFrameStyle(QFrame.Box | QFrame.Plain)
        main_layout = QHBoxLayout()
        main_ratio = [2,1,2]
        self.num_label = [QLabel() for i in range(3)]
        for i in range(3):
            self.num_label[i].setFrameStyle(QFrame.NoFrame)
            self.num_label[i].setAlignment(Qt.AlignCenter)
            main_layout.addWidget(self.num_label[i], main_ratio[i])
        main.setLayout(main_layout)
        ### main ###

        ### footer ###
        self.timer_label = QLabel()
        self.timer_label.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.timer_label.setAlignment(Qt.AlignCenter)
        self.timer_label.setFont(QFont("Times", 36, QFont.Bold))
        ### footer ###

        vbox = QVBoxLayout()
        vbox.addWidget(top,1)
        vbox.addWidget(main,5)
        vbox.addWidget(self.timer_label,1)
        
        self.timer = QTimer()
        self.timer.timeout.connect(self.on_timeout)
        
        self.setLayout(vbox)        

        self.show()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.close()

        ### game start ###
        if e.key() == Qt.Key_Return and self.start_button_ready == True:
            self.start_button_ready = False
            self.num_button_ready = True
            self.ok_count = 0
            for i in range(10):
                self.star_label[i].clear()
            for i in range(3):
                self.num_label[i].clear()
            self.timer_label.clear()
            
            player0.play()

            loop = QEventLoop()
            QTimer.singleShot(1000, loop.quit)
            loop.exec_()
            
            self.startTimer()
            self.make_question()

        if QKeySequence(e.key()).toString() == str(self.real_answer) and self.num_button_ready == True:
            self.correct_answer()

    def make_question(self):
        self.q1 = random.randint(1, q1_max_num)
        self.q2 = random.randint(1, q2_max_num)
        self.real_answer = self.q1 + self.q2

        ### display ###
        self.set_image_into_label(self.num_pixmap[self.q1], self.num_label[0])
        self.set_image_into_label(self.plus_pixmap, self.num_label[1])
        self.set_image_into_label(self.num_pixmap[self.q2], self.num_label[2])

        self.num_button_ready = True

    def correct_answer(self):
        self.num_button_ready = False
        self.ok_count += 1
        self.set_image_into_label(self.donut_pixmap, self.star_label[self.ok_count -1])
        if self.ok_count == 10:
            self.now_playing = False
            self.stopTimer()
            player2.play()
            loop = QEventLoop()
            QTimer.singleShot(4000, loop.quit)
            loop.exec_()
            self.start_button_ready = True
        else:
            player1.play()
            loop = QEventLoop()
            QTimer.singleShot(800, loop.quit)
            loop.exec_()
            for i in range(3):
                self.num_label[i].clear()
            loop = QEventLoop()
            QTimer.singleShot(200, loop.quit)
            loop.exec_()
            self.make_question()
        
    def set_image_into_label(self, image, label):
        w = label.width()
        h = label.height()
        label.setPixmap(image.scaled(w-2, h-2, Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def startTimer(self):
        self.time1 = QTime.currentTime()
        delta = datetime.timedelta(0)
        self.timer_label.setText(str(delta))
        self.timer.start(1000)

    def stopTimer(self):
        self.timer.stop()

    def on_timeout(self):
        time2 = QTime(self.time1).secsTo(QTime.currentTime())
        delta = datetime.timedelta(seconds=time2)
        self.timer_label.setText(str(delta))

app = QApplication(sys.argv)
ex =Window()

ex.showFullScreen()
sys.exit(app.exec_())

動作環境

Windows 10
Python 3.8.6
PyQt5==5.15.4
PyQt5-Qt5==5.15.2
PyQt5-sip==12.8.1
python-vlc==3.0.11115

その他

スクリプトと使用する画像、音声ファイルをGitHubで公開しています。
VLCプレーヤーのインストールが必要です。
github.com