Search code examples
pythonturtle-graphicsfractals

Python Turtle Wait for Keypress


I want to be to follow a turtle drawing step by step by making the user press a key before each movement.

I can do it with by asking for user input like so:

def wait():
    input('Press a key')

but this is a terrible solution as focus leaves the turtle window.

I am aware of screen.listen() and could set an event listener with screen.onkeypress(). - E.g my_screen.onkeypress('wait') but not sure how to implement this.

EDIT: I realise I should probably be more specific. I'm trying to trace thr recursion for the Koch curve. My code so far is below:

import turtle

def koch(t, order, size):
    """
       Make turtle t draw a Koch fractal of 'order' and 'size'.
       Leave the turtle facing the same direction.
    """
    wait_for_keypress()
    if order == 0:          # The base case is just a straight line
        t.forward(size)
    else:
        koch(t, order-1, size/3)   # Go 1/3 of the way
        t.left(60)
        koch(t, order-1, size/3)
        t.right(120)
        koch(t, order-1, size/3)
        t.left(60)
        koch(t, order-1, size/3)


def wait_for_keypress():
    input('Press a key') # There must be a better way

t = turtle.Turtle()
s = turtle.Screen()
s.listen()

koch(t, 3, 100)

turtle.done()

Solution

  • This sounds like a job for a recursive generator! We start the fractal code running but we use yield and yield from to make it stop each step along the way. We then have the screen click event do a next() on our generator:

    from turtle import Turtle, Screen
    
    def koch(t, order, size):
        """
        Make turtle t draw a Koch fractal of 'order' and 'size'.
        Leave the turtle facing the same direction.
        """
    
        if order == 0:
            t.forward(size)  # The base case is just a straight line
            yield
        else:
            yield from koch(t, order - 1, size / 3)  # Go 1/3 of the way
            t.left(60)
            yield from koch(t, order - 1, size / 3)
            t.right(120)
            yield from koch(t, order - 1, size / 3)
            t.left(60)
            yield from koch(t, order - 1, size / 3)
    
    def click_handler(x, y):
        screen.onclick(None)  # disable handler while in handler
    
        try:
            next(generator)
        except StopIteration:
            return
    
        screen.onclick(click_handler)
    
    screen = Screen()
    
    turtle = Turtle()
    
    generator = koch(turtle, 3, 200)
    
    screen.onclick(click_handler)
    
    screen.mainloop()
    

    Run the program. Each time you click the mouse on the window, you'll get an additional segment of your Koch fractal. We can also make this work with a key event, keeping the imports and koch() routine the same:

    ...
    
    def key_handler():
        screen.onkey(None, "Up")  # disable handler while in handler
    
        try:
            next(generator)
        except StopIteration:
            return
    
        screen.onkey(key_handler, "Up")
    
    screen = Screen()
    
    turtle = Turtle()
    
    generator = koch(turtle, 3, 200)
    
    screen.onkey(key_handler, "Up")
    
    screen.listen()
    
    screen.mainloop()
    

    Note that this responds to up arrow keys presses in the turtle graphics window, not key presses in the console.