Вступление
Честно говоря, я давненько хотел написать какую-то игрушку и опыт их написания на данном языке даже был, но все они были консольные 🙁
Но несколько дней назад мне пришла классная идея: написать ремейк какой-нибудь старой игрушки в минимальное количество строк кода и используя только стандартные библиотеки Python, а именно: Tkinter, Time, Random и Winsound.
Да-да, никакого Pygame’а. Я ещё со школьных уроков Информатики не любил лёгких путей при написании программ на Паскале 🙂
Для написания я выбрал игрушку Donkey, написанную в далёком 1981 году для IBM PC DOS самим Биллом Гейтсом.
Как-то так)
Как всё писалось?
Для начала я создаю окно для игры. Здесь я устанавливаю иконку и заголовок окна, а также рассчитываю размеры и местоположение, чтобы окно появилось в центре экрана.
Я фиксирую размер окна, чтобы его нельзя было изменить, подготавливая среду для дальнейшей разработки игры:
import tkinter as tk import time from random import randint import winsound # Create the main application window window = tk.Tk() # Setting the window icon window.iconbitmap('resources\icon.ico') # Setting the window title window.title('DonkeyPy 1.0') # Get the width and height of the screen screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() # Set the width and height of the window window_width = 718 window_height = 418 # Calculate coordinates for placing the window in the centre of the screen x = (screen_width // 2) - (window_width // 2) y = (screen_height // 2) - (window_height // 2) # Apply window size and position window.geometry(f'{window_width}x{window_height}+{x}+{y}') # Prohibit window resizing window.resizable(False, False)
Я добавляю фоновое изображение с текстовыми метками. На экране отображается сообщение о том, что для выхода нужно нажать клавишу Esc, а также ведётся счёт для осла, который обновляется с помощью специальной функции.
Кроме того, я загружаю изображение, показывающее, когда осёл побеждает, аналогичное делается и с машиной:
# Set the background of the window window.image = tk.PhotoImage(file='resources\zf.png') bg = tk.Label(window, image=window.image) bg.grid(row=0, column=0) bg.config(bg='#555555') # Escape key label esc_lbl = tk.Label(window, text='Press Esc to exit', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) esc_lbl.place(x=500, y=345) # Window closing function def exit(event): if event.keysym == 'Escape': window.destroy() window.bind('<KeyPress-Escape>', exit) # Donkey labels donkey_lbl = tk.Label(window, text='Donkey', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_lbl.place(x=26, y=40) donkey_count = tk.Label(window, text=0, bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_count.place(x=26, y=90) donkey_loses = tk.Label(window, text='Donkey loses!', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_loses.place(x=1000, y=1000) # Donkey scoring function def donkey_points_count(): donkey_count['text'] = int(donkey_count['text']) + 1 donkey_wins = tk.PhotoImage(file='resources\donkey_wins.png') donkey_wins_label = tk.Label(window) donkey_wins_label.image = donkey_wins donkey_wins_label['image'] = donkey_wins_label.image donkey_wins_label.place(x=1000, y=1000) donkey_wins_label.config(bg='#555555') # Car labels car_lbl = tk.Label(window, text='Driver', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) car_lbl.place(x=500, y=40) car_count = tk.Label(window, text=0, bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) car_count.place(x=500, y=90) driver_loses = tk.Label(window, text='Driver loses!', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) driver_loses.place(x=1000, y=1000) # Driver scoring function def driver_points_count(): car_count['text'] = int(car_count['text']) + 1 driver_wins = tk.PhotoImage(file='resources\driver_wins.png') driver_wins_label = tk.Label(window) driver_wins_label.image = driver_wins driver_wins_label['image'] = driver_wins_label.image driver_wins_label.place(x=1000, y=1000) driver_wins_label.config(bg='#555555')
Я создаю изображение машины и добавляю его на окно. Затем я определяю функцию, которая перемещает машину при нажатии клавиш Right и Left, и задаю функцию перезапуска игры:
# Uploading car image car = tk.PhotoImage(file='resources\car.png') car_label = tk.Label(window) car_label.image = car car_label['image'] = car_label.image car_y = 280 car_label.place(x=250, y=car_y) car_label.config(bg='#555555') car_y_initial = 280 # Car move function def move_car(event): if car_y == 100: return else: if event.keysym == 'Right': car_label.place(x=380) elif event.keysym == 'Left': car_label.place(x=250) winsound.PlaySound('sounds\move_car.wav', 1) window.bind('<KeyPress-Right>', move_car) window.bind('<KeyPress-Left>', move_car) # Game restart function def restart_game(): global car_y, car_y_initial car_y = car_y_initial car_label.place(x=250) donkey_loses.place(x=1000, y=1000) driver_wins_label.place(x=1000, y=1000) if car_count['text'] == 10: car_count['text'] = int(car_count['text']) * 0 donkey_count['text'] = int(donkey_count['text']) * 0 change_road()
Этот фрагмент кода загружает изображение осла в программу с помощью библиотеки Tkinter, я размещаю его на экране и устанавливаю некоторые параметры его отображения.
Также в коде есть функции для перезапуска игры (их суммарно 3 штуки) и скрытия метки проигрыша водителя:
# Uploading donkey image donkey = tk.PhotoImage(file='resources\donkey.png') donkey_label = tk.Label(window) donkey_label.image = donkey donkey_label['image'] = donkey_label.image donkey_x = 365 donkey_y = -40 donkey_label.place(x=donkey_x, y=donkey_y) donkey_label.config(bg='#555555') donkey_y_initial = -1340 # Game restart function in case of a donkey win def restart_game_2(): global car_y, car_y_initial, donkey_y, donkey_y_initial car_y = car_y_initial car_label.place(x=250, y=car_y) donkey_y = donkey_y_initial # Label hiding function def driver_loses_f(): driver_loses.place(x=1000, y=1000)
Затем я написал функцию, которая проверяет столкновение между двумя объектами: машиной и ослом.
Функция использует методы winfo_rootx() и winfo_rooty(), чтобы получить координаты объектов на экране. Затем она сравнивает эти координаты, чтобы определить, произошло ли столкновение:
# Function for checking image collision def check_collision(): car_x = car_label.winfo_rootx() car_y = car_label.winfo_rooty() donkey_x = donkey_label.winfo_rootx() donkey_y = donkey_label.winfo_rooty() # Condition, if the donkey wins if car_x >= donkey_x and car_x <= donkey_x + donkey.width() and \ car_y >= donkey_y and car_y <= donkey_y + donkey.height(): donkey_points_count() winsound.PlaySound('sounds\image_collision.wav', 1) if donkey_count['text'] < 10: driver_loses.place(x=26, y=140) restart_game_2() window.after(2500, driver_loses_f) else: donkey_wins_label.place(x=498, y=225) restart_game_3() window.after(2500, donkey_wins_f)
Здесь описывается функция, которая приводит осла в движение.
Я проверяю, не выполняется ли уже перемещение, и если нет — устанавливаю флаг is_moving в значение True. Затем я увеличиваю координату Y осла на 50 и проверяю его столкновение с машиной:
is_moving = False # Donkey move function def move_donkey(): global is_moving, donkey_y, car_y # If the function is already in progress, exit if is_moving: return # Set the flag that the function is running is_moving = True donkey_y += 50 donkey_label.place(y=donkey_y) # Checking for collision window.after(1000, check_collision) # Condition, if the donkey reaches a certain y-coordinate if donkey_y == 360: donkey_x = 365 if randint(1, 2) == 1 else 230 donkey_y = -40 donkey_label.place(x=donkey_x, y=donkey_y) car_y -= 20 car_label.place(y=car_y) # Condition, if the driver wins if car_y == 100: driver_points_count() donkey_x = 1000 donkey_label.place(x=donkey_x) if car_count['text'] < 10: donkey_loses.place(x=500, y=140) window.after(2500, move_donkey) window.after(2500, restart_game) else: driver_wins_label.place(x=498, y=225) window.after(2500, move_donkey) window.after(2500, restart_game) else: window.after(110, move_donkey) else: window.after(110, move_donkey) # Reset flag on function termination is_moving = False
В этом фрагменте кода я создаю объект, который будет обозначать дорогу:
# Create one Label for the road road_label = tk.Label(window) road_label.place(x=308, y=5)
Затем я определяю функцию, которая отвечает за анимацию дорожной разметки. Функция изменяет изображение метки road_label, используя разные файлы изображений, и обновляет его каждые 10 миллисекунд:
# The function responsible for animating road markings def change_road(): if car_y == 100: return else: current_time = (int(time.time() * 20) % 3) + 1 road = tk.PhotoImage(file='resources\doroga_{}.png'.format(current_time)) road_label.image = road road_label['image'] = road_label.image road_label.config(bg='#555555') window.after(10, change_road)
Итоговый код:
import tkinter as tk import time from random import randint import winsound window = tk.Tk() window.iconbitmap('resources\icon.ico') window.title('DonkeyPy 1.0') screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() window_width = 718 window_height = 418 x = (screen_width // 2) - (window_width // 2) y = (screen_height // 2) - (window_height // 2) window.geometry(f'{window_width}x{window_height}+{x}+{y}') window.resizable(False, False) window.image = tk.PhotoImage(file='resources\zf.png') bg = tk.Label(window, image=window.image) bg.grid(row=0, column=0) bg.config(bg='#555555') esc_lbl = tk.Label(window, text='Press Esc to exit', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) esc_lbl.place(x=500, y=345) def exit(event): if event.keysym == 'Escape': window.destroy() window.bind('<KeyPress-Escape>', exit) donkey_lbl = tk.Label(window, text='Donkey', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_lbl.place(x=26, y=40) donkey_count = tk.Label(window, text=0, bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_count.place(x=26, y=90) donkey_loses = tk.Label(window, text='Donkey loses!', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) donkey_loses.place(x=1000, y=1000) def donkey_points_count(): donkey_count['text'] = int(donkey_count['text']) + 1 donkey_wins = tk.PhotoImage(file='resources\donkey_wins.png') donkey_wins_label = tk.Label(window) donkey_wins_label.image = donkey_wins donkey_wins_label['image'] = donkey_wins_label.image donkey_wins_label.place(x=1000, y=1000) donkey_wins_label.config(bg='#555555') car_lbl = tk.Label(window, text='Driver', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) car_lbl.place(x=500, y=40) car_count = tk.Label(window, text=0, bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) car_count.place(x=500, y=90) driver_loses = tk.Label(window, text='Driver loses!', bg='#555555', fg='#C0C0C0', font=('Comic Sans MS', 16, 'bold')) driver_loses.place(x=1000, y=1000) def driver_points_count(): car_count['text'] = int(car_count['text']) + 1 driver_wins = tk.PhotoImage(file='resources\driver_wins.png') driver_wins_label = tk.Label(window) driver_wins_label.image = driver_wins driver_wins_label['image'] = driver_wins_label.image driver_wins_label.place(x=1000, y=1000) driver_wins_label.config(bg='#555555') car = tk.PhotoImage(file='resources\car.png') car_label = tk.Label(window) car_label.image = car car_label['image'] = car_label.image car_y = 280 car_label.place(x=250, y=car_y) car_label.config(bg='#555555') car_y_initial = 280 def move_car(event): if car_y == 100: return else: if event.keysym == 'Right': car_label.place(x=380) elif event.keysym == 'Left': car_label.place(x=250) winsound.PlaySound('sounds\move_car.wav', 1) window.bind('<KeyPress-Right>', move_car) window.bind('<KeyPress-Left>', move_car) def restart_game(): global car_y, car_y_initial car_y = car_y_initial car_label.place(x=250) donkey_loses.place(x=1000, y=1000) driver_wins_label.place(x=1000, y=1000) if car_count['text'] == 10: car_count['text'] = int(car_count['text']) * 0 donkey_count['text'] = int(donkey_count['text']) * 0 change_road() donkey = tk.PhotoImage(file='resources\donkey.png') donkey_label = tk.Label(window) donkey_label.image = donkey donkey_label['image'] = donkey_label.image donkey_x = 365 donkey_y = -40 donkey_label.place(x=donkey_x, y=donkey_y) donkey_label.config(bg='#555555') donkey_y_initial = -1340 def restart_game_2(): global car_y, car_y_initial, donkey_y, donkey_y_initial car_y = car_y_initial car_label.place(x=250, y=car_y) donkey_y = donkey_y_initial def driver_loses_f(): driver_loses.place(x=1000, y=1000) def restart_game_3(): global car_y, car_y_initial, donkey_y, donkey_y_initial car_y = car_y_initial car_label.place(x=250, y=car_y) donkey_y = donkey_y_initial if donkey_count['text'] == 10: donkey_count['text'] = int(donkey_count['text']) * 0 car_count['text'] = int(car_count['text']) * 0 def donkey_wins_f(): donkey_wins_label.place(x=1000, y=1000) def check_collision(): car_x = car_label.winfo_rootx() car_y = car_label.winfo_rooty() donkey_x = donkey_label.winfo_rootx() donkey_y = donkey_label.winfo_rooty() if car_x >= donkey_x and car_x <= donkey_x + donkey.width() and \ car_y >= donkey_y and car_y <= donkey_y + donkey.height(): donkey_points_count() winsound.PlaySound('sounds\image_collision.wav', 1) if donkey_count['text'] < 10: driver_loses.place(x=26, y=140) restart_game_2() window.after(2500, driver_loses_f) else: donkey_wins_label.place(x=498, y=225) restart_game_3() window.after(2500, donkey_wins_f) is_moving = False def move_donkey(): global is_moving, donkey_y, car_y if is_moving: return is_moving = True donkey_y += 50 donkey_label.place(y=donkey_y) window.after(1000, check_collision) if donkey_y == 360: donkey_x = 365 if randint(1, 2) == 1 else 230 donkey_y = -40 donkey_label.place(x=donkey_x, y=donkey_y) car_y -= 20 car_label.place(y=car_y) if car_y == 100: driver_points_count() donkey_x = 1000 donkey_label.place(x=donkey_x) if car_count['text'] < 10: donkey_loses.place(x=500, y=140) window.after(2500, move_donkey) window.after(2500, restart_game) else: driver_wins_label.place(x=498, y=225) window.after(2500, move_donkey) window.after(2500, restart_game) else: window.after(110, move_donkey) else: window.after(110, move_donkey) is_moving = False road_label = tk.Label(window) road_label.place(x=308, y=5) def change_road(): if car_y == 100: return else: current_time = (int(time.time() * 20) % 3) + 1 road = tk.PhotoImage(file='resources\doroga_{}.png'.format(current_time)) road_label.image = road road_label['image'] = road_label.image road_label.config(bg='#555555') window.after(10, change_road) move_donkey() change_road() window.mainloop()
Если убрать все пробелы и комментарии, то получается 172 строки.
Скриншот работающей игры:
Заключение
У меня есть идея по развитию проекта на будущее:
-
Создание Android версии игры (надеюсь, руки когда-нибудь до этого дойдут).
Ну а на этом думаю, стоит закончить данную статью. Потыкать исходный код игры или скачать уже собранную версию вы сможете на GitHub.
С вами был Yura_FX. Спасибо, что дочитали данную статью до конца. Не забывайте делиться своим мнением в комментариях 🙂
ссылка на оригинал статьи https://habr.com/ru/articles/831198/
Добавить комментарий