Search code examples
pythonturtle-graphicspython-turtle

How to add parameters to Python Turtle screen.onTimer function


This seems like it should be simple but I'm new to python. I'm learning how to use Turtle so I decided to create pong. All the tutorials I found have you using screen.onkeypress but this creates a lag in movement which I couldn't find a fix for, so I instead set the movement direction inside of the onkeypress, and then have a recursive function that runs every 10 milliseconds like this:

def movePaddleB():
if (paddleBDirection != 0):
    y = paddleB.ycor()
    y += 1 * paddleBDirection * paddleSpeed
    paddleB.sety(y)
screen.ontimer(movePaddleB, 10)

movePaddleB()

This seems to work really well, but I want to make the function dynamic so that I don't have to create 2 functions, one for paddle A, and one for paddle B (see code below).

def movePaddle(paddle,direction):
if (direction != 0):
    y = paddle.ycor()
    y += 1 * direction * paddleSpeed
    paddle.sety(y)
#ADD DYNAMIC PARAMS TO RECURSIVE FUNCTION CALL
screen.ontimer(movePaddle(paddle,direction), 10)

movePaddle(paddleA,paddleADirection)
movePaddle(paddleB,paddleBDirection)

When I add parameters to the function called in screen.ontimer, I get this error: RecursionError: maximum recursion depth exceeded in comparison. I'm completely lost so any help would be great, even if I have to do this a completely different way.


Solution

  • First of all, your approach to handling the key retrigger is the right idea. Keys should only trigger movement during the game loop/update function, not immediately when the key is pressed.

    For handling parameter differences, you can add a lambda wrapper:

    def move_paddle(paddle, direction):
        if direction != 0:
            y = paddle.ycor()
            y += 1 * direction * paddle_speed
            paddle.sety(y)
    
        screen.ontimer(lambda: move_paddle(paddle, direction), 10)
    
    move_paddle(paddle_a, paddle_a_direction)
    move_paddle(paddle_b, paddle_b_direction)
    

    Note that this function isn't really recursive because the call stack is cleared by the time the next timer is triggered asynchronously. If it was recursive, CPython would crash after 1k frames or so as you saw when you called it yourself rather than passing a reference to the function for the turtle library to call on your behalf.

    Also, function and variable names should be snake_cased per PEP-8.

    That said, this design of having two ontimers running isn't great. I'd prefer to run one rendering loop for the whole app, then call whichever movement/update functions you want from it:

    def move_paddle(paddle, direction):
        if direction != 0:
            y = paddle.ycor()
            y += 1 * direction * paddle_speed
            paddle.sety(y)
    
    
    def tick():
        move_paddle(paddle_a, paddle_a_direction)
        move_paddle(paddle_b, paddle_b_direction)
        turtle.update() # necessary if you used `tracer(0)`
        screen.ontimer(tick, 1000 // 30)
    
    
    tick()
    turtle.exitonclick()
    

    See How to bind several key presses together in turtle graphics? for complete real-time turtle boilerplate that uses press and release handlers for smooth movement and the same tracer(0) + turtle.update() + screen.ontimer approach.