Пишем игру "алфавит", используя питон, tkinter
Как-то раз один друг попросил меня сделать версию игры "Алфавит", которую можно было бы запускать без доступа к интернету. Суть игры заключается в том, что вам с определенным интервалом показывается две буквы, верхнюю букву нужно произнести в слух, а нижняя буква означает то, какую руку или руки вам нужно в этот момент поднять. Допустим верхняя буква - "А", нижняя - "П", значит называем букву "А", поднимая вверх правую руку. Буквы каждый раз меняются. Более подробно об игре можно прочитать тут (осторожно, концентрация сомнительной информации большая, берегите свой мозг).
Мне задача показалась интересной с чисто технической стороны, я никоим образом не интересуюсь НЛП и прочим. Я вообще не уверен, что эта игра способна как-то повлиять на работу мозга, а тем более ввести в "состояние сверхвысокой продуктивности", лол. Просто интересная задачка, потому что раньше я почти не делал ничего такого, связанного с 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 пока просто принтит настройки в консоль.
После запуска получите примерно такую картинку, по нажатию на кнопку "Играть" вы увидите в консоли все переданные переменные:
Выглядит, конечно, страшновато, но для моей задачи хватит. Теперь давайте сделаем так, чтобы по клику на кнопку "Играть" открывалось бы новое окно, а окно настроек исчезало бы. Нужно изменить функцию 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()
Теперь должно получится что-то такое:
Теперь надо добавить логику игры. Импортируем 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_size
, random_order
, change_color
, change_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.
Видео игры: