'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")
Solution 1:[1]
I'm skeptical that you can cleanly solve this coordinating variables between onkeypress() and onkeyrelease() events. (Though I'd be pleased to be shown otherwise.) I offer an alternate approach where key presses simply post move requests and a timer applies those requests, whether individual or doubled up:
from turtle import Turtle, Screen
win = Screen()
flynn = Turtle('turtle')
def process_events():
events = tuple(sorted(key_events))
if events and events in key_event_handlers:
(key_event_handlers[events])()
key_events.clear()
win.ontimer(process_events, 200)
def Up():
key_events.add('UP')
def Down():
key_events.add('DOWN')
def Left():
key_events.add('LEFT')
def Right():
key_events.add('RIGHT')
def move_up():
flynn.setheading(90)
flynn.forward(25)
def move_down():
flynn.setheading(270)
flynn.forward(20)
def move_left():
flynn.setheading(180)
flynn.forward(20)
def move_right():
flynn.setheading(0)
flynn.forward(20)
def move_up_right():
flynn.setheading(45)
flynn.forward(20)
def move_down_right():
flynn.setheading(-45)
flynn.forward(20)
def move_up_left():
flynn.setheading(135)
flynn.forward(20)
def move_down_left():
flynn.setheading(225)
flynn.forward(20)
key_event_handlers = { \
('UP',): move_up, \
('DOWN',): move_down, \
('LEFT',): move_left, \
('RIGHT',): move_right, \
('RIGHT', 'UP'): move_up_right, \
('DOWN', 'RIGHT'): move_down_right, \
('LEFT', 'UP'): move_up_left, \
('DOWN', 'LEFT'): move_down_left, \
}
key_events = set()
win.onkey(Up, "Up")
win.onkey(Down, "Down")
win.onkey(Left, "Left")
win.onkey(Right, "Right")
win.listen()
process_events()
win.mainloop()
This might take some fine tuning depending on your particular needs. (E.g. how you handle more than two events in key_events).
Solution 2:[2]
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:
import turtle
def tick():
for action in keys_pressed:
actions[action]()
turtle.update()
win.ontimer(tick, frame_delay_ms)
t = turtle.Turtle()
turtle.tracer(0)
frame_delay_ms = 1000 // 30 # default for turtle is 10 in _CFG["delay"]
step_speed = 10
actions = dict(
l=lambda: t.left(step_speed),
r=lambda: t.right(step_speed),
u=lambda: t.forward(step_speed),
)
win = turtle.Screen()
keys_pressed = set()
win.onkeypress(lambda: keys_pressed.add("u"), "Up")
win.onkeypress(lambda: keys_pressed.add("l"), "Left")
win.onkeypress(lambda: keys_pressed.add("r"), "Right")
win.onkeyrelease(lambda: keys_pressed.remove("u"), "Up")
win.onkeyrelease(lambda: keys_pressed.remove("l"), "Left")
win.onkeyrelease(lambda: keys_pressed.remove("r"), "Right")
win.listen()
tick()
win.exitonclick()
Ultimately, though, if you want to go much further into realtime graphics and games, Pygame is better equipped.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | cdlane |
| Solution 2 |

