пятница, 23 августа 2019 г.

Учи таблицу умножения с Бэтменом

Доброе утро али день, мой дорогой друг!
Через несколько дней наступит долгожданная и ненавистная пора школьных занятий и миллионы юных и сонных неучей устремятся в школы за знаниями.
Там их будут мучить домашними заданиями, тестами и прочими контрольными.
А самые юные из них начнут свое знакомство с НЕЙ - великой и ужасной - Таблицей Умножения!
Конечно, родители дома будут продолжать мучить несчастных, неожиданно задавая вопрос посреди разговора: "семь умножить на восемь, быстро??", от чего становится еще тоскливее и печальнее...
Вот было бы классно, если можно выучить таблицу умножения с другом. Причем, с таким другом, который, конечно, круче самих родителей... Например, с Бэтменом!
А у меня и телефончик его завалялся=)
Эй, Рыцарь Ночи, помоги выучить таблицу умножения, будь другом!


Цель

Итак, сегодня мы с помощью программирования совершим маленькое чудо, и напишем программу для изучения таблицы умножения для юных страдальцев.
Программа должна иметь графический интерфейс, иметь визуальную стилистику Темного Рыцаря, говорить голосом Бэтмена. Казалось бы, такая классная программа нужна и важна сама по себе, но нет, возьмем планку выше - она еще должна случайным образом задавать вопросы, типа "2x2=?" и проверять ответы. 
Начнем с главного вопроса: чем будем рисовать окошки? 
Конечно моей первой мыслью, как программиста на Java, было Swing, но...
От мысли, что нужно писать детскую веселую программку на могучем и грозном языке Enterprise разработки, повеяло холодком и я отложил эту мысль подальше.

Что же еще умеет рисовать красивые окошки? 
Причем хотелось бы именно рисовать, не утруждая себя написанием кода, который задает местоположение кнопочек в окне.
И тут я вспомнил про Qt. Великий и прекрасный Qt!
С помощью Qt можно нарисовать любые, самые фантастические окошки. Существует QtCreator и QtDesigner для таких лентяев как я, которые хотят просто набросать кнопочек на поле - и все готово.
Но есть один ужасный минус - чтобы использовать Qt, нужно писать на C++.
А от мысли, что нужно писать детскую веселую программку на C++, повеяло таким морозом, что я, как Эддард Старк, забормотал: "Зима близко..."


И все-таки Qt мне нравится, можно ли как-то обойтись без C++?
Оказалось, можно!

Qt for Python

А не написать ли мне все на Python? А что, мне это нравится))
Сочетание простого лаконичного синтаксиса Python и мощь графической библиотеки Qt - это однозначно круто!
Разработчики Qt, видимо, тоже так думали, когда создавали Qt for Python, официальную библиотеку на Python для работы с Qt.
Спасибо им за это огромное от лица всех школьников, еще не выучивших таблицу умножения!

Инструменты

Итак, для создания нашей чудо-программы, мы будем использовать:
  1. Python v3.7.3;
  2. Jetbrans PyCharm в качестве IDE;
  3. QtDesigner v5.11.1 в качестве рисовалки окошек;
  4. Горячий шоколад, для придания себе отличного настроения!
Теперь, когда мы во всеоружии - поехали!

Рисуем окошко

Итак, скачиваем QtDesigner, запускаем его. Он сразу предлагает создать форму, выбираем вариант Main Window. 
Форма нашей программы очень простая и содержит всего 4 элемента:
  1. Картинка с Бэтменом (QLabel);
  2. Надпись с вопросом, типа 2x2 (QLabel);
  3. Поле вводе ответа (QTextEdit);
  4. Кнопка проверки ответа (QPushButton).
Все эти элементы управления нашел в списке виджетов, перетащил мышкой на формочку и подогнал по размеру. 
Единственный неожиданный для меня момент оказался связан с тем, что  картинка на формочке - это тоже QLabel, ну то есть надпись. 
Но не будем формалистами, работает и ладно. 

Получилось что-то такое:


Нажатием клавиш Ctrl+R можно запустить окошко и посмотреть его в режиме предпросмотра. Удобно.

С рисованием закончено, сохраняем форму в виде файла main.ui вместе с картинкой Бэтмена и иконкой стрелки повтора для кнопки в одну папку.
battest
├── batman.png
├── refresh.png
└── main.ui
Содержимое файла main.ui выглядит примерно так:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>722</width>
    <height>405</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QLabel" name="batman">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>0</y>
      <width>431</width>
      <height>371</height>
     </rect>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="pixmap">
     <pixmap>back.png</pixmap>
    </property>
   </widget>
   <widget class="QLabel" name="label">
    <property name="geometry">
     <rect>
      <x>440</x>
      <y>10</y>
      <width>271</width>
      <height>51</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <pointsize>30</pointsize>
     </font>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="alignment">
     <set>Qt::AlignCenter</set>
    </property>
   </widget>
   <widget class="QTextEdit" name="result">
    <property name="geometry">
     <rect>
      <x>440</x>
      <y>90</y>
      <width>271</width>
      <height>71</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <pointsize>30</pointsize>
     </font>
    </property>
    <property name="acceptRichText">
     <bool>true</bool>
    </property>
   </widget>
   <widget class="QPushButton" name="refresh">
    <property name="geometry">
     <rect>
      <x>444</x>
      <y>172</y>
      <width>271</width>
      <height>181</height>
     </rect>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="icon">
     <iconset>
      <normaloff>refresh.png</normaloff>refresh.png</iconset>
    </property>
    <property name="iconSize">
     <size>
      <width>128</width>
      <height>128</height>
     </size>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>722</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
Теперь нужно написать немного кода на Python, чтобы придать этой красоте немного жизни))

Пишем код

Создаем в нашей папочке battest файл main.py, который будет содержать весь код нашей программы. 

Для начала, нам нужно импортировать все необходимые библиотеки:
#Доступ к функциям операционной системы
import sys
#Генератор случайных чисел
import random
#Для работы с аудио
from playsound import playsound
#Загрузчик формочек от QtCreator
from PyQt5 import uic
#Классики для работы с Qt
from PyQt5.QtWidgets import QApplication, QMessageBox, QErrorMessage
Это все библиотеки, которые нам понадобятся. Если каких-то библиотек не окажется на вашем компьютере, то PyCharm любезно спросит нас, не хотим ли мы их установить.
Не спорьте с PyCharm и соглашайтесь, она знает, что делает=)

Теперь, когда все нужные библиотеки установлены, нужно загрузить формочку, нарисованную в QtCreator.
Это делается всего одной строчкой:
#Загружаем формочку, нарисованную в QtCreator
Form, Window = uic.loadUiType("main.ui")
В результате загрузки формы у нас есть два класса Form и Window, которые мы используем для отображения нарисованной формочки.
Создадим собственный класс BatTestForm, который будет наследоваться от класса Form и содержать логику игры
# Класс формы с логикой
class BatTestForm(Form):
    # Конструктор получает на вход переменную total - количество вопросов, на которые должен ответить ребенок
    def __init__(self, total):
        # Вызываем конструктор родителя
        super().__init__()
        # x - первый множитель
        self.x = 0
        # y - второй множитель
        self.y = 0
        # score - число ответов ребеонка
        self.score = 0
        # total - количество правильных ответов, на которые должен ответить ребенок
        self.total = total

    # Метод повторной генерации множителей
    def update(self):
        # x - случайное целое число от 1 до 9
        self.x = random.randint(1, 9)
        # y - случайное целое число от 1 до 9
        self.y = random.randint(1, 9)
        # Отображаем надпись на формочке, типа x x y =
        self.label.setText(f"{self.x} x {self.y} = ")

    # обработчик нажатия кнопки
    def check(self):
        # z - ответ, введенныйв поле
        z = int(self.result.toPlainText().strip())

        # Если ответ правильный
        if z == self.x * self.y:
            # увеличиваем счетчик правильных ответов
            self.score += 1

            # Играем победную мелодию
            playsound("ok.wav", False)
            # Если можно продолжать игру дальше
            if self.score < self.total:
                # Диалоговое окошко показывает, что ответ правильный
                msg = QMessageBox()
                # Значок информационного окошка
                msg.setIcon(QMessageBox.Information)
                # Хвалим игррока=)
                msg.setText("Ты молодец!")
                # Хвалим еще сильнее!
                msg.setWindowTitle("Отлично!")
                # Диалоговое окошко имеет только кнопку OK
                msg.setStandardButtons(QMessageBox.Ok)
                # Показываем диалоговое окошко
                msg.exec_()

                # Очищаем поле ввода
                self.result.setText("")
                # Ставим курсов в поле ввода
                self.result.setFocus()

                # Генерируем следующий вопрос
                self.update()
            # Если конец игры (кончились попытки)
            else:
                # Диалоговое окошко сообщает о конце игры
                msg = QMessageBox()
                # Значок окошка с вопросом
                msg.setIcon(QMessageBox.Question)
                # Спрашиваем, будет ли игрок продолжать?
                msg.setText("Еще одну битву?")
                # Заголовок окошка
                msg.setWindowTitle("Конец битвы!")
                # Добавляем кнопки Ok и Cancel диалоговому окошку
                msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
                # Отображаем окошко и сохраняем ответ пользователя
                res = msg.exec_()

                # Если игрок нажал Ok - обнуляем счетчик попыток и начинаем заново
                if res == QMessageBox.Ok:
                    self.score = 0

                    self.result.setText("")
                    self.result.setFocus()

                    self.update()
                else:
                    # Если игрок нажал Cancel - завершаем программу
                    sys.exit(0)
        # Если ответ неправильный
        else:
            # Играем мелодию проигрыша
            playsound("error.wav", False)
            # Создаем окно ошибки
            m = QErrorMessage()
            # Текcт ошибки
            m.showMessage("Неправильно((( Давай еще разок")
            # Показываем ошибку
            m.exec_()

Из-за обилия комментариев код кажется длинным, но на самом деле все довольно просто. 
Обрабатываются 3 ситуации:
  1. Игрок ответил правильно;
  2. Игрок ответил неправильно;
  3. Конец игры.
Для нового вопроса есть метод update, который генерирует случайные множители.
Для обработки ответа есть метод check, который проверяет ответ и реагирует в зависимости от результата.
Осталось совсем немного: запустить приложение
# Объект Qt приложения
application = QApplication([])
# Главное окно
window = Window()
# Форма в главном окне
form = BatTestForm(10)
# Отображаем форму в окне
form.setupUi(window)
# Вызываем метод BatTestForm.update - генерируем первый вопрос
form.update()
# Связываем нажатие на кнопку и метод BatTestForm.check
form.refresh.clicked.connect(form.check)
# Показываем главное окно
window.show()

# Играем победную музыку
playsound("ok.wav", False)

# Запускаем приложение и ждем закрытия
application.exec_()
Вот и все, приложение можно запускать и звать игрока на битву с числами. Бэтмен ему поможет=) Итоговая структура проекта содержит код, формочку, 2 иконки и 2 аудио файла
battest
├── batman.png
├── error.wav
├── main.py
├── main.ui
├── ok.wav
└── refresh.png
И вот так эта красота выглядит в работающем виде:


Заключение
Итак, с помощью Qt и Python мы немного разнообразили довольно скучный учебный процесс и внесли в него немного веселья. Исходный код проекта тут, можете менять его на свое усмотрение, я думаю, другие супергерои тоже не откажут в помощи вашему ребенку.
Хотя, не ограничивайтесь героями, я думаю такие злодеи как Танос - тоже смогут внушить школьнику любовь к математике!
До новых встреч!







Комментариев нет:

Отправить комментарий