Nested if-statements, clean code, and being Pythonic, with controller/keyboard input - python

First the background: I use Python and PyGame. I've experimented with writing my own input/controller module, that allows users to remap controls in any way they like. This necessarily requires that my code be written to handle different types of input (for example, keyboard keys, controller buttons, and controller axes/hats/etc.)
For example, if the button "Right" is mapped to an arrow key, the code to handle GetPush("Right") or GetRelease("Right") would be somewhat different than if it were mapped to a control stick being pushed along a certain axis. To help take care of this, I made a helper function __GetButtonType() which -- as the name replies -- returns what type of button it is. For the code shown here, buttons are referred to as strings. The first letter of the string denotes the button type, and the rest denotes the button. For example k97 refers to the keyboard key with a value of 97. (the A key.)
Now for the issue at hand. My code does what I want it to do. But as I began cleaning it up, I came across one part that bothered me. I have the following segment of code which handles presses from the keyboard. (The DoSomething() functions replace entirely unrelated code, that I assume does not matter for the question at hand.)
for n in TheController.ControlMap:
if __GetButtonType(n)=="key":
if event.type == KEYUP:
if event.key == int(n[1:]): DoSomething()
if event.type == KEYDOWN:
if event.key == int(n[1:]): DoSomethingElse()
Something about this just looks a little off to me. It may be that the if event.key == int(n[1:]) condition is repeated. That seems wasteful. However, if event.key is not KEYUP or KEYDOWN, then the event will not have a key attribute (that is how pyGame works, that isn't of my design.) This means that the condition if (event.type == KEYDOWN and event.key == int(n[1:]) will throw an error whenever the event.type is something else.
However, I thought about it a bit, and realized that I CAN shorten the code by one line by structuring it in the following way:
for n in TheController.ControlMap:
if event.type == KEYUP:
if __GetButtonType(n)=="key" and event.key == int(n[1:]): DoSomething()
if event.type == KEYDOWN:
if __GetButtonType(n)=="key" and event.key == int(n[1:]): DoSomethingElse()
Both code snippets work. Both do the same thing. The first one looks a bit easier to read, but not by a whole lot. The second one uses fewer lines. My question is this:
Which style is "better"? Does one perform faster than the other one would by a non-negligible amount? Or is one just considered "poor practice?" Maybe they both are, in fact. I am still relatively new to programming.
Thank you,
Chris

Both of your statements have one same feature which would make people cringe. Equivalent if statements are repeated twice. This is no good because it clatters your code and is absolutely unnecessary:
if event.type in [KEYUP, KEYDOWN] and __GetButtonType(n)=="key" and event.key == int(n[1:]):
if event.type == KEYUP:
DoSomething()
elif event.type == KEYDOWN:
DoSomethingElse()
Here the first condition makes sure that you don't get an error on event.key.
Also use elif because the options are mutually exclusive. Simple else will also suffice, but I'd leave elif for the purpose of readability. Also you can hide this into DoSomething(event.type).
Another option which could look ugly or pretty depending on who you ask is collapse it all into a flat if-else:
if event.type in [KEYUP, KEYDOWN] and __GetButtonType(n)=="key" and event.key == int(n[1:]):
(DoSomething if event.type == KEYUP else DoSomethingElse)()
But that's getting a bit weird... Still, it works.
Performance-wise, there is absolutely nothing to worry about here. Optimization is a whole other topic and the first thing to optimize would be program design / algorithm rather than local low-importance commands.

on the lines of what 'sashkello' mentioned, I generally have the below block of code common in my pygame programs.
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT :
do_something()
elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
do_something_else()

Related

Pygame's pygame.event.get()'s key value is a ridiculous number as of today, why?

My program used to run the following bit of code and it worked perfectly when i pressed left shift. But as of today it suddenly stopped working, no code changed, no reinstalls, no updates since it was working and stopped working.
#running on windows 10
while True:
for event in pygame.event.get():
if event.type == KEYDOWN:
print(event)
if event.key == 304:#left shift
print("worked")
the output of print(event) used to be :
<Event(768-KeyDown {'unicode': '', 'key': 304, 'mod': 4096,
'scancode': 79, 'window': None})> now it is : <Event(768-KeyDown
{'unicode': '', 'key': 1073742049, 'mod': 4096, 'scancode': 79,
'window': None})>
I know how to fix it, or do it in a different way, but does someone know the reason the 'key' is suddenly a ridiculous number? Is there something wrong with my computer that the key values it's giving is messed up?
It would help if you didn`t write the key codes on your own. It will be better if you use the predefined pygame key, for example:
while True:
for event in pygame.event.get():
if event.type == KEYDOWN:
print(event)
if event.key == pygame.K_LSHIFT:
print("worked")
Here is a list of pygame`s predefined keys.
pygame documentation - pygame.key
Use pygame.K_LEFT if you want to detect if LEFT is pressed.
Let me refer to the documentation of the pygame.key module:
Portability note: The integers for key constants differ between pygame 1 and 2. Always use key constants (K_a) rather than integers directly (97) so that your key handling code works well on both pygame 1 and pygame 2.
A user friendly name of a key can be get by pygame.key.name():
Get the descriptive name of the button from a keyboard button id constant.
The keyboard events KEYDOWN and KEYUP (see pygame.event module) create a pygame.event.Event object with additional attributes. The key that was pressed can be obtained from the key attribute. The unicode attribute provides the Unicode representation of the keyboard input.

pygame - return key pressed before event.key handler results in immediate processing of event.key block

In my program made with pygame, I have an intro sequence that blits a total of 3 text boxes to the screen timed apart from each other, so one appears, then the next, then the one after that. I have no event polling during this time, as I want the whole sequence to play for it's total of probably around 10 seconds. However, after this sequence plays, I have a 'Continue' text that appears in the bottom right to show the user to press Return to continue to the next slide. This is where I then have my event handler like so:
exited = False
while not exited:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exited = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
exited = True
However, if the user hits Return at any time in this sequence, once the control flow hits the event handler, it just exits immediately through the pygame.KEYDOWN branch. How should I go about fixing this?
I have solved this by actually just adding
for event in pygame.event.get():
pass
before my next pygame.event.get() loop. The first one consumes the other events that happened in the sequence, so that the second loop will only handle input given after the sequence plays.

How to determine if a key is pressed in python using pygame

Is there a really simple way of knowing if the key SPACE is currently DOWN. I'm making a basic snake game and I don't know to use pygame and I can't find it anywhere.
I imagine it like something along these lines:
if K_SPACE = DOWN:
turtle.turn(90)
thank you for any help
In your game loop:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
turtle.turn(90)

Keydown function not working

I have just downloaded pygame 1.9.2 for python 3.3 and the keydown function is not working. The IDLE shell keeps telling me the same error:
NameError: name 'KEYDOWN' is not defined
How do I solve this problem? I am kinda new to programming so could you please explain this to me.
NOTE: Sorry for deleting my previous answer but Stack Overflow just randomly posted my answer before I was done.
Well, there could be several things wrong. You could be using pygame.key.keydown(), which is wrong, and you should be using pygame.key.get_pressed().
But what I suspect is wrong with your code is that you're not using pygame.KEYDOWN properly.
If you're trying to do something while a key is being held down, use pygame.key.get_pressed(), and do something like this:
key = pygame.key.get_pressed()
if key[pygame.K_WHATEVER_KEY_YOU_WANT]:
# do code
and just repeat that for every key you want to check.
If you're just trying to check if a key is being pressed, use pygame.KEYDOWN with an if statement, then check if the key you want is being pressed in a nested if statement under the first if statement. Like so:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_WHATEVER_KEY_YOU_WANT:
#do code
I'm assuming you have an event variable your using to iterate over pygame.event.get() function that 'gets' events. If not then here is the code above in a game/window loop:
running = True # variable to control while loop
while running: # while the variable running is equal to true
for event in pygame.event.get(): # use the variable 'event' to iterate over user events
if event.type == pygame.QUIT: # test if the user is trying to close the window
running = False # break the loop if the user is trying to close the window
pygame.quit() # de-initialize pygame module
quit() # quit() the IDE shell
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_WHATEVER_KEY_YOU_WANT:
#do code
This is basically how you use pygame.KEYDOWN. It's probably not the only way however. I recommend (since you're new to programming) to read the pygame docs on their official website for more info about checking key input in pygame. And read some of the answers to this question.

How to delay pygame.key.get_pressed()?

I haven't been able to find a straightforward answer.
key.set_repeat() works for KEYDOWN events, but not for key.get_pressed():
import sys, pygame
from pygame.locals import *
pygame.init()
Clock = pygame.time.Clock()
pygame.display.set_mode((200, 100))
pygame.key.set_repeat(1, 500)
while True:
if pygame.key.get_pressed()[K_UP]:
print('up!')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_DOWN:
print('down!')
Clock.tick(30)
Even the slightest tap on UP explodes into at least a few up!s, only down!s are delayed. I want to use key.get_pressed() since it conveniently handles multiple inputs. Do I have to work around it with some sorta tick counter? Alternatively, is there a way to handle multiple KEYDOWN events?
Just don't mess around with key.set_repeat() if you don't have to.
Simply use key.get_pressed() to check for keys that are pressed and where keeping that key pressed down has a meaning to you, e.g. something like move left while K_LEFT is pressed.
Use event.get() / KEYDOWN when you're interessed in a single key press, e.g. something like pressing K_P pauses the game.
If you mess around with key.set_repeat(), e.g. set the delay to low, you won't be able to recognize a single key stroke, since every key stroke will create multiple KEYDOWN events.
To handle multiple KEYDOWN events, just check for the relevant events. Every key stroke will generate a KEYDOWN event, so you can simple check e.g.:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_DOWN:
print('down!')
elif event.key == K_UP:
print('up!')
That will of course print up!/down! multiple times if the user keeps pressing the keys.
The golden rule is: If you're interessted in the KEYDOWN and the KEYUP event of a single key, better use key.get_pressed() instead.
Even the slightest tap on UP explodes into at least a few up!s, only down!s are delayed
That's because the event system and key.get_pressed() are totally different. If you get pressing K_UP over multiple frames, for each frame 'up!' is printed because the key is pressed down in every frame. key.get_pressed() simple returns which keys are pressed at the moment you call this function. In other words: you can't "delay" it.

Categories