在我使用 Python Turtles 制作的菜单中移动太快时会出现错误

问题描述 投票:0回答:1

好的,所以我正在尝试为今年早些时候制作的一款小型乌龟竞赛游戏制作菜单。菜单主要由不同颜色(和圆形)的海龟组成,当您单击它们时,它们会执行某些操作。然而,当海龟按钮转换为一组新的海龟按钮时,我的问题就出现了。

在转换过程中发生的事情是,所有海龟都从它们所在的位置下降到屏幕下方,被新的海龟按钮取代,然后这些海龟按钮又回到它们在菜单中的位置,其中一个位于一次。

但是,由于按钮一次会打开一个,因此您可以在转换仍在进行时按下一个按钮来加载转换过程!可以预见的是,该程序不太喜欢这样。那么,您将如何解决这样的问题呢?也许有一种方法可以从另一个函数远程中止一个函数,但我不知道该怎么做,哈哈

虽然我可以禁用该按钮或让他们等到转换完成,但我更愿意找到一种允许人们快速导航的方法,因为我尊重他们的时间。这是原始 Repl 的链接,以及 unfluffed可以在这里找到突出显示我的问题的版本。我怀疑问题源于的函数是

introductions
resolution
,如果您想了解按钮是如何创建的,请查看
multibutton
create_button
。请帮忙!

编辑:既然有人告诉我不允许使用外部链接,我将在这里发布完整的代码(不用担心,它不再是 450 行了):

    # Imports
import turtle
from giant_list_of_all_colors import all_colors, all_shapes
import tkinter as tk
from functools import partial
from tkinter import colorchooser
from time import sleep
import random
from turtle import TurtleGraphicsError
import traceback
# Window properties
root = tk.Tk()
root.title("haha yes")

debug = True

# Making and configuring the canvas inside the window
canvas = turtle.ScrolledCanvas(root)
canvas.config(width= 600, height=400)
# Turtems
t = turtle.RawTurtle(canvas)
t.hideturtle()
sc = turtle.TurtleScreen(canvas)

# Pack the canvas into the window
canvas.pack()

# vars
screen_width = sc.window_width()
screen_height = sc.window_height()

negative_max = -(screen_width / 2)
positive_max = -(negative_max)

all_button_turtles = []
all_button_pos = []
all_shapes = ["arrow", "square", "circle", "turtle", "triangle", "classic"]

if debug:
  print('program starting...')

# Core Button Automation/Creation Code
""" 
CREATE_BUTTON:
- Makes a Turtle-based button, complete with text and function binding to onclick(). 
- While it _can_ be used by itself, it's more commonly used by the multibutton function 
- After the creation of the button, it adds position data to all_button_pos (gets deleted in "def introductions") as well as adds the button to all_button_turtles (gets deleted in "def resolutions")
- Don't expect anything else to be this well commented-- I made this part for a school assignment, but then decided to expand upon it more myself... so yeah, sorry about that 
"""

def create_button(button_text, redirect_to_func, color = 'grey', pos = (0,-50), turtle_size = (3,5), font_info = ('Comic Sans MS', 10, 'bold'), text_color = 'purple', shape = 'circle'):
  global all_button_turtles
  turtle_name = turtle.RawTurtle(canvas) # Makes it so whatever name you put in becomes a turtle
  turtle_name.speed(0)
  turtle_name.up()
  turtle_name.hideturtle()
  turtle_name.goto(pos) # Goes to specified pos in arguments
  turtle_name.turtlesize(turtle_size[0], turtle_size[0], turtle_size[1]) #Makes the DIY button bigger
  turtle_name.pencolor(text_color)
  turtle_name.rt(90)
  turtle_name.fd(20 * turtle_size[0])
  turtle_name.write(button_text, True, 'center', font = font_info)
  turtle_name.fillcolor(color) # Changes the color to what you want the color of the button to be
  turtle_name.pencolor('black') # Makes the border along the edge black
  turtle_name.shape(shape) #Makes the button... look like a button (assuming it's a circle)
    
  if debug:
    print(f"The '{color}' button ('{button_text}') is been created and put at {pos[0]}, {pos[1]}!") #debug go brrr
  all_button_turtles.append(turtle_name)
  all_button_pos.append(pos)  
  turtle_name.onclick(redirect_to_func, 1) # Starts up the click listener on left click
  
  return turtle_name # Returns all of this data in case I wanna do something with it later

""" 
MULTIBUTTON:
- It's create_button, but for more buttons!
- It make a bunch of buttons at once, and automatically spaces them out depending on how many buttons you specified for it to make (up to like 8 or so because the canvas is too small to support more)
- It can also automate coloring of text/buttons, like it you want random or a repeating pattern of alternating colors
- A bit outdated, as I think there was a small bug with the color_pick_type parameter that I fixed later. Don't worry-- I swear that's not the cause of my problem!
"""

def multibutton(button_text, button_funcs, button_colors = ['red', 'orange'], y_value = 0, color_pick_type = 'incrimental', text_colors = 'purple', button_shapes = 'circle'):
  
  Button = {num: turtle.Turtle for num in range(len(button_text))}
  
  for new_buttons in range(len(button_text)):

    spacing = (negative_max * (1 / len(button_text) + 1)) - ((new_buttons + 1) * -(screen_width / len(button_text)))

    if isinstance(button_colors, str):
      color = button_colors
    elif color_pick_type == 'random':
      color = random.choice(button_colors)
    elif color_pick_type == 'incrimental':
      color = button_colors[new_buttons % len(button_colors)]
    else:
      msg("Something went wrong", "Color or button_color wasn't assigned correctly!", 3)
      exit()

    if isinstance(text_colors, str):
      text_color = text_colors
    elif color_pick_type == 'random':
      text_color = random.choice(text_colors)
    elif color_pick_type == 'incrimental':
      text_color = text_colors[new_buttons % len(text_colors)]
    else:
      msg("Something went wrong", "Color or text_color wasn't assigned correctly!", 3)
      exit()
    
    if isinstance(button_shapes, str):
      shape = button_shapes
    elif color_pick_type == 'random':
      shape = random.choice(button_shapes)
    elif color_pick_type == 'incrimental':
      shape = button_shapes[new_buttons % len(button_shapes)]
    else:
      msg("Something went wrong", "shape or button_shapes wasn't assigned correctly!", 3)
      exit()
    
    # Running final commands
    try:
      if button_funcs[new_buttons][0]:    
        Button[new_buttons] = create_button(button_text[new_buttons], partial(resolution, button_funcs[new_buttons][1]), color, pos = (spacing, y_value), text_color = text_color, shape = shape)
      elif not button_funcs[new_buttons][0]:
        Button[new_buttons] = create_button(button_text[new_buttons], partial(button_funcs[new_buttons][1]), color, pos = (spacing, y_value), text_color = text_color, shape = shape)
  
    except TypeError:
        Button[new_buttons] = create_button(button_text[new_buttons], partial(resolution, button_funcs[new_buttons]), color, pos = (spacing, y_value), text_color = text_color, shape = shape)
    if debug:
      print("Multibutton Job's done :p")

# Scene Transitioners
"""
INTRODUCTIONS:
- The problem child and where all the errors are stemming from 
- Essentially handles moving the buttons up to their proper places while making it look prettier than just having the buttons teleport to where they need to go
- Deletes all saved button positions in 'all_button_pos' after its done running, as I don't need them anymore (does this near the very end) 
- Also handles making the title and subtitles... by forwarding it to an entirely different function later down the line (create_titlescreen) 
"""
def introductions(title, *subtitle):
  global all_button_turtles
  if debug:
    print(f"moving ({len(all_button_turtles)}) button(s) up")
  create_titlescreen(title, subtitle)
  for turtems in all_button_turtles:
    turtems.up()
    try:
      turtems.goto(all_button_pos[(all_button_turtles.index(turtems))][0], -(screen_height))
    except IndexError as ie:
      print("\n\nAAHHH NOOOO IT FAILEDDD D:")
      print(f"All called turtles ({len(all_button_turtles)}):{all_button_turtles}")
      print(f"All called turtle's positions ({len(all_button_pos)}): {all_button_pos}")
      print(f"ONE-LINER ERROR: {ie}")
      print(f"TRACEBACK:")
      traceback.print_exc()
      exit()
    if debug:
      print(f"TURTEM POS ({all_button_turtles.index(turtems) + 1}): {turtems.pos()}")
    turtems.showturtle()
    turtems.seth(270)
    turtems.speed(3)
    turtems.goto(all_button_pos[(all_button_turtles.index(turtems))])
    if debug:
      try:
        print(f"FINAL TURTEM POS ({all_button_turtles.index(turtems) + 1}): {turtems.pos()}")
      except ValueError as ve:
        print("\n\nAAHHH NOOOO IT FAILEDDD... but for a different reason???")
        print(f"All called turtles ({len(all_button_turtles)}):{all_button_turtles}")
        print(f"All called turtle's positions ({len(all_button_pos)}): {all_button_pos}")
        print(f"ONE-LINER ERROR: {ve}")
        print(f"TRACEBACK:")
        traceback.print_exc()
        exit()
  del all_button_pos[:]
"""
RESOLUTION:
- Does the same thing as 'introductions', but backwards!
- Moves buttons down in preparation for deletion offscreen
- Not only does this handle deleting the buttons/text, but it also clears "all_turtle_buttons", the program's ONLY way of telling what button turtles are currently in play
"""   
def resolution(func, x = None, y = None, *args):
  global all_button_turtles
  temp_turtles = all_button_turtles.copy()
  del all_button_turtles[:]
  for turtems in temp_turtles:
    turtems.onclick(None)
  t.clear()
  if not debug:
    print(f"THE GREAT RESOLUTION ({len(temp_turtles)})")
  for turtem in temp_turtles:
    turtem.clear()
    turtem.speed(3)
    turtem.up()
    turtem.seth(90)
    turtem.fd(abs(turtem.xcor()) - screen_height)
    turtem.hideturtle()
  del temp_turtles
  if debug:
    print(f"The following number should be 0... and if it's not, then we have some problems: {len(all_button_turtles)}")
  func(args)

"""
CREATE_TITLESCREEN:
- Handles making the title/subtitles at the start of every scene
- Accepts only 1 title parameter, but as many subtitle parameters as your heart's desires
- the "size" parameter controls the size of the big title, then half of size makes the font size for the subtitles 
- For every new argument for a subtitle, it acts like you just did a \n and makes a new line
""" 
def create_titlescreen(title, subtitles, size = 23):
  t.speed(69) # haha funny number
  t.home()
  t.seth(90)
  t.clear()
  t.up()
  t.goto(0, screen_height / 3)
  t.write(title, align= "center", font=("Comic Sans MS", size, "bold"))
  if len(subtitles) != 0:
    for txt in subtitles:  
      t.bk(size / 2)
      t.write(str(txt), align = 'center')
  t.home()
  t.hideturtle()
  t.down()

# GUI messaging
"""
MSG:
- There used to be other functions here (user-input popups like bool, num, and str), but I removed them for the sake of simplicity
- Pretty much self-explanitory; the function uses 'tk.messagebox' to make a popup that the user has to read before moving on. I probably didn't _have_ to make this (probably REALLY redundant), but I got sick of typing the entire thing out 
-  Like with the original messagebox function, the first 2 parameters make the title of the popup as well as its contents, respectively
- However, the last perameter (type) handles what _type_ of messagebox is shown to the user, with the higher the number, the more severe the message will be (normal, then warning, then error)
- Any type argument higher than 3 or less than 1 will error out
"""
def msg(title, prompt, type = 1, *args):
  if type == 1:
    tk.messagebox.showinfo(title, prompt)
  elif type == 2:
    tk.messagebox.showwarning(title, prompt)
  elif type == 3:
    tk.messagebox.showerror(title, prompt)
  else:
    tk.messagebox.showerror("Either your or my code sucks", "Whatever number you put into msg() was not recognized!")

# Screen5 Button Code (all of it got removed)
def receive_shape(player, x, y):
  msg("(Un)successfully applied!", "I literally deleted everything in here :)")
  if player == 1:
    resolution(screen5A)
  else:
    resolution(screen5B)

def choose_color(turtle,player,*gonna):
  msg("I cut out all the content here", "To help isolate my problem, I took out all the code here")

def shape_scene(player, x, y):
  multibutton(
    [all_shapes[0], all_shapes[1], all_shapes[2], all_shapes[3], all_shapes[4], all_shapes[5]],
              [
               [False, partial(receive_shape, player)],
               [False, partial(receive_shape, player)],
               [False, partial(receive_shape, player)],
               [False, partial(receive_shape, player)],
               [False, partial(receive_shape, player)],
               [False, partial(receive_shape, player)]
              ],
              
              button_colors = 'black', button_shapes = all_shapes)
  introductions("test3: Shape Scene", "If you tried to go here before the menus were done loading, it shouldn't have crashed.", "Probably because the previous menu had the same # of buttons in the same place.", "Might be worth looking into all_buttons_pos?")

def convert_dice():
  msg("I cut out all the content here", "To help isolate my problem, I took out all the code here")
def choose_dice(*you):
  msg("I cut out all the content here", "To help isolate my problem, I took out all the code here")

def choose_key(*down):  
  msg("I cut out all the content here", "To help isolate my problem, I took out all the code here")
def choose_headstart(*never):
  msg("I cut out all the content here", "To help isolate my problem, I took out all the code here")

def back_to_menu(x, y):
  resolution(screen2)

# Actual Scene code:
def screen2(gonna):
  button_colors = ['red', 'orange', 'red']
  sc.onscreenclick(None, 3)
  sc.onscreenclick(None, 2)
  multibutton(['Player 1', 'Player 2', 'back to title'], [screen5A, screen5B, titlescreen], button_colors = button_colors)
  introductions("Player Select", "Choose a button and do it quick! Don't let the buttons reach their place!!!")
def screen5A(never):
  multibutton(["A", "B", "C", "D", "E?", "Leave"],
             [
               [False, choose_color],
               partial(shape_scene, 1, None),
               [False, choose_key],
               [False, choose_dice],
               [False, choose_headstart],
               [False, back_to_menu]
             ], button_colors = [random.choice(list(all_colors.keys())) for r in range(6)], text_colors = [random.choice(list(all_colors.keys())) for r in range(6)])
  introductions("Player 1", "most of these buttons don't do anything lol", "Button B leads to another menu. Click ASAP!")
def screen5B(*never):
  multibutton(["A", "B", "C", "D", "E?", "Leave"],
             [
               [False, choose_color],
               partial(shape_scene, 2, None),
               [False, choose_key],
               [False, choose_dice],
               [False, choose_headstart],
               [False, back_to_menu]
             ], button_colors = [random.choice(list(all_colors.keys())) for r in range(6)], text_colors = [random.choice(list(all_colors.keys())) for r in range(6)])  
  introductions("Player 2", "most of these buttons don't do anything lol", "Button B leads to another menu. Click while the transition's still going on!")

def to_screen2(*you):
  msg("Before you continue...", "The bug in question happens when you go through the menus too fast! Try blitzing through the next menu before all the buttons come up, and watch the console while ding it!")
  msg("One more thing...", "Different things happen depending on if debug mode is on! With it off, it crashes due to an IndexError. With it on, it's just a ValueError. Give each one a shot later!")
  resolution(screen2)

def titlescreen(*give):
  hi = create_button('Press da big red button to start the bad code :)', to_screen2, 'firebrick', turtle_size = (5, 7), text_color = 'purple')
  introductions("Turtems; Problematic Build", "haha no")
  
"""RUNTIME CODE"""
titlescreen(None)

# Start the Tkinter event loop
root.mainloop()

Edit2:我忘记了您需要将其放在一个单独的文件中才能运行此代码。它曾经有所有的颜色(有太多的颜色,我不得不制作一个单独的Python程序只是为了将它们全部转换成字典变量),但我用完了字数限制来发布所有的颜色,所以就用这个吧:

all_colors = {'yellow': [255, 255, 0], 'green': [0, 255, 0], 'red': [255, 0, 0], 'blue': [0, 255, 0], 'orange': [255, 165, 0], 'purple': [160, 32, 240], 'violet': [238, 130, 238]}
all_shapes = ["arrow", "square", "circle", "turtle", "triangle", "classic"]

请务必将这两行保存在名为 giant_list_of_all_colors.py

单独文件
中!我绝对应该把它做成一个文本文件,对此感到抱歉😅。如果您出于某种原因想要全彩列表,请查看上面我喜欢的 2 个 Repl 中的任何一个。如果您有兴趣的话,那里应该有完整的列表

python turtle-graphics python-turtle
1个回答
0
投票

那里仍然有很多代码,但我猜测这是

all_button_turtles
列表的多任务同步问题(“竞争条件”)。例如,调用事件处理程序来激活按钮的动画。然后,立即单击一个按钮,并调用将其动画化的事件处理程序。所以你有 2 个处理程序同时运行。一个正在读取列表,另一个正在修改它,同时进行。这是一个问题。

有几种解决方案。我认为 OOP 可以在这里帮助你。创建(至少)2 个类:

Screen
Button
。每个
Screen
对象都有自己的按钮列表。每个
Button
对象都包含自己的位置信息。如果您不熟悉 Python 中的类,您可能只有多个按钮列表。每个屏幕一个。

目标是不存在全局

all_button_turtles
all_button_pos
列表。或者,至少使用同步技术来确保不存在竞争条件。此选项将比多个列表或类更复杂。

© www.soinside.com 2019 - 2024. All rights reserved.