Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind several key presses together in turtle graphics?

I'm trying to make a connect-the-dot python game. I want the game to register 2 button presses. Example: if the user presses Up and Right arrow key, the turtle goes 45 degrees north east.

here is my code:

import turtle

flynn=turtle.Turtle()
win=turtle.Screen()
win.bgcolor("LightBlue")
flynn.pensize(7)
flynn.pencolor("lightBlue")

win.listen()

def Up():
    flynn.setheading(90)
    flynn.forward(25)

def Down():
    flynn.setheading(270)
    flynn.forward(20)

def Left():
    flynn.setheading(180)
    flynn.forward(20)

def Right():
    flynn.setheading(0)
    flynn.forward(20)

def upright():
    flynn.setheading(45)
    flynn.forward(20)

win.onkey(Up, "Up")
win.onkey(Down,"Down")
win.onkey(Left,"Left")
win.onkey(Right,"Right")

1 Answers

cdlane has an awesome idea here of using ontimer and a set of currently-pressed keys, but I thought I'd try to extend and refine it a bit.

The problem with a secondary loop with ontimer is that it seems to fight the main turtle loop, both in terms of computation and also in terms of thread safety/interleaving, where you can begin iterating the key pressed set and find that a handler has pulled a key out during iteration, raising an error.

The (seemingly poorly-named) tracer(0) function lets you disable turtle's loop so you can call it manually from within the hand-rolled ontimer loop using update(). This reduces some of the choppiness of the competing loops, although I imagine the timer resolution on rolling your own loop with repeated calls to ontimer is less precise than the built-in loop. But I haven't really looked at the source yet -- feel free to leave a comment if you have any insight.

Here's the proof of concept:

from turtle import Screen, Turtle


def tick():
    for action in list(keys_pressed):
        actions[action]()

    win.update()
    win.ontimer(tick, frame_delay_ms)


t = Turtle()
win = Screen()
win.tracer(0)
frame_delay_ms = 1000 // 30
step_speed = 10

actions = dict(
    Left=lambda: t.left(step_speed),
    Right=lambda: t.right(step_speed),
    Up=lambda: t.forward(step_speed),
)
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()

Ultimately, though, if you want to go much further into realtime graphics and games, Pygame is better equipped.

The approach here does not implement an optimal real-time event loop, just an improvement on the default turtle loop, sufficient for most casual projects.


cdlane has a few good posts on tracer: 1, 2, 3.

like image 109
ggorlen Avatar answered Oct 29 '25 00:10

ggorlen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!