Home > Blockchain >  Tkinter program slowing down after canvas.delete('all') and canvas.destroy()
Tkinter program slowing down after canvas.delete('all') and canvas.destroy()

Time:08-26

I am creating a simple pong game clone in tkinter and i want to create a game loop so that when the game is finished, it will restart.

import tkinter as tk
import random
import time
from time import sleep

# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')

# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h} {center_x} {center_y}')

def main():

    # canvas
    cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
    cv.delete('all')
    cv.configure(bg="#000000")

    global x, y, num_r, num_l, ball

    # initializing variables
    dirx = random.randint(0, 1)
    diry = random.randint(0, 1)
    x = 0
    y = 0
    num_l = 0
    num_r = 0
    if dirx == 0 and diry == 0:
        x = -10
        y = -10
    if dirx == 0 and diry == 1:
        x = -10
        y = 10
    if dirx == 1 and diry == 0:
        x = 10
        y = -10
    if dirx == 1 and diry == 1:
        x = 10
        y = 10
    #print(dirx, diry)


    # paddles
    paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
    paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')

    # middle line
    midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2 5, 85, outline='#000000', fill='#ffffff')
    midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2 5, 185, outline='#000000', fill='#ffffff')
    midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2 5, 285, outline='#000000', fill='#ffffff')
    midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2 5, 385, outline='#000000', fill='#ffffff')
    midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2 5, 485, outline='#000000', fill='#ffffff')
    midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2 5, 585, outline='#000000', fill='#ffffff')
    midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2 5, 685, outline='#000000', fill='#ffffff')

    # score trackers
    score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
    score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
    score_left.place(relx=0.43, rely=0.1)
    score_right.place(relx=0.55, rely=0.1)

    # ball
    ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10, outline='#000000', fill='#696969')

    # movement of the paddles
    def detectMoveKeys():

        # left paddle
        root.bind('w', leftUp)
        root.bind('s', leftDown)
        
        # right paddle
        root.bind('i', rightUp)
        root.bind('k', rightDown)

        root.after(5, detectMoveKeys)

    def leftUp(self):
        cv.move(paddle_l, 0, -10)
        #print('registered')
    def leftDown(self):
        cv.move(paddle_l, 0, 10)
        #print('registered')
    def rightUp(self):
        cv.move(paddle_r, 0, -10)
        #print('registered')
    def rightDown(self):
        cv.move(paddle_r, 0, 10)
        #print('registered')


    def ballMovement():
        global x, y 
        if cv.coords(ball)[1] 10 == canvas_h:
            y = y * (0-1)
            print('ball touched bottom', x, y)
        
        if cv.coords(ball)[1] == 0:
            y = y * (0-1)
            print('ball touched top', x, y)
        
        pos = cv.coords(ball)
        if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
            x = x * (0-1)
            print('ball touched left paddle', x, y)
        if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
            x = x * (0-1)
            print('ball touched right paddle', x, y)

        cv.move(ball, x, y)
        #print(cv.coords(ball)[0])
        cv.pack()

        root.after(35, ballMovement) 

    def endGame():
        if score_right['text'] == '10':
            score_right['text'] = '0'
            score_left['text'] = '0'
            r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
            r_won.place(relx=0.3, rely=0.5)
            cv.delete('all')
            score_right.config(text='')
            score_left.config(text='')
            cv.destroy()
            root.after(3000, main)
        root.after(10, endGame)

    def outOfBounds():
        global num_l, num_r, ball, dirx, diry, x, y
        
        # detects if the ball is out of bounds on the left side of the screen
        if cv.coords(ball)[0] == -30.0:
            print('right scored')
            num_r  = 1
            score_right['text'] = str(num_r)
            cv.pack()
            cv.coords(ball, 630, 350, 650, 370)
            #ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10, fill='#696969')
            dirx = random.randint(0, 1)
            diry = random.randint(0, 1)
            if dirx == 0 and diry == 0:
                x = -10
                y = -10
            if dirx == 0 and diry == 1:
                x = -10
                y = 10
            if dirx == 1 and diry == 0:
                x = 10
                y = -10
            if dirx == 1 and diry == 1:
                x = 10
                y = 10

        # detects if the ball is out of bounds on the right side of the screen
        if cv.coords(ball)[0] == 1310.0:
            print('left scored')
            num_l  = 1
            score_left['text'] = str(num_l)
            cv.pack()
        
            cv.delete(ball)
            ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10, fill='#696969')
            dirx = random.randint(0, 1)
            diry = random.randint(0, 1)
            if dirx == 0 and diry == 0:
                x = -10
                y = -10
            if dirx == 0 and diry == 1:
                x = -10
                y = 10
            if dirx == 1 and diry == 0:
                x = 10
                y = -10
            if dirx == 1 and diry == 1:
                x = 10
                y = 10

        root.after(10, outOfBounds)

    root.after(5, detectMoveKeys)
    root.after(35, ballMovement)
    root.after(10, outOfBounds)
    root.after(10, endGame)

main()

root.mainloop()

When I first start the program, everything runs fine until the first game loop is started. Then it starts lagging and eventually freezes.

Here are the error messages:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
    return self.func(*args)
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
    func(*args)
  File "/home/anton/Documents/pong/pong.py", line 140, in outOfBounds
    if cv.coords(ball)[0] == -30.0:
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
    self.tk.call((self._w, 'coords')   args))]
_tkinter.TclError: invalid command name ".!canvas"
Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
    return self.func(*args)
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
    func(*args)
  File "/home/anton/Documents/pong/pong.py", line 103, in ballMovement
    if cv.coords(ball)[1] 10 == canvas_h:
  File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
    self.tk.call((self._w, 'coords')   args))]
_tkinter.TclError: invalid command name ".!canvas"

What I have tried:

  1. Making sure all variables are set to their default values after the game start
  2. Putting everything in a tk.Frame widget and destroying that

If it helps I am running Fedora 36 Workstation.

Does anyone know what's the problem? Thanks in advance.

Edit:

I have implemented an 'end' flag so the loops stop, but the program starts lagging again after some restarts. I don't know how to fix this either.

...
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)

def main():
    #print('main called')

    global x, y, num_r, num_l, ball, end

    # canvas
    cv.configure(bg="black") 
    ...
    end = False
    ...
    def detectMoveKeys():
        ...
        if end == False:
            root.after(5, detectMoveKeys)
        ...
    def ballMovement():
        ...
        if end == False:
            root.after(35, ballMovement) 

    def outOfBounds():
        ...
        if end == False:
            root.after(10, outOfBounds)

    def endGame():
        global end, ball

        def r_won_destroy():
            r_won.destroy()
            cv.coords(ball, canvas_w/2-5, canvas_h/2-5, canvas_w/2 5, canvas_h/2 5)
            cv.delete('all')
            

        if score_right['text'] == '10':
            score_right['text'] = ''
            score_left['text'] = ''
            r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
            r_won.place(relx=0.3, rely=0.5)
            end = True
            #cv.itemconfig('all', fill='black')
            root.after(3000, main)
            root.after(2000, r_won_destroy)
        if end == False:
            root.after(10, endGame)

    if end == False:
        root.after(5, detectMoveKeys)
        root.after(35, ballMovement)
        root.after(10, outOfBounds)
        root.after(10, endGame)

main()

root.mainloop()

I am new to tkinter so sorry if this is an obvious mistake.

Can someone solve this, please?

CodePudding user response:

Part of the problem is in this block of code:

def endGame():
    if score_right['text'] == '10':
        ...
        root.after(3000, main)
    root.after(10, endGame)

If the if condition is true, you call main after 3 seconds. You also continue to call endGame 100 times a second whether it's true or false So, even when the game ends, you're continuing to call endGame 100 times a second.

When you call main from inside that if block, main also calls endGame, causing endGame to start a new loop of running 100 times a second. So after one restart you are calling endGame 100 times a second twice. The third restart you're calling endGame 100 times a second three times. And so on. Eventually you'll end up calling endGame thousands of times a second, far more often than it can keep up with.

The same thing is happening for detectMoveKeys, ballMovement, and outOfBounds. You never stop the endless loop of those functions when the game ends, and start a new endless loop every time a game starts.

CodePudding user response:

I think I did it. I may have just been calling the outOfBounds, ballMovement, detectMoveKeys and endGame too many times a second. (100 times) I have reduced that to 5 times a second and the lagging seems to be gone. I've tested 100 restarts and it was still responsive. But I feel like after an undefined amount of restarts, it's going to be lagging again, because the function calls may still be stacking on top of one another. Please tell me if this is the case. Thanks a lot for the help.

Here is the full code for you to test it out:

import tkinter as tk
import random

# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')

# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h} {center_x} {center_y}')

# canvas
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
cv.configure(bg="black")

# paddles
paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')

# middle line
midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2 5, 85, outline='#000000', fill='#ffffff')
midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2 5, 185, outline='#000000', fill='#ffffff')
midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2 5, 285, outline='#000000', fill='#ffffff')
midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2 5, 385, outline='#000000', fill='#ffffff')
midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2 5, 485, outline='#000000', fill='#ffffff')
midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2 5, 585, outline='#000000', fill='#ffffff')
midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2 5, 685, outline='#000000', fill='#ffffff')

# score trackers
score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_left.place(relx=0.43, rely=0.1)
score_right.place(relx=0.55, rely=0.1)

# ball
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10, outline='#000000', fill='#696969')

cv.pack()

# movement of the paddles
def detectMoveKeys():

    # left paddle
    root.bind('w', leftUp)
    root.bind('s', leftDown)
    
    # right paddle
    root.bind('i', rightUp)
    root.bind('k', rightDown)
    if end == False:
        root.after(200, detectMoveKeys)

def leftUp(self):
    cv.move(paddle_l, 0, -10)
    #print('registered')
def leftDown(self):
    cv.move(paddle_l, 0, 10)
    #print('registered')
def rightUp(self):
    cv.move(paddle_r, 0, -10)
    #print('registered')
def rightDown(self):
    cv.move(paddle_r, 0, 10)
    #print('registered')

def ballMovement():
    global x, y 

    # checking if the ball touched the bottom side of the window
    if cv.coords(ball)[1] 10 == canvas_h:
        y = y * (0-1)
        print('ball touched bottom', x, y)

    # checking if the ball touched the top side of the window
    if cv.coords(ball)[1] == 0:
        y = y * (0-1)
        print('ball touched top', x, y)
    
    pos = cv.coords(ball)
    if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
        x = x * (0-1)
        print('ball touched left paddle', x, y)
    if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
        x = x * (0-1)
        print('ball touched right paddle', x, y)

    cv.move(ball, x, y)
    #print(cv.coords(ball)[0])
    cv.pack()
    if end == False:
        root.after(35, ballMovement) 

def outOfBounds():
    global num_l, num_r, ball, dirx, diry, x, y

    # checking if the ball is out of bounds on the left side
    if cv.coords(ball)[0] <= -30.0:
        print('right scored')
        num_r  = 1
        score_right['text'] = str(num_r)
        
        # setting the ball to the original position and setting the direction
        cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10)
        cv.pack()
        dirx = random.randint(0, 1)
        diry = random.randint(0, 1)
        if dirx == 0 and diry == 0:
            x = -10
            y = -10
        if dirx == 0 and diry == 1:
            x = -10
            y = 10
        if dirx == 1 and diry == 0:
            x = 10
            y = -10
        if dirx == 1 and diry == 1:
            x = 10
            y = 10

    # checking if the ball is out of bounds on the right side
    if cv.coords(ball)[0] >= 1310.0:
        print('left scored')
        num_l  = 1
        score_left['text'] = str(num_l)
    
        # setting the ball to the original position and setting the direction
        cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10)
        cv.pack()
        dirx = random.randint(0, 1)
        diry = random.randint(0, 1)
        if dirx == 0 and diry == 0:
            x = -10
            y = -10
        if dirx == 0 and diry == 1:
            x = -10
            y = 10
        if dirx == 1 and diry == 0:
            x = 10
            y = -10
        if dirx == 1 and diry == 1:
            x = 10
            y = 10
    if end == False:
        root.after(200, outOfBounds)

# checking if the game should end
def endGame():
    global end, ball, paddle_l, paddle_r

    # deleting the label and settings objects back to original positions
    def r_won_destroy():
        r_won.destroy()
        cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10)
        cv.coords(paddle_l, 20, 320, 30, 390)
        cv.coords(paddle_r, 1250, 320, 1260, 390)
        score_right['text'] = '0'
        score_left['text'] = '0'

    # checking if the right score is 10, if yes, the game ends
    if score_right['text'] == '10':
        r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
        r_won.place(relx=0.3, rely=0.5)
        end = True
        #cv.itemconfig('all', fill='black')
        root.after(3000, main)
        root.after(2000, r_won_destroy)

    # deleting the label and settings objects back to original positions
    def l_won_destroy():
        l_won.destroy()
        cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2 10, canvas_h/2 10)
        cv.coords(paddle_l, 20, 320, 30, 390)
        cv.coords(paddle_r, 1250, 320, 1260, 390)
        score_right['text'] = '0'
        score_left['text'] = '0'

    # checking if the left score is 10, if yes, the game ends
    if score_left['text'] == '10':
        l_won = tk.Label(text='Left won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
        l_won.place(relx=0.3, rely=0.5)
        end = True
        #cv.itemconfig('all', fill='black')
        root.after(3000, main)
        root.after(2000, l_won_destroy)

    if end == False:
        root.after(200, endGame)

def main():
    # initializing variables
    global x, y, end, num_l, num_r
    end = False
    num_l = 0
    num_r = 0
    x = 0
    y = 0
    dirx = random.randint(0, 1)
    diry = random.randint(0, 1)

    # setting the starting diretion of the ball
    if dirx == 0 and diry == 0:
        x = -10
        y = -10
    if dirx == 0 and diry == 1:
        x = -10
        y = 10
    if dirx == 1 and diry == 0:
        x = 10
        y = -10
    if dirx == 1 and diry == 1:
        x = 10
        y = 10

    # calling other functions
    detectMoveKeys()
    ballMovement()
    outOfBounds()
    endGame()

# calling the main function
main()

# starting mainloop for the main window object
root.mainloop()
  • Related