pythonmultithreadingturtle-graphicspython-turtleonkeypress

Using 2 onkeypress-es (with a thread/process) in Python Turtle


Basically, I'm recreating the game Pong in Turtle. I'm aware there's 1000 better languages, modules (I think it's called interfaces), basically ways to do it but I wanted to take on this challenge. The first problem I got is that when 1 stick moves up or down, the second can't and vice versa. My first thought was to fix it with a thread/process, however since I am pretty new to programming altogether I couldn't do it.

import turtle
from multiprocessing import Process
ekran = turtle.Screen()
ekran2 = turtle.Screen()
def go_up1():
    if palica1.ycor() <= 525 / 3 -5:
        palica1.goto(palica1.xcor(), palica1.ycor() + 5)


def go_down1():
    if palica1.ycor() >= -525 / 3 + 5:
        palica1.goto(palica1.xcor(), palica1.ycor() - 5)
        

def go_up2():
    if palica2.ycor() <= 525 / 3 + -4:
        palica2.goto(palica2.xcor(), palica2.ycor() + 5)
        


def go_down2():
    if palica2.ycor() >= -525 / 3 + 4:
        palica2.goto(palica2.xcor(), palica2.ycor() - 5)

        
def prva():
    ekran.onkeypress(go_up1, "w")
    ekran.onkeypress(go_down1, "s")
    ekran.listen()

    
def druga():
    ekran2.onkeypress(go_up2, "o")
    ekran2.onkeypress(go_down2,"l")
    ekran2.listen()

    
palica1 = turtle.Turtle()
palica2 = turtle.Turtle()
a = Process(target = prva)
b = Process(target = druga)
a.start()
b.start()
turtle.mainloop()
#this code is partially in my language so don't mind the weird names

I tried the same thing using threads instead of processes and got a not working result but at least there wasn't any errors lol. p.s. this is a very shortened and simplified version of everything I did already

Thanks for reading!


Solution

  • There's a fundamental flaw to your setup, which is that turtle is handling the event loop, moving everything synchronously with a delay. This means there's no obvious way to bind several key presses such that they fire simultaneously. Instead, disable turtle's event loop and take control of when frames display, so you can reposition turtles arbitrarily. I'd avoid threading entirely in turtle in favor of ontimer, tracer(0) and update().

    This answer shows the basic approach for setting up handlers, disabling tracer and using ontimer and update. Read it to understand the fundamental setup for the code below. The only changes here are directing the key handlers to control two turtles instead of one, and making those turtles look and behave like pong paddles.

    from turtle import Screen, Turtle
    
    
    class Paddle:
        def __init__(self, x):
            self.t = t = Turtle()
            t.shape("square")
            t.shapesize(1, 4)
            t.penup()
            t.goto(x, 0)
            t.left(90)
    
        def up(self):
            self.t.forward(step_speed)
    
        def down(self):
            self.t.backward(step_speed)
            
    
    def tick():
        for action in keys_pressed:
            actions[action]()
    
        win.update()
        win.ontimer(tick, frame_delay_ms)
    
    
    win = Screen()
    win.tracer(0)
    frame_delay_ms = 1000 // 30
    step_speed = 10
    right_paddle = Paddle(200)
    left_paddle = Paddle(-200)
    
    actions = dict(
        Up=right_paddle.up,
        Down=right_paddle.down,
        w=left_paddle.up,
        s=left_paddle.down,
    )
    keys_pressed = set()
    
    def bind(key):
        win.onkeypress(lambda: keys_pressed.add(key), key)
        win.onkeyrelease(lambda: keys_pressed.remove(key), key)
    
    for key in actions:
        bind(key)
    
    win.listen()
    tick()
    win.exitonclick()