views:

551

answers:

3

What is a good way to implement keyboard handling? In any language, where I write a keyboard-interactive program (such as a tetris game), I end up having some code that looks like this:

for event in pygame.event.get():
    if event.type == KEYDOWN:
        if False: pass          #make everything an elif
        elif rotating: pass
        elif event.key == K_q:
        elif event.key == K_e:
        elif event.key == K_LEFT:
            curpiece.shift(-1, 0)
            shadowpiece = curpiece.clone(); setupshadow(shadowpiece)
        elif event.key == K_RIGHT:
            curpiece.shift(1, 0)
            shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

(shortened). I don't like this, as this has to go in my main loop, and it messes with all parts of the program. This also makes it impossible to have a user config screen where they can change which key maps to which action. Is there a good pattern to do this using some form of function callbacks?

+15  A: 

You could create a dictionary where the keys are the input and the value is a function that handles the keypress:

def handle_quit():
  quit()

def handle_left():
    curpiece.shift(-1, 0)
    shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

def handle_right():
    curpiece.shift(1, 0)
    shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

def handle_pause():
    if not paused:
        paused = True

branch = {
  K_q: handle_quit
  K_e: handle_pause
  K_LEFT: handle_left
  K_RIGHT: handle_right
}

for event in pygame.event.get():
    if event.type == KEYDOWN:
        branch[event.key]()

Then changing the keys is a matter of modifying keys of the dictionary.

superjoe30
I am not certain, but I believe those function defs have to go *above* the assignment of branch, otherwise you'll just get a dictionary full of Nones. Good answer though, this is probably how I would do it, too.
luqui
i guess these will still have to be defined inside the main() loop - or perhaps take some argument to a GameState class
Claudiu
It might be better to maintain the inverse dictionary: `func2keys = { handle_quit: [K_q], handle_left: [K_LEFT, K_a], ...}`. And generate a new `branch` dictionary if the config is changed. `branch = dict((k, f) for f, keys in func2keys.items() for k in keys)`
J.F. Sebastian
+3  A: 

in addition to superjoe30's answer, you can use two levels of mapping (two dictionaries)

  • key => command string
  • command string => function

I think this would make it easier to allow user-defined mappings. i.e. so users can map their keys to "commands" rather than "the name of a function"

hasen j
You could also get a name of the command out of the function's docstring.
Paul Fisher
You can use objects with `__call__` attribute instead of functions.
J.F. Sebastian
+2  A: 

What I do nowadays is have some sort of input gathering class/function/thread which will check a list of predefined key->event bindings.

Something like this:

class InputHandler:
    def __init__ (self, eventDispatcher):
        self.keys = {}
        self.eventDispatcher = eventDispatcher
    def add_key_binding (self, key, event):
        self.keys.update((key, event,))
    def gather_input (self):
        for event in pygame.event.get():
            if event.type == KEYDOWN:
                event = self.keys.get(event.key, None)
                if not event is None:
                    self.eventDispatcher.dispatch(event)

....
inputHandler = InputHandler(EventDispatcher)
inputHandler.add_key_binding(K_q, "quit_event")
...
inputHandler.gather_input()
....

It's basically what superjoe30 is doing, except that instead of calling callbacks directly, I add another level of separation by using an event dispatching system, so that any code that cares about the keys being pressed simply listen for that event.

Also, keys can be easily bound to different events, which could be read from a config file or something and any key which is not bound to an event is simply ignored.

Dan
`if event is not None` looks better.
J.F. Sebastian