Изображение гика

Блог питониста

Пишем игру "алфавит", используя питон, tkinter

1 апреля 2018 г.

Как-то раз один друг попросил меня сделать версию игры "Алфавит", которую можно было бы запускать без доступа к интернету. Суть игры заключается в том, что вам с определенным интервалом показывается две буквы, верхнюю букву нужно произнести в слух, а нижняя буква означает то, какую руку или руки вам нужно в этот момент поднять. Допустим верхняя буква - "А", нижняя - "П", значит называем букву "А", поднимая вверх правую руку. Буквы каждый раз меняются. Более подробно об игре можно прочитать тут (осторожно, концентрация сомнительной информации большая, берегите свой мозг).  

Мне задача показалась интересной с чисто технической стороны, я никоим образом не интересуюсь НЛП и прочим. Я вообще не уверен, что эта игра способна как-то повлиять на работу мозга, а тем более ввести в "состояние сверхвысокой продуктивности", лол. Просто интересная задачка, потому что раньше я почти не делал ничего такого, связанного с GUI.

Я решил делать игру на питоне 3, используя gui библиотеку tkinter.

Вообще, по идее, tkinter включен в стандартную библиотеку, но возможно вам понадобится ее установить, почему так, честно, не знаю, но мне пришлось:

sudo apt-get install python3-tk

Пожалуй, начнем с меню настроек игры. Это первый экран, который увидет пользователь. Там будут следующие настройки: 

  • язык (русский, английский)
  • отображать ли буквы в случайном порядке или по алфавиту
  • менять ли расположение букв на экране или показывать всегда в одной точке
  • менять ли размер шрифта
  • менять ли цвет букв
  • пауза между показами (секунды)

Из библиотеки tkinter нам понадобится несколько элементов:

  • Frame - для задания расположения и отступов
  • Label - для названия поля конкретной настройки
  • StringVar - для строковой переменной
  • Combobox - для select'a  для выбора из нескольких вариантов
  • IntVar - для переменной типа int
  • Checkbutton - для выбора из двух вариантов
  • Button - для кнопки перехода на следующий экран (экран игры)
  1 import tkinter as tk
  2 from tkinter import ttk
  3 
  4 
  5 class AlphabetSettings():
  6 
  7     def __init__(self, parent):
  8         # заголовок окна
  9         self.root = parent
 10         self.root.title("Алфавит. Настройки")
 11         # главный фрейм, задаем отступы
 12         main_frame = tk.Frame(parent)
 13         main_frame.grid(padx=50, pady=10)
 14         # настройка языка
 15         lang_setting_frame = tk.Frame(main_frame)
 16         lang_setting_frame.grid(pady=1)
 17         # тест настройки языка
 18         label = tk.Label(lang_setting_frame, text='Язык алфавита')
 19         label.grid(column=0, row=0, sticky=tk.E)
 20         # стринговая переменная язык
 21         language = tk.StringVar()
 22         # select - элемент для выбора языка
 23         language_box = ttk.Combobox(
 24             lang_setting_frame, textvariable=language)
 25         language_box['values'] = ('русский', 'английский')
 26         language_box.current(0)
 27         language_box.grid(column=1, row=0)
 28 
 29         settings_frame = tk.Frame(main_frame)
 30         settings_frame.grid(padx=50)
 31         # int переменная для настройки отображения
 32         # букв в случайном порядке
 33         random_order = tk.IntVar()
 34         random_order_btn = tk.Checkbutton(
 35             settings_frame,
 36             text='Отображать буквы в случайном порядке',
 37             variable=random_order,
 38             onvalue=1,
 39             offvalue=0)
 40         random_order_btn.grid(column=0, row=1, sticky=tk.W)
 41         # int переменная для настройки
 42         # менять ли расположение на экране
 43         change_pos = tk.IntVar()
 44         change_pos_btn = tk.Checkbutton(
 45             settings_frame,
 46             text='Менять расположение на экране',
 47             variable=change_pos,
 48             onvalue=1, offvalue=0)
 49         change_pos_btn.grid(column=0, row=2, sticky=tk.W)
 50         # int переменная для настройки
 51         # менять ли размер шрифта
 52         change_font_size = tk.IntVar()
 53         change_font_size_btn = tk.Checkbutton(
 54             settings_frame, text='Менять размер шрифта',
 55             variable=change_font_size,
 56             onvalue=1, offvalue=0)
 57         change_font_size_btn.grid(column=0, row=3, sticky=tk.W)
 58         # int переменная для настройки
 59         # менять ли цвет
 60         change_color = tk.IntVar()
 61         change_color_btn = tk.Checkbutton(
 62             settings_frame, text='Менять цвет',
 63             variable=change_color,
 64             onvalue=1, offvalue=0)
 65         change_color_btn.grid(column=0, row=4, sticky=tk.W)
 66         # фрейм для паузы
 67         pause_frame = tk.Frame(main_frame)
 68         pause_frame.grid(pady=10)
 69 
 70         label = ttk.Label(pause_frame, text='Пауза между показами')
 71         label.grid(column=0, row=5, sticky=tk.E)
 72 
 73         pause = tk.StringVar()
 74         pause_box = ttk.Combobox(pause_frame, textvariable=pause)
 75         pause_box['values'] = (0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.3,
 76                                1.5, 1.7, 2, 3, 4,
 77                                5, 7, 10)
 78         pause_box.current(0)
 79         pause_box.grid(column=1, row=5)
 80 
 81         game_btn_frame = tk.Frame(main_frame)
 82         game_btn_frame.grid(pady=10)
 83         # Создаем кнопку "Играть", переменную pause
 84         # переводим в миллисекунды
 85         btn = ttk.Button(
 86             game_btn_frame,
 87             text="Играть",
 88             command=lambda: self.open_frame(
 89                 language.get(),
 90                 random_order.get(),
 91                 change_pos.get(),
 92                 change_font_size.get(),
 93                 change_color.get(),
 94                 int(float(pause.get()) * 1000))
 95             )
 96         btn.grid()
 97 
 98     def open_frame(self, lang, random_order,
 99                    change_pos, change_font_size,
100                    change_color, pause):
101         print(lang, random_order, change_pos,
102               change_font_size, change_color, pause)
103 
104 
105 if __name__ == "__main__":
106     root = tk.Tk()
107     root.geometry("500x250")
108     app = AlphabetSettings(root)
109     root.mainloop()

На строке 88 я привязываю вызов функции open_frame к клику по кнопке "Играть", используя lambda. Функция open_frame пока просто принтит настройки в консоль.

После запуска получите примерно такую картинку, по нажатию на кнопку "Играть" вы увидите в консоли все переданные переменные:

alphabet.png

Выглядит, конечно, страшновато, но для моей задачи хватит. Теперь давайте сделаем так, чтобы по клику на кнопку "Играть" открывалось бы новое окно, а окно настроек исчезало бы. Нужно изменить функцию open_frame, чтобы она открывала новое окно, добавить функции hide и show для того, чтобы прятать и показывать снова окно настроек соответственно, также надо добавить импорт модуля sys:

 1 import tkinter as tk
 2 from tkinter import ttk
 3 import sys
 4 ...
 5     # внутри класса AlphabetSettings:
 6     def open_frame(self, lang, random_order,
 7                    change_pos, change_font_size,
 8                    change_color, pause):
 9         self.hide()
10         AlphabetGame(self, lang, random_order, change_pos,
11                      change_font_size, change_color, pause)
12 
13     # добавляем в конец класса две новые функции:
14     def hide(self):
15         self.root.withdraw()
16 
17     def show(self):
18         self.root.update()
19         self.root.deiconify()

Также, надо добавить класс AlphabetGame, который будет отвечать за саму игру. В нем будет описана вся логика, в нем я буду использовать класс Canvas, потому что нужно будет отображать буквы:

 1 class AlphabetGame(tk.Toplevel):
 2 
 3     def __init__(self, original_frame, lang, random_order, change_pos,
 4                  change_font_size, change_color, pause):
 5         tk.Toplevel.__init__(self)
 6         self.original_frame = original_frame
 7         self.lang = lang
 8         self.pause = pause
 9         self.protocol("WM_DELETE_WINDOW", sys.exit)
10         # задаем размеры окна игры
11         w = 800
12         h = 650
13 
14         # получаем размеры экрана
15         ws = root.winfo_screenwidth()
16         hs = root.winfo_screenheight()
17 
18         # вычисляем координаты расположения окна
19         x = (ws / 2) - (w / 2)
20         y = (hs / 2) - (h / 2)
21 
22         # задаем расположение, навзание, цвет
23         self.geometry('%dx%d+%d+%d' % (w, h, x, y))
24         self.title("Алфавит")
25         self.configure(background='white')
26 
27         self.canvas = tk.Canvas(self)
28         self.canvas.grid()
29         self.canvas.configure(background='white', width=800, height=600)
30 
31         btn = ttk.Button(
32             self, text="Назад", command=self.close_and_back_to_settings)
33         btn.grid()
34 
35     def close_and_back_to_settings(self):
36         """Функция для закрытия окна игры и возврата к настройкам"""
37         self.destroy()
38         self.original_frame.show()

Теперь должно получится что-то такое:

alpha.gif

Теперь надо добавить логику игры. Импортируем randrange и зададимся настройками игры (цвета, буквы, шрифты):

 1 import tkinter as tk
 2 from tkinter import ttk
 3 import sys
 4 from random import randrange
 5 
 6 FONT_SIZE = (24, 26, 28, 30, 32, 36, 40, 42, 44, 50, 56, 65, 72, 76, 78)
 7 DEFAULT_SIZE = 46
 8 NUMBER_OF_FONTS = len(FONT_SIZE)
 9 FONT = ("Verdana", DEFAULT_SIZE)
10 LETTERS_BOTTOM_RUS = ('О', 'Л', 'П')
11 LETTERS_BOTTOM_ENG = ('R', 'L', 'B')
12 COLORS = ("#563093", "#911414", "#cec379", "#B76EFF", "#FF90FF",
13           "#9CFF9C", "#FFB76E", "#A3A375", "#9F9FFF", "#FF66CC")
14 NUMBER_OF_COLORS = len(COLORS)

Также, надо поменять класс AlphabetGame, добавив в него обработку хотя бы первого варианта, когда все переменные change_font_sizerandom_orderchange_colorchange_pos не выбраны (равны 0):

 1 class AlphabetGame(tk.Toplevel):
 2 
 3     def __init__(self, original_frame, lang, random_order, change_pos,
 4                  change_font_size, change_color, pause):
 5         tk.Toplevel.__init__(self)
 6         self.original_frame = original_frame
 7         self.lang = lang
 8         self.pause = pause
 9         self.protocol("WM_DELETE_WINDOW", sys.exit)
10         # задаем размеры окна игры
11         w = 800
12         h = 650
13 
14         # получаем размеры экрана
15         ws = root.winfo_screenwidth()
16         hs = root.winfo_screenheight()
17 
18         # вычисляем координаты расположения окна
19         x = (ws / 2) - (w / 2)
20         y = (hs / 2) - (h / 2)
21 
22         # задаем расположение, навзание, цвет
23         self.geometry('%dx%d+%d+%d' % (w, h, x, y))
24         self.title("Алфавит")
25         self.configure(background='white')
26 
27         self.canvas = tk.Canvas(self)
28         self.canvas.grid()
29         self.canvas.configure(background='white', width=800, height=600)
30 
31         btn = ttk.Button(
32             self, text="Назад", command=self.close_and_back_to_settings)
33         btn.grid()
34 
35         # случайный инт от 0 до 2 для выбора нижней буквы
36         random_int = randrange(3)
37         # задаем начальные буквы (верхнюю и нижнюю)
38         if lang == 'русский':
39             initial_letter_top = chr(randrange(1040, 1072))
40             initial_letter_bottom = LETTERS_BOTTOM_RUS[random_int]
41         else:
42             initial_letter_top = chr(randrange(65, 91))
43             initial_letter_bottom = LETTERS_BOTTOM_ENG[random_int]
44 
45         if all((change_font_size == 0,
46                 random_order == 0,
47                 change_color == 0,
48                 change_pos == 0)):
49             # id верхней буквы
50             self.top_letter_id = self.canvas.create_text(
51                 400, 200, anchor=tk.W, font=(
52                     FONT[0], DEFAULT_SIZE), text=initial_letter_top)
53             # id нижней буквы
54             self.bottom_letter_id = self.canvas.create_text(
55                 400, 260, anchor=tk.W, font=(
56                     FONT[0], DEFAULT_SIZE), text=initial_letter_bottom)
57             # выполняется после оторбажения двух букв:
58             self.canvas.after(
59                 self.pause, lambda: self.change_letters())
60 
61     def change_ord(self, cur_ord=None):
62         if not cur_ord:
63             cur_letter_ord = ord(
64                 self.canvas.itemcget(self.top_letter_id, "text"))
65         else:
66             cur_letter_ord = cur_ord
67         if self.lang == "русский":
68             # буква Я, начинаем сначала
69             if cur_letter_ord == 1071:
70                 cur_letter_ord = 1039
71             # буква Е, выбираем Ё
72             elif cur_letter_ord == 1045:
73                 cur_letter_ord = 1024
74             # после Ё возврат к Ж
75             elif cur_letter_ord == 1025:
76                 cur_letter_ord = 1045
77             next_ord = cur_letter_ord + 1
78         else:
79             if cur_letter_ord == 90:
80                 cur_letter_ord = 64
81         next_ord = cur_letter_ord + 1
82         return next_ord
83 
84     def get_next_bottom_letter(self):
85         random = randrange(3)
86         if self.lang == 'русский':
87             next_bottom_letter = LETTERS_BOTTOM_RUS[random]
88         else:
89             next_bottom_letter = LETTERS_BOTTOM_ENG[random]
90         return next_bottom_letter
91 
92     def change_letters(self):
93         next_ord = self.change_ord()
94         self.canvas.itemconfigure(self.top_letter_id, text=chr(next_ord))
95 
96         self.canvas.itemconfigure(
97             self.bottom_letter_id, text=self.get_next_bottom_letter())
98 
99         self.after(self.pause, lambda: self.change_letters())

Смысл в том, что после каждого показа двух букв я меняю буквы функцией change_letters, с задержкой, определяемой переменной self.pause. На данном этапе вы можете сыграть в игру только в одном случае (нулевых настроек). Теперь осталось расписать все оставшиеся варианты. Код получился довольно длинным, поэтому лучше я просто дам ссылку на github.

Заключение

Сделана простая игра, зато с GUI.

Видео игры:

Метки

python
Если вам понравился пост, можете поделиться им в соцсетях: