Luke
Luke

Reputation: 65

Python turtle game error _tkinter.TclError: invalid command name ".!canvas"

I have coded a small game using python turtle, and it seems like whenever I close the turtle window manually it gives me an error, but if the game finished running and then I close it's fine. I think it has something to do with the ontimer part of the code, but I'm not sure how to fix it.

import turtle
from random import randint


wn = turtle.Screen()
circle1 = turtle.Turtle(shape = 'circle')
bullet = turtle.Turtle(shape = "circle")
bullet.ht()
bullet.speed(0)
circle1.speed(-1)
circle1.penup()
circle1.ht()
circle1.sety(-270)
circle1.st()

wn.setup(300, 600)

enemies = []
score = 0
prevscore = 1
speed = 10


for i in range(10):
    p = turtle.Turtle(shape='square',visible=False)
    p.speed(0)
    p.penup()
    p.color('blue')
    x = randint(-240,240)
    y = randint(180,360)
    p.goto(x,y)
    p.showturtle()
    enemies.append(p)

def enemy_move():
    global game_on
    global speed
    global score
    global prevscore

    for p in enemies:

        y = p.ycor()
        p.sety(y-speed)

        if p.ycor() < -300  or p.distance(bullet.pos())<30:
            if p.distance(bullet.pos())<30:
                score += 1
            p.hideturtle()
            y = randint(180,360)
            p.sety(y)
            p.showturtle()

        if circle1.isvisible() and p.distance(circle1.pos())<20:
            p.hideturtle()
            circle1.hideturtle()
            game_on = False
            wn.clear()
            circle1.goto(0, 0)
            circle1.write(f"Your final score is {score}", align ="center", font = ("Arial", 26, "normal"))


    if game_on == True:

        if score%10 == 0:

            if score != prevscore:
                speed += 0.5
            prevscore = score

        wn.ontimer(enemy_move,50)

    else:
        return

game_on = True
enemy_move()

def goright():
    if(circle1.xcor() < 130):
        circle1.seth(0)
        circle1.fd(10)
def goleft():
    if(circle1.xcor() > -130):
        circle1.seth(180)
        circle1.fd(10)

def shoot():
    bullet.penup()
    bullet.goto(circle1.pos())
    bullet.seth(90)
    bullet_move()
    bullet.showturtle()

def bullet_move():
    if bullet.ycor() <= 300:
        bullet.sety(bullet.ycor() + 10)
        wn.ontimer(bullet_move, 50)
    else:
        bullet.hideturtle()


wn.listen()

wn.onkeypress(goright, "Right")
wn.onkeypress(goleft, "Left")
wn.onkeypress(shoot, "Up")

wn.mainloop()

The error I get when I exit the code manually is this:

Traceback (most recent call last):
  File "/Users/luke/PycharmProjects/Class Teaching/Game interface example.py", line 1, in <module>
    import game
  File "/Users/luke/PycharmProjects/Class Teaching/game.py", line 31, in <module>
    p.goto(x,y)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 1777, in goto
    self._goto(Vec2D(x, y))
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 3159, in _goto
    screen._pointlist(self.currentLineItem),
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/turtle.py", line 756, in _pointlist
    cl = self.cv.coords(item)
  File "<string>", line 1, in coords
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 2762, in coords
    self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"

Upvotes: 3

Views: 3032

Answers (1)

acw1668
acw1668

Reputation: 46669

It is caused by the for loop inside enemy_move(). If you try to exit the application by destroying the window, the for loop may still be running and so accessing the already destroyed canvas raises the exception.

You can check game_on at the beginning of each iteration of the for loop:

def enemy_move():
    ...
    for p in enemies:
        # exit the function is game_on is False
        if not game_on:
            return
        ...
    ...

Then you need to set game_on to False before destroying the window. It can be done using tkinter.protocol() (as turtle is built on tkinter):

...
def on_quit():
    global game_on
    game_on = False
    # give some time to stop the for loop before destroying window
    wn._root.after(100, wn._root.destroy)

wn._root.protocol("WM_DELETE_WINDOW", on_quit)

wn.mainloop()

Upvotes: 4

Related Questions