python - 使用 tkinter 在海龟中打开不同窗口时出错
问题描述
我在 Python 3.8 中将 turtle 与 tkinter 一起使用时遇到问题。仍然是编程新手,所以提前感谢!
我有一个 tkinter 窗口,您可以在其中选择播放第一级或第二级,每次启动程序时,任何一个级别都可以工作,但是一旦您完成该级别并尝试另一个级别,包括同一级别,我就会收到错误消息。
错误信息:
"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"
代码:
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()
解决方案
需要进行许多更改才能获得有效的设计:
首先,当你在 tkinter 中使用 turtle 时,你需要使用嵌入的turtle(即TurtleScreen
,RawTurtle
)而不是独立的turtle(Screen
,Turtle
)。
由于TurtleScreen
不想成为Toplevel
实例,我交换了迷宫和菜单窗口。
turtlescreen.clear()
方法具有很强的破坏性——除了清除屏幕外,它还取消绑定、背景颜色、跟踪设置等,并杀死所有的海龟。所以我们必须相应地编程。
screen.bye()
如果您打算再次使用该窗口,请勿致电。Turtle 有一个distance()
方法,你不必重新发明它。
最后,海龟游走一个浮点计划。如果您保存墙壁的坐标,它们将与海龟的位置不匹配,因为海龟会累积误差。您需要强制比较整数。
以下是我尝试修改您的代码以解决上述问题:
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()
推荐阅读
- matrix - 从矩阵中删除不匹配的元素
- python - 使用 PIL 将图像转换为 rgb 数组并手动将其转换为 CMYK 数组
- c - C - 如何在 ay/n 循环中排除其他输入
- angular - Angular Forms-FormArray:找不到带有路径的控件:'strains -> i'
- regex - 使用 grep 控制台突出显示文本中所有看起来像 '&name=' 的键
- sql - 我什么时候可以使用计数或大于或小于
- arrays - 如何正确地将数据附加到数组?斯威夫特4
- asp.net-core - 包含 ViewComponent 的表单
- grafana - 自值为零以来的时间
- php - WordPress - Ajax 调用时出现 array_merge() 错误