问题
I have a problem using turtle with tkinter in Python 3.8. Still new to programming so thanks in advance!
I have a tkinter window where you can chose to play either level one or two, every time you start the program either level will work but once you have finished the level and try another level, including the same level I get an error.
error message:
"Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:/Users/Kev/IdeaProjects/HelloWorld/Games/Maze.py", line 49, in load_level_2
set_up_maze(levels[2]) # choose what level to load
File "C:/Users/Kev/IdeaProjects/HelloWorld/Games/Maze.py", line 254, in set_up_maze
walls.goto(screen_x, screen_y) # make the * characters into walls
File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 1776, in goto
self._goto(Vec2D(x, y))
File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 3158, in _goto
screen._pointlist(self.currentLineItem),
File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\turtle.py", line 755, in _pointlist
cl = self.cv.coords(item)
File "<string>", line 1, in coords
File "C:\Users\Kev\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2761, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"
code:
import turtle
import math
from time import monotonic as my_timer
import tkinter as tk
# turtle variables
bg_color = "black"
wall_shape = "square"
wall_color = "red"
player_shape = "classic"
player_color = "white"
def load_level_1():
hide_root()
main_level1 = turtle.Screen()
main_level1.bgcolor(bg_color)
main_level1.title("Level 1")
main_level1.setup(700, 700)
main_level1.tracer(0)
print("try to set level up")
set_up_maze(levels[1])
print("level set up")
start_time = my_timer()
level_finished = False
while not level_finished:
for treasure in treasures:
if player.has_collided(treasure):
player.gold += treasure.gold
treasure.destroy()
treasures.remove(treasure)
for end in end_points:
if player.has_collided(end):
print("Finish reached")
level_finished = True
main_level1.update()
main_level1.clear()
main_level1.bye()
show_root()
end_time = my_timer()
total_time = end_time - start_time
player.print_score()
print("Total time was {:.2f} seconds".format(total_time))
def load_level_2():
hide_root()
main_level2 = turtle.Screen()
main_level2.bgcolor(bg_color)
main_level2.title("Level 2")
main_level2.setup(700, 700)
main_level2.tracer(0)
print("try to set level up")
set_up_maze(levels[2]) # choose what level to load
print("level set up")
start_time = my_timer()
level_finished = False
while not level_finished:
for treasure in treasures:
if player.has_collided(treasure):
player.gold += treasure.gold
treasure.destroy()
treasures.remove(treasure)
for end in end_points:
if player.has_collided(end):
print("Finish reached")
level_finished = True
main_level2.update()
main_level2.clear()
main_level2.bye()
show_root()
print("finished")
end_time = my_timer()
total_time = end_time - start_time
player.print_score()
print("Total time was {:.2f} seconds".format(total_time))
# create the pen
class Walls(turtle.Turtle):
def __init__(self):
turtle.Turtle.__init__(self)
self.shape(wall_shape)
self.color(wall_color)
self.penup()
self.speed(0)
class Player(turtle.Turtle):
def __init__(self):
turtle.Turtle.__init__(self)
self.shape(player_shape)
self.color(player_color)
self.penup()
self.speed(0)
self.gold = 0
self.settiltangle(-90)
def move_up(self):
self.settiltangle(90)
if (self.xcor(), self.ycor() + 24) not in wall_coordinates:
self.goto(self.xcor(), self.ycor() + 24)
def move_down(self):
self.settiltangle(-90)
if (self.xcor(), self.ycor() - 24) not in wall_coordinates:
self.goto(self.xcor(), self.ycor() - 24)
def move_left(self):
self.settiltangle(180)
if (self.xcor() - 24, self.ycor()) not in wall_coordinates:
self.goto(self.xcor() - 24, self.ycor())
def move_right(self):
self.settiltangle(0)
if (self.xcor() + 24, self.ycor()) not in wall_coordinates:
self.goto(self.xcor() + 24, self.ycor())
def has_collided(self, other):
a = self.xcor() - other.xcor()
b = self.ycor() - other.ycor()
distance = math.sqrt((a ** 2) + (b ** 2))
if distance < 5:
return True
else:
return False
def print_score(self):
print("Your total score is: {} ".format(self.gold))
class Treasure(turtle.Turtle):
def __init__(self, x, y):
turtle.Turtle.__init__(self)
self.shape("circle")
self.color("yellow")
self.penup()
self.speed(0.5)
self.gold = 100
self.goto(x, y)
def destroy(self):
self.goto(2000, 2000)
self.hideturtle()
class Finish(turtle.Turtle):
def __init__(self, x, y):
turtle.Turtle.__init__(self)
self.shape("square")
self.color("green")
self.penup()
self.speed(0.5)
self.goto(x, y)
# lists
levels = [""]
wall_coordinates = []
treasures = []
end_points = []
level_template = ["*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************",
"*************************"]
level_1 = ['*************************',
'*S***** ********',
'* E******* *** *** *',
'** T **** *** ** *',
'**** ****** **** *** ** *',
'**** ** **** *** ** *',
'*** ** * ** ** *',
'**** * * T ******* * *',
'*** *** ** T ** * **',
'*T * ******** ** **',
'** ***** **** ** *',
'** ***T************** *',
'* ** *** **** ** T *',
'*T * *** ***** ****',
'** * ** **** * *',
'** * ** * ** ***** * **',
'** ** ** *** **',
'****** *** T **** **',
'** ** ** * ** ** **** **',
'*E* ** ** * ***** **** **',
'* * ** ** * ***',
'* * *** **** *****',
'* *********** *T** **',
'* * ** *',
'*************************']
level_2 = ['*************************',
'*******S E********',
'**T ******* *** *** *',
'*** **** *** ** *',
'**** ****** **** *** ** *',
'**** ** **** *** ** *',
'*** ** * ** ** *',
'**** * * T ******* * *',
'*** *** ** T ** * **',
'*T * ******** ** **',
'** ***** **** ** *',
'** ***T************** *',
'* ** *** **** ** T *',
'*T * *** ***** ****',
'** * ** **** * *',
'** * ** * ** ***** * **',
'** ** ** *** **',
'****** *** T **** **',
'** ** ** * ** ** **** **',
'** ** ** * *** ** **** **',
'*T* ** ** * ***',
'* * *** **** *****',
'* *********** *T** **',
'* * ** E*',
'*************************']
levels.append(level_1)
levels.append(level_2)
def set_up_maze(level):
for y in range(len(level)): # get the character co ordinates
for x in range(len(level[y])):
character = level[y][x] # save the character coordinates
screen_x = -288 + (x * 24) # calculate the screen co ordinates
screen_y = 288 - (y * 24)
if character == "*":
walls.goto(screen_x, screen_y) # make the * characters into walls
walls.stamp()
wall_coordinates.append((screen_x, screen_y))
if character == "S": # make the player start point
player.goto(screen_x, screen_y)
if character == "T": # make the treasure spawn points
treasures.append(Treasure(screen_x, screen_y))
if character == "E": # make the end point
end_points.append(Finish(screen_x, screen_y))
walls = Walls()
player = Player()
# key bindings
turtle.listen()
turtle.onkey(player.move_up, "Up")
turtle.onkey(player.move_down, "Down")
turtle.onkey(player.move_left, "Left")
turtle.onkey(player.move_right, "Right")
# tk variables
tk_bg_color = "light green"
font = ("comic sans ms", 20)
btn_height = 2
btn_width = 10
pad_x = 40
pad_y = 25
def hide_root():
root.withdraw()
def show_root():
root.deiconify()
root.update()
root = tk.Tk()
root.title("Maze game")
root.config(bg=tk_bg_color)
root.geometry("250x600+600+100")
root.resizable(width=False, height=False)
title_label = tk.Label(root, text="Maze Game", font=font, bg=tk_bg_color)
title_label.grid(row=0, column=0, padx=pad_x, pady=pad_y)
level_1_btn = tk.Button(root, text="Level 1", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
command=load_level_1)
level_1_btn.grid(row=1, column=0, padx=pad_x, pady=pad_y)
level_2_btn = tk.Button(root, text="Level 2", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
command=load_level_2)
level_2_btn.grid(row=2, column=0, padx=pad_x, pady=pad_y)
close_btn = tk.Button(root, text="Exit", font=font, height=btn_height, width=btn_width, bg=tk_bg_color,
command=quit)
close_btn.grid(row=3, column=0, padx=pad_x, pady=pad_y)
root.mainloop()
回答1:
A number of changes need to be made to get a design that works:
First, when you use turtle within tkinter, you need to use embedded turtle (i.e. TurtleScreen
, RawTurtle
) not standalone turtle (Screen
, Turtle
).
Since TurtleScreen
doesn't want to be a Toplevel
instance, I swapped the maze and menu windows.
The turtle screen.clear()
method is highly destructive -- besides clearing the screen, it undoes bindings, background colors, tracer setings, etc. and kills all the turtles. So we have to program accordingly.
Don't call screen.bye()
if you plan to use the window again. Turtle has a distance()
method, you don't have to reinvent it.
Finally, turtles wander a floating point plan. If you save the coordinates of your walls, they won't match turtle positions as turtles accumulate error. You need to coerce the comparison to integer.
Below is my attempt to rework your code to address the above issues:
from turtle import TurtleScreen, RawTurtle
from time import monotonic as my_timer
import tkinter as tk
# tk constants
TK_BG_COLOR = "light green"
FONT = ("comic sans ms", 20)
BUTTON_HEIGHT = 2
BUTTON_WIDTH = 10
PAD_X = 40
PAD_Y = 25
# turtle contants
BG_COLOR = 'black'
WALL_SHAPE = 'square'
WALL_COLOR = 'red'
WALL_SIZE = 24
PLAYER_SHAPE = 'classic'
PLAYER_COLOR = 'white'
CURSOR_SIZE = 20
level_1 = [
'*************************',
'*S***** ********',
'* ******* *** *** *',
'** T **** *** ** *',
'**** ****** **** *** ** *',
'**** ** **** *** ** *',
'*** ** * ** ** *',
'**** * * T ******* * *',
'*** *** ** T ** * **',
'*T * ******** ** **',
'** ***** **** ** *',
'** ***T************** *',
'* ** *** **** ** T *',
'*T * *** ***** ****',
'** * ** **** * *',
'** * ** * ** ***** * **',
'** ** ** *** **',
'****** *** T **** **',
'** ** ** * ** ** **** **',
'*E* ** ** * ***** **** **',
'* * ** ** * ***',
'* * *** **** *****',
'* *********** *T** **',
'* * ** *',
'*************************'
]
level_2 = [
'*************************',
'*******S ********',
'**T ******* *** *** *',
'*** **** *** ** *',
'**** ****** **** *** ** *',
'**** ** **** *** ** *',
'*** ** * ** ** *',
'**** * * T ******* * *',
'*** *** ** T ** * **',
'*T * ******** ** **',
'** ***** **** ** *',
'** ***T************** *',
'* ** *** **** ** T *',
'*T * *** ***** ****',
'** * ** **** * *',
'** * ** * ** ***** * **',
'** ** ** *** **',
'****** *** T **** **',
'** ** ** * ** ** **** **',
'** ** ** * *** ** **** **',
'*T* ** ** * ***',
'* * *** **** *****',
'* *********** *T** **',
'* * ** E*',
'*************************'
]
levels = [("", None), ("Level 2", level_1), ("Level 2", level_2)]
class Walls(RawTurtle):
def __init__(self, canvas):
super().__init__(canvas)
self.shape(WALL_SHAPE)
self.color(WALL_COLOR)
self.penup()
class Player(RawTurtle):
def __init__(self, canvas):
super().__init__(canvas)
self.shape(PLAYER_SHAPE)
self.color(PLAYER_COLOR)
self.penup()
self.setheading(270)
self.gold = 0
def move_up(self):
self.setheading(90)
if (int(self.xcor()), int(self.ycor()) + WALL_SIZE) not in wall_coordinates:
self.sety(self.ycor() + WALL_SIZE)
def move_down(self):
self.setheading(270)
if (int(self.xcor()), int(self.ycor()) - WALL_SIZE) not in wall_coordinates:
self.sety(self.ycor() - WALL_SIZE)
def move_left(self):
self.setheading(180)
if (int(self.xcor()) - WALL_SIZE, int(self.ycor())) not in wall_coordinates:
self.setx(self.xcor() - WALL_SIZE)
def move_right(self):
self.setheading(0)
if (int(self.xcor()) + WALL_SIZE, int(self.ycor())) not in wall_coordinates:
self.setx(self.xcor() + WALL_SIZE)
def has_collided(self, other):
return self.distance(other) < 5
def print_score(self):
print("Your total score is: {} ".format(self.gold))
class Treasure(RawTurtle):
def __init__(self, canvas, x, y):
super().__init__(canvas)
self.shape('circle')
self.color('yellow')
self.penup()
self.goto(x, y)
self.gold = 100
def destroy(self):
self.hideturtle()
class Finish(RawTurtle):
def __init__(self, canvas, x, y):
super().__init__(canvas)
self.shape('square')
self.color('green')
self.penup()
self.goto(x, y)
def load_level(level):
global player
hide_menu()
title, maze = levels[level]
root.title(title)
player = Player(screen) # recreate as it's destroyed by screen.clear()
set_up_maze(maze)
# rebind turtle key bindings as they're unbound by screen.clear()
screen.onkey(player.move_up, 'Up')
screen.onkey(player.move_down, 'Down')
screen.onkey(player.move_left, 'Left')
screen.onkey(player.move_right, 'Right')
screen.listen()
level_finished = False
start_time = my_timer()
while not level_finished:
for treasure in treasures:
if player.has_collided(treasure):
player.gold += treasure.gold
treasure.destroy()
treasures.remove(treasure)
for end in end_points:
if player.has_collided(end):
level_finished = True
screen.update()
screen.clear()
screen.bgcolor(BG_COLOR) # redo as it's undone by clear()
screen.tracer(0) # redo as it's undone by clear()
show_menu()
end_time = my_timer()
total_time = end_time - start_time
player.print_score()
print("Total time was {:.2f} seconds".format(total_time))
def set_up_maze(maze):
walls = Walls(screen)
for y, row in enumerate(maze): # get the character co ordinates
for x, character in enumerate(row):
screen_x = -288 + (x * WALL_SIZE) # calculate the screen co ordinates
screen_y = 288 - (y * WALL_SIZE)
if character == '*':
walls.goto(screen_x, screen_y) # make the * characters into walls
walls.stamp()
wall_coordinates.append((screen_x, screen_y))
elif character == 'S': # make the player start point
player.goto(screen_x, screen_y)
elif character == 'T': # make the treasure spawn points
treasures.append(Treasure(screen, screen_x, screen_y))
elif character == 'E': # make the end point
end_points.append(Finish(screen, screen_x, screen_y))
def hide_menu():
menu.withdraw()
def show_menu():
menu.deiconify()
menu.update()
# lists
wall_coordinates = []
treasures = []
end_points = []
root = tk.Tk()
root.title("Maze game")
root.resizable(width=False, height=False)
canvas = tk.Canvas(root, width=700, height=700)
canvas.pack()
screen = TurtleScreen(canvas)
screen.bgcolor(BG_COLOR)
screen.tracer(0)
player = None
menu = tk.Toplevel(root)
menu.title("Maze game")
menu.config(bg=TK_BG_COLOR)
menu.geometry("250x600+600+100")
menu.resizable(width=False, height=False)
title_label = tk.Label(menu, text="Maze Game", font=FONT, bg=TK_BG_COLOR)
title_label.grid(row=0, column=0, padx=PAD_X, pady=PAD_Y)
level_1_btn = tk.Button(menu, text="Level 1", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=lambda: load_level(1))
level_1_btn.grid(row=1, column=0, padx=PAD_X, pady=PAD_Y)
level_2_btn = tk.Button(menu, text="Level 2", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=lambda: load_level(2))
level_2_btn.grid(row=2, column=0, padx=PAD_X, pady=PAD_Y)
close_btn = tk.Button(menu, text="Exit", font=FONT, height=BUTTON_HEIGHT, width=BUTTON_WIDTH, bg=TK_BG_COLOR, command=quit)
close_btn.grid(row=3, column=0, padx=PAD_X, pady=PAD_Y)
screen.mainloop()
来源:https://stackoverflow.com/questions/62191210/error-when-opening-different-windows-in-turtle-using-tkinter