Donkey.bas in Python in 170 lines

Introduction

To be honest, I've wanted to write some kind of game for a long time and I even had experience writing them in this language, but they were all console games 🙁

But a few days ago I had a great idea: to write a remake of some old game in a minimum number of lines of code and using only standard Python libraries, namely: Tkinter, Time, Random And Winsound.

Yes, yes, none. Pygame'a. Ever since my school Computer Science lessons I didn't like easy ways to write programs in Pascal 🙂

I chose a toy for writing Donkeywritten back in 1981 for IBM PC DOS Bill Gates himself.

Something like this)

How was everything written?

First, I create a window for the game. Here I set the icon and title of the window, and also calculate the size and position so that the window appears in the center of the screen.

I fix the window size so that it cannot be changed, preparing the environment for further game development:

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)

I add a background image with text labels. The screen displays a message that you need to press a key to exit Escand also a donkey account is kept, which is updated using a special function.

Also, I upload an image showing when the donkey wins, and the same is done with the car:

# 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')

I create an image of a car and add it to the window. Then I define a function that moves the car when keys are pressed. Right And Leftand I set the game restart function:

# 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()

This code snippet loads an image of a donkey into the program using the Tkinter library, I place it on the screen and set some display options for it.

The code also contains functions for restarting the game (there are 3 of them in total) and hiding the driver's loss mark:

# 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)

Then I wrote a function that checks for a collision between two objects: a car and a donkey.

The function uses the winfo_rootx() and winfo_rooty() methods to get the coordinates of objects on the screen. It then compares these coordinates to determine whether a collision has occurred:

# 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)

This describes the function that makes the donkey move.

I check if a move is already in progress, and if not, I set the is_moving flag to True. Then I increment the coordinate. Y donkey at 50 and check its collision with the car:

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

In this code snippet I create an object that will represent the road:

# Create one Label for the road
road_label = tk.Label(window)

road_label.place(x=308, y=5)

Then I define a function that is responsible for animating the road markings. The function changes the image of the road_label using different image files and updates it every 10 milliseconds:

# 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)

Final code:

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()

If you remove all spaces and comments, you get 172 lines.

Screenshot of the running game:

DonkeyPy 1.0

DonkeyPy 1.0

Conclusion

I have an idea for the future development of the project:

Well, I think it's worth finishing this article here. Poke around the game's source code or download the already assembled version you can on GitHub.

Yura_FX was with you. Thank you for reading this article to the end. Don't forget to share your opinion in the comments 🙂

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *