简体   繁体   中英

How to make items draw at the same time in python using turtle?

I have a homework assignment, and I have to make four different turtles move like they are planets around the sun. I have it all written, its just a matter of making the turtles draw at the same time. I was wondering if there was a relatively easy way to make them start around the same time (within reason)? Anyway, here's the code:

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)

Thanks in advance!

In general, if you want to do multiple things at the same time, there are two options:

  • Preemptive multithreading, where you just create a thread for each thing and they all try to work at full speed and the computer figures out how to interleave that work.
  • Cooperative scheduling: you do a small piece of work for one thing, then a small piece for the next, and so on, then come back to the first one.

In this case, it's the second one that you want. (Well, you might want the first, but you can't have it; tkinter , and therefore turtle , can only run on the main thread.) Draw, say, the first 1° of each circle, then the next 1° of each circle, and so on.

So, how do you do that? The circle method has an optional extent parameter, which is an angle (in degrees) to draw. So, you can do this:

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

Of course the smaller you make that extent value, the more "steps" each turtle is taking, so the slower they will take to orbit.

Also, I'm not sure you really want to use speed the way you're using it. That causes each move to animate more slowly. It doesn't affect how quickly they orbit around the sun, it just affects how long each step takes to draw. So I think what you really want to do here is leave all the speeds at 0 (no animation delay), but move the faster planets by a larger extent each step:

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)

Of course this means Mercury will end up orbiting the sun 7.5 times, while Mars will only orbit once… but that's exactly what you want, right?

Since none of the previous answers mention turtle's own ontimer() for running the simulation, I decided to do an implementation that uses it and makes the whole problem more data-oriented:

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()

OUTPUT SNAPSHOT IN TIME

在此输入图像描述

In my other answer, I said that you have to do some kind of cooperative scheduling, because tkinter isn't thread-safe. But that isn't quite true. tkinter is thread-safe, it just doesn't have any kind of dispatching mechanism to post events on the main loop from a background thread, so you have to add a queue or some other way to do it.

I'd definitely not recommend using threads here, but it's worth seeing how it would work.

Allen B. Taylor's clever mtTkinter library wraps all of the magic up for you. It doesn't work with Python 3, but I've ported it, and you can get it as mttkinter on GitHub. The module have any installer; you'll have to copy it into the same directory as planets.py . But then you can do this:

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()

turtle.py ends with two test demos. The second one ends with one turtle 'tri' chasing another 'turtle'. It does what abarnet suggests in the first post -- increments within a loop.

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

The turtledemo package has multiple examples to learn from. python -m turtledemo is the easy way to start the viewer. There are a couple of bugs that have been fixed for future releases.

Methods posted by other users work well. However, I did a similar model of a solar system with object oriented design and what I did was create a class called System where I create a system with a desired height and width and created a stepAll function which has a list of agents and advances all agents in that list one 'step':

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()

Then, I created a Planet class, and made the planets agents, and defined what their step would be in a Step function.

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

Then in my main() function I initialized all the planets with their respective values and said:

while True:
    space.stepAll()

This accomplishes your goal and also makes it easier to add another planet later with the specific parameters you want it to contain by just calling the Planet Class, instead of drawing a completely new planet and trying to individually move it along with the others.

Hope this helps someone!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM