Donkey.bas на Python в 170 строк

от автора

Вступление

Честно говоря, я давненько хотел написать какую-то игрушку и опыт их написания на данном языке даже был, но все они были консольные 🙁

Но несколько дней назад мне пришла классная идея: написать ремейк какой-нибудь старой игрушки в минимальное количество строк кода и используя только стандартные библиотеки 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 строки.

Скриншот работающей игры:

DonkeyPy 1.0

DonkeyPy 1.0

Заключение

У меня есть идея по развитию проекта на будущее:

  • Создание Android версии игры (надеюсь, руки когда-нибудь до этого дойдут).

Ну а на этом думаю, стоит закончить данную статью. Потыкать исходный код игры или скачать уже собранную версию вы сможете на GitHub.

С вами был Yura_FX. Спасибо, что дочитали данную статью до конца. Не забывайте делиться своим мнением в комментариях 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/831198/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *