【问题标题】:How to make items draw at the same time in python using turtle?python - 如何使用turtle在python中同时绘制项目?
【发布时间】:2014-11-22 20:31:34
【问题描述】:

我有一个家庭作业,我必须让四只不同的海龟像围绕太阳运行的行星一样移动。我都写好了,只是让乌龟同时画画。我想知道是否有一种相对简单的方法可以让它们在同一时间(在合理范围内)开始?无论如何,这是代码:

def planets():
    """simulates motion of Mercury, Venus, Earth, and Mars"""
    import turtle

    mercury = turtle.Turtle()
    venus = turtle.Turtle()
    earth = turtle.Turtle()
    mars = turtle.Turtle()
    mercury.shape('circle')
    venus.shape('circle')
    earth.shape('circle')
    mars.shape('circle')
    mercury.pu()
    venus.pu()
    earth.pu()
    mars.pu()
    mercury.sety(-58)
    venus.sety(-108)
    earth.sety(-150)
    mars.sety(-228)
    mercury.pd()
    venus.pd()
    earth.pd()
    mars.pd()
    mars.speed(7.5)
    venus.speed(3)
    earth.speed(2)
    mars.speed(1)
    mercury.circle(58)
    venus.circle(108)
    earth.circle(150)
    mars.circle(228)

提前致谢!

【问题讨论】:

    标签: python python-3.x turtle-graphics


    【解决方案1】:

    一般来说,如果你想同时做多件事情,有两种选择:

    • 抢占式多线程,您只需为每个事物创建一个线程,然后它们都尝试全速工作,而计算机会弄清楚如何交错工作。
    • 协作调度:您为一件事情做一小部分工作,然后为下一件事情做一小部分,依此类推,然后再回到第一个。

    在这种情况下,它是您想要的第二个。 (好吧,你可能想要第一个,但你不能拥有它;tkinter,因此turtle,只能在主线程上运行。)例如,画第一个 1每个圆的°,然后每个圆的下一个 1°,依此类推。

    那么,你是怎么做到的呢? circle 方法有一个可选的extent 参数,它是一个要绘制的角度(以度为单位)。所以,你可以这样做:

    for i in range(360):
        mercury.circle(58, 1)
        venus.circle(108, 1)
        earth.circle(150, 1)
        mars.circle(228, 1)
    

    当然,extent 的值越小,每只海龟的“步数”就越多,因此它们进入轨道的速度就越慢。

    另外,我不确定您是否真的想以您使用它的方式使用speed。这会导致每个动作的动画更慢。它不会影响它们绕太阳运行的速度,它只会影响每一步绘制所需的时间。所以我认为你在这里真正想要做的是将所有速度保持在 0(没有动画延迟),但每一步移动更快的行星更大范围:

    mercury.speed(0)
    venus.speed(0)
    earth.speed(0)
    mars.speed(0)
    for i in range(360):
        mercury.circle(58, 7.5)
        venus.circle(108, 3)
        earth.circle(150, 2)
        mars.circle(228, 1)
    

    当然这意味着水星最终会绕太阳公转 7.5 圈,而火星只会绕太阳公转一次……但这正是你想要的,对吧?

    【讨论】:

    • 谢谢!没想到
    • 当您说只有两种解决方案时,我认为您将这个特定问题过于复杂化了。例如,如果海龟在刷新显示之前绘制了四个项目,它们将同时绘制所有意图和目的。所以,这变成了第三种方法。对于初学者来说,谈论线程和调度程序是将它们推入池的深处。 (诚​​然,谈论推迟屏幕重绘也将它们带到了浅滩之外......)
    • @BryanOakley:我很确定他真的想看到轨道动画,在这种情况下,在关闭更新的情况下绘制它们将无济于事。但我可能是错的。让我们看看是否是 cmets 进一步,如果是,我会在适当的时候添加该选项。
    【解决方案2】:

    在我的另一个答案中,我说您必须进行某种协作调度,因为tkinter 不是线程安全的。但这并不完全正确。 tkinter 是线程安全的,它只是没有任何类型的调度机制来从后台线程在主循环上发布事件,因此您必须添加队列或其他方式来做到这一点。

    我绝对不推荐在这里使用线程,但值得看看它是如何工作的。

    Allen B. Taylor 聪明的mtTkinter 库为您包装了所有的魔法。它不适用于 Python 3,但我已经移植了它,您可以在 GitHub 上以 mttkinter 的形式获取它。该模块有任何安装程序;您必须将其复制到与planets.py 相同的目录中。但是你可以这样做:

    import threading
    import turtle
    import mttkinter
    
    def planets():
        """simulates motion of Mercury, Venus, Earth, and Mars"""
        # Use your existing code, up to...
        mars.speed(1)
    
        # Now create a thread for each planet and start them
        mercury_thread = threading.Thread(target=lambda: mercury.circle(58))
        venus_thread = threading.Thread(target=lambda: venus.circle(108))
        earth_thread = threading.Thread(target=lambda: earth.circle(150))
        mars_thread = threading.Thread(target=lambda: mars.circle(228))
        mercury_thread.start()
        venus_thread.start()
        earth_thread.start()
        mars_thread.start()
    
        # Unfortunately, if we just exit the function here, the main thread
        # will try to exit, which means it'll wait on all the background threads.
        # But since they're all posting events and waiting on the main thread to
        # reply, they'll deadlock. So, we need to do something tkinter-related
        # here, like:
        turtle.Screen().exitonclick()
    
    planets()
    

    【讨论】:

      【解决方案3】:

      turtle.py 以两个测试演示结束。第二个以一只乌龟“三”追逐另一只“乌龟”结束。它执行 abarnet 在第一篇文章中建议的操作——在循环内递增。

      while tri.distance(turtle) > 4:
          turtle.fd(3.5)
          turtle.lt(0.6)
          tri.setheading(tri.towards(turtle))
          tri.fd(4)
      

      turtledemo 包有多个示例可供学习。 python -m turtledemo 是启动查看器的简单方法。有几个错误已在未来版本中修复。

      【讨论】:

        【解决方案4】:

        由于之前的答案都没有提到海龟自己的ontimer() 来运行模拟,所以我决定做一个使用它的实现,并使整个问题更加面向数据:

        from turtle import Turtle, Screen
        
        """ Simulate motion of Mercury, Venus, Earth, and Mars """
        
        planets = {
            'mercury': {'diameter': 0.383, 'orbit': 58, 'speed': 7.5, 'color': 'gray'},
            'venus': {'diameter': 0.949, 'orbit': 108, 'speed': 3, 'color': 'yellow'},
            'earth': {'diameter': 1.0, 'orbit': 150, 'speed': 2, 'color': 'blue'},
            'mars': {'diameter': 0.532, 'orbit': 228, 'speed': 1, 'color': 'red'},
        }
        
        def setup_planets(planets):
            for planet in planets:
                dictionary = planets[planet]
                turtle = Turtle(shape='circle')
        
                turtle.speed("fastest")  # speed controlled elsewhere, disable here
                turtle.shapesize(dictionary['diameter'])
                turtle.color(dictionary['color'])
                turtle.pu()
                turtle.sety(-dictionary['orbit'])
                turtle.pd()
        
                dictionary['turtle'] = turtle
        
            screen.ontimer(revolve, 50)
        
        def revolve():
            for planet in planets:
                dictionary = planets[planet]
                dictionary['turtle'].circle(dictionary['orbit'], dictionary['speed'])
        
            screen.ontimer(revolve, 50)
        
        screen = Screen()
        
        setup_planets(planets)
        
        screen.exitonclick()
        

        及时输出快照

        【讨论】:

          【解决方案5】:

          其他用户发布的方法效果很好。然而,我用面向对象的设计做了一个类似的太阳系模型,我所做的是创建一个名为 System 的类,在其中我创建了一个具有所需高度和宽度的系统,并创建了一个 stepAll 函数,它有一个代理列表并推进所有该列表中的代理“一步”:

          class System:
          """A two-dimensional world class."""
          
              def __init__(self, width, height):
                  """Construct a new flat world with the given dimensions."""
          
                  self._width = width
                  self._height = height
                  self._agents = { }
                  self.message = None
          
              def getWidth(self):
                  """Return the width of self."""
          
                  return self._width
          
              def getHeight(self):
                  """Return the height of self."""
          
                  return self._height
          
              def stepAll(self):
                  """All agents advance one step in the simulation."""
          
                  agents = list(self._agents.values())
                  for agent in agents:
                      agent.step()
          

          然后,我创建了一个 Planet 类,并制作了行星代理,并在 Step 函数中定义了它们的步骤。

          class Planet:
          """A planet object"""
          
              def __init__(self, mySystem, distance, size, color, velocity, real_size, real_mass, name):
                  self = self
                  self._system = mySystem
                  mySystem._agents[self] = self #make planet an agent
                  self._velocity = velocity
                  self._color = color
                  self._distance = distance
                  self._size = size
                  self._position = [distance, distance - distance]
                  self._angle = 90
                  self._real_size = real_size
                  self._real_mass = real_mass
                  self._name = name
          
                  #MAKE PLANET
                  self._turtle = turtle.Turtle(shape = 'circle')
                  self._turtle.hideturtle()
          
                  #INITIALIZE PLANET
                  self._turtle.speed('fastest')
                  self._turtle.fillcolor(color)
                  self._turtle.penup()
                  self._turtle.goto(self._position)
                  self._turtle.turtlesize(size,size,size)
                  self._turtle.showturtle()
          
          
              def getmySystem(self):
                  """Returns the system the planet is in"""
                  return self._mySystem
          
              def getdistance(self):
                  """Returns the distance the planet is from the sun"""
                  return self._distance
          
              def getposition(self):
                  """Returns the position of the planet"""
                  return self._position
          
              def getvelocity(self):
                  """Returns the velocity of the planet"""
                  return self._velocity
          
              def step(self):
                  """Moves the planet one step on its orbit according to its velocity and previous position"""
                  xvar = self._position[0]
                  yvar = self._position[1]
          
                  newx = int(self._distance*math.cos(math.radians(90-self._angle)))
                  newy = int(self._distance*math.sin(math.radians(90-self._angle)))
          
                  self._turtle.goto(newx, newy)
          
                  self._angle = self._angle - self._velocity
          

          然后在我的 main() 函数中,我用它们各自的值初始化了所有行星并说:

          while True:
              space.stepAll()
          

          这实现了您的目标,并且还可以通过调用 Planet Class 来更轻松地稍后添加具有您希望它包含的特定参数的另一个行星,而不是绘制一个全新的行星并尝试单独将其与其他行星一起移动.

          希望这对某人有所帮助!

          【讨论】:

          • 您的最后两个语句update()exitonclick() 永远不会到达,因为前一个语句是一个无限循环:while True:。如果您单击您的窗口,它不会按指定的单击退出。一个合适的海龟程序不应该包含无限循环,因为它会阻止其他事件触发。例如,如果您在行星/海龟上设置了onclick() 事件,它们将不会响应。此外,您上面的代码缩进也不正确。
          • 我将修复代码缩进。感谢您指出“update()”和“exitonclick()”不会做任何事情。但是,我有行星的点击事件来显示信息,它们工作得很好。
          猜你喜欢
          • 2018-03-23
          • 1970-01-01
          • 2014-08-29
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          • 2016-09-22
          • 2017-03-05
          • 1970-01-01
          相关资源
          最近更新 更多