How to bind several key presses together in turtle graphics?
Asked Answered
A

3

4

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")
Ahumada answered 19/12, 2017 at 3:27 Comment(1)
you could use onkey and onkeyrelease to set variables key_up = True and key_right = Trueand if you press rigth and you already have key_up == True then you have two keys combination. The same: if you press up and you already have key_right == True then you also have your combination.And remember to use onkeyrelease to set variables FalseCurable
S
2

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()

enter image description here

This might take some fine tuning depending on your particular needs. (E.g. how you handle more than two events in key_events).

Saloon answered 19/12, 2017 at 19:41 Comment(2)
oh I see, thanks for revising the code. But i'm a novice at coding. What exactly does _key_events mean. Is there python documentation for it? thanksAhumada
@AjwadKabir, the key_events variable is simply a Python set. I used a set so that muliple UP, etc., events would collapse into a single one. There are no additional Python features in use here except what's in the turtle library. The interesting additional piece is the ontimer() event from the turtle library which allows a function to fire off in the future.Saloon
M
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:

from turtle import Screen, Turtle


def tick():
    for action in 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.


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

Missis answered 4/2, 2022 at 0:2 Comment(0)
D
0

Sometimes pointing to an alternative approach and/or pointing out the issues with the targeted one is a better answer than showing ways how to accomplish what was requested.

The modern keyboard devices allow pressing multiple keys at the same time without getting damaged, but it is generally not the best idea to use simultaneous key presses of keys not designed to be used for this purpose. It may happen that you risk some damage and it may happen that not all the keyboard hardware out there can handle this properly or at all.

There are keys on a keyboard which purpose is to be simultaneously pressed with other ones like Ctrl, Alt, Shift, so using them should be the preferred way to go.

In other words I suggest to drop the idea of simultaneous arrow key presses in favor of choosing for example the block

 Q W E
  A S D
    Z X C

on the keyboard for the direction control or use the key combinations Ctrl+Up Alt+Up and Ctrl+Down Alt+Down for the 45 degree directions.

Another alternative approach would be to use Up and Down for moving the Turtle forward and backward and Left/Right to change its heading by 45 degrees. This would allow, like using the QWEASDZXC block, to handle only plain keypresses in the Turtle application.

By the way: the idea of simultaneous key presses of two arrow keys at the same time is based on mistaken understanding of what simultaneous actually mean. There is no such thing as a perfect simultaneous press of two keys at the same time what makes it impossible to cleanly decide when very fast key presses of two different arrow keys mean two different actions and when they should be considered to be a simultaneous pressing.

Dustheap answered 6/11, 2023 at 0:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.