8
\$\begingroup\$

I've built a simple decorator that wraps a function in a general Pygame game loop. It also allows for the programmer to set the tick rate.

import pygame
import sys


pygame.init()


def game_loop(tick_rate=60):
    """
    This simple decorator wraps functions
    with a pygame game loop. Here's an
    example of usage:

        @game_loop(tick_rate=70)
        def main():
            ...
    """
    def game_loop_decorator(function):
        def wrapper(*args, **kwargs):
            clock = pygame.time.Clock()

            while True:
                clock.tick(tick_rate)
                function(*args, **kwargs)

                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit(0)

                pygame.display.flip()
        return wrapper
    return game_loop_decorator

Here's some example usage:

screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Test game!")


@game_loop(tick_rate=100)
def main():
    font = pygame.font.SysFont("monospace", 15)
    text = font.render("Look! A game loop!", 1, (255, 255, 255))
    screen.blit(text, (50, 50))


if __name__ == "__main__":
    main()
\$\endgroup\$
  • \$\begingroup\$ Do you really need the sys.exit(0) there? It's not a good practice to exit in the middle of execution. Can't you simply return? \$\endgroup\$ – janos Jul 15 '15 at 19:51
  • \$\begingroup\$ @janos I use sys.exit(0) because it seems to exit more cleanly when I'm using Python. \$\endgroup\$ – Ethan Bierlein Jul 15 '15 at 20:10
2
\$\begingroup\$

style


PEP8

  • There is one too many blank line after your imports.

  • It is debatable if there is too many blank lines in your decorator too. I personally think it's good without the blank lines.
    If you ignore the docstring then 17.6% of the function is blank lines.

    Use blank lines in functions, sparingly, to indicate logical sections.

You may feel the white space is needed, and so this section can be happily ignored.


PEP257

  • Multi-line docstrings consist of a summary line just like a one-line docstring, followed by a blank line, followed by a more elaborate description.

    Yours has this short summary with the elaborate description.
    I would be thrown off if your summery came up in my IDE,as I would be confused where this example is.

  • The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command ("Do this", "Return that"), not as a description; e.g. don't write "Returns the pathname ..."

    Your summary line is more of a description. I'm not good with words, but the summary could be changed to, "Decorate a function inside a pygame loop, at the specified tick-rate.".

  • The docstring for a function or method should summarize its behavior and document its arguments, return value(s), side effects, exceptions raised, and restrictions on when it can be called (all if applicable). Optional arguments should be indicated. It should be documented whether keyword arguments are part of the interface.

    Yours does not explain what tick_rate is. A good way to do this is also explained in the PEP.

    """Form a complex number.
    
    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)
    """

I was thrown off a bit to see the event handler below the main of the code. I think there is no difference, however it's probably best to keep to the norm.


Overall there's almost no problems with your style. Your docstrings could follow PEP257 more, as the biggest problem would be the summarized line at the beginning.


code


You should add the ability to pass a function to handle pygame events. This is as the programmer currently would have to use a hack to get user input, or not use the decorator. Both bad options.

for a rudimentary example of what the handler could look like:

def even_handler(event):
    if event.type == pygame.QUIT:
        raise SystemExit

You could however use pygame.quit();sys.exit(0), but then if you exit multiple times you would need to make a function to exit, and it would remove a little added abstraction.

you would then need to add it to the decorator. The nicest way would probably be:

def game_loop(event_function, tick_rate=60):

    # replace the current event handler.
    for event in pygame.event.get():
        try:
            event_function(event)
        except SystemExit:
            pygame.quit()
            sys.exit(0)

This has the downside of having to pass an event-handler, unless you pass a default one, like the one above. However it allows the programmer to handle user input, a must in most pygame applications.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.