2

I am currently making a pong game in python:

enter image description here

However, say the ball is coming at pink, and the yellow guy decides to spam his w and s keys. Then his paddle will start moving (fine), but then the pink one will stop (not fine).

Is it possible for python to listen to two event keys simultaneously?

Here is the code:

import pygame, sys
from pygame.locals import *
pygame.init()

DISPLAYSURF = pygame.display.set_mode((1439, 790))
YELLOW = (255, 255, 0)
PINK = (255, 0, 255)
BLUE = (120, 214, 208)
y1, y2 = (0, 0)
circx, circy = (1439//2, 790//2)
diffx, diffy = (15, 15)
pygame.key.set_repeat(1, 10)
while True:
        if (0 <= circy <= 780) == False:
                diffy*=-1
        if circx <= 60 and y1-10 <= circy <= y1+75:
                diffx*=-1
        if circx >= 1439-60 and y2-10 <= circy <= y2+75:
                diffx*=-1
        if (0 <= circx <= 1439) == False:
                circx, circy = (720, 395)
        DISPLAYSURF.fill((0, 0, 0))
        pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
        pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
        pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
        circx+=diffx
        circy+=diffy
        try:
                for event in pygame.event.get():
                        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
                                pygame.quit()
                                sys.exit()
                        if event.key == pygame.K_w:
                                y1-=15
                        if event.key == pygame.K_s:
                                y1+=15
                        if event.key == pygame.K_UP:
                                y2-=15
                        if event.key == pygame.K_DOWN:
                                y2+=15
        except AttributeError:
                pass
        pygame.display.flip()

How do I make it independently handle each key press if there are 2 simultaneous ones?

sloth
  • 99,095
  • 21
  • 171
  • 219
ZenOfPython
  • 891
  • 6
  • 15
  • I do believe you'll want to look into [threading](https://docs.python.org/2/library/threading.html) – wnnmaw Jun 30 '14 at 14:43
  • 1
    Are you sure this is a Python problem and not a keyboard problem? Your code should be handling all 4 events independently each game loop, but it won't do anything if your keyboard can only send 2 simultaneous key presses. – Silas Ray Jun 30 '14 at 14:46
  • Exactly. How do I make it independently handle each key press if there are 2 simultaneous ones? – ZenOfPython Jun 30 '14 at 14:47

5 Answers5

1

I agree with SilasRay that this is almost certainly NOT an issue with thread blocking, but if it is: this should fix it (therefore if it doesn't, it's an issue with your keyboard or the SDL layer that Silas mentioned). It's kind of important to be making objects out of your Paddles anyway, so think about doing that part anyhow. The thread section is in paddlelisten().

import threading

class Paddle(object):
    def __init__(self, posx, color):
        self.posx = posx
        self.posy = 0
        self.color = color
    @property
    def pos():
        return (self.posx, self.posy, 10, 75)

def main():
    # the logic for the program
    p1_paddle = Paddle(50, YELLOW)
    p2_paddle = Paddle(1439-60, PINK)
    def paddlelisten(paddle, upkey, downkey):
        if event.key == upkey:
            paddle.posy -= 15
        if event.key == pygame.downkey:
            paddle.posy += 15
    t1 = threading.Thread(target=lambda: paddlelisten(p1_paddle, pygame.K_w, pygame.K_s))
    t2 = threading.Thread(target=lambda: paddlelisten(p2_paddle, pygame.K_UP, pygame.K_DOWN))
    for t in [t1, t2]:
        t.daemon = True
        t.start()
    while True:
        # game loop
        pygame.draw.rect(DISPLAYSURF, p1_paddle.color, p1_paddle.pos)
        pygame.draw.rect(DISPLAYSURF, p2_paddle.color, p2_paddle.pos)
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • 1
    +1 for the object suggestion. A good step on the way to a more efficient rendering system would be to start using Pygame's sprites and sprite groups to do selective blitting to the background instead of re-rending the entire frame from scratch each time. – Silas Ray Jun 30 '14 at 15:51
1

After some research, I think this is actually an SDL event queue overflow problem. SDL is a C event library that PyGame's event system is built on top of. From what I've read, the event queue has a 128 event limit, after which new events being added are simply dropped until the queue is flushed.

Your rendering logic isn't very efficient, and you've bound your input poll rate to your rendering logic (you re-render the entire screen, paddles, and ball every game loop, and then you poll the event queue, so you can't empty the event queue any faster than you can render an entire frame from scratch). I think what you need to do here is improve your render logic (look at how you can erase/redraw just portions of a sprite instead of flushing the entire buffer) and unbind event polling from rendering by putting rendering in a separate thread.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • For OP: generally you'd run two different threads, one named something e.g. `update` and one named something e.g. `draw`, the first of which handles all the data processing (event bindings etc) and the second of which handles drawing to the screen. Limit the `update` function to only run after a set amount of time (60/sec is usual) so that a faster computer doesn't get an advantage. – Adam Smith Jun 30 '14 at 15:43
  • 1
    @AdamSmith Or just use a triple buffered display system rendered in a separate thread, and only have the main game loop flip to a new frame once one of the 2 back buffers has completed rendering. – Silas Ray Jun 30 '14 at 15:49
0

I think you need to use threads otherwise the program is always sequencial.

Check this example:

Make 2 functions run at the same time

Try to put the while True in each thread's function so that both responde to commands.

Community
  • 1
  • 1
Pimenta
  • 1,029
  • 2
  • 13
  • 32
  • Could you go a bit more in depth? – ZenOfPython Jun 30 '14 at 14:49
  • The idea is for you to have a two threads, one for handling the player1 key events and another for the player2.
    Your while true loop will be on the main program, with the first if clause, so that it handles the exit keys.
    The player1 while true loop, definded inside a function (replacing the 'Working' example) would have the S and W keys.
    The player2 while true loop, definded inside a function (replacing the 'Working' example) would have the UP and DOWN keys.
    – Pimenta Jun 30 '14 at 15:03
  • 1
    I'm not sure it's as simple as this. @Pimenta, have you actually worked in PyGame before? I've been reading around, and it looks like the problem arises from the SDL event system underlying PyGame. SDL does not appear to play nicely with being pumped/flushed across threads. Rather, I think the OP may be better served by rendering his GUI in a separate thread, then only having the buffer flipped when the next frame is ready, so that the keyboard sampling can run at a higher rate (and thus sidestep the SDL buffer size limit) than the GUI rendering. – Silas Ray Jun 30 '14 at 15:21
  • Hi there. @SilasRay I haven´t tried doing this example. I was trying to solve the multiple keys issue. Thanks for your indications. Here is another solved question that uses PyGame [link](http://stackoverflow.com/questions/576634/pygame-not-receiving-events-when-3-keys-are-pressed-at-the-same-time) When I have the time I will try this example myself and check how my computer/keyboard behaves. – Pimenta Jun 30 '14 at 15:33
0

As I said above, I used pygame.key.set_repeat(1, 10) only so that even while the key was held down the event would occur.

I discovered an alternate way to do the same, using pygame.key.get_pressed()[pygame.DESIRED_KEY].

pygame.key.get_pressed() returns a tuple such as the following:

(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

When a specific key is pressed down, then the 0 turns into a 1, enabling us to use if pygame.key.get_pressed()[pygame.DESIRED_KEY].

Here is your edited code:

import pygame, sys
from pygame.locals import *
pygame.init()

DISPLAYSURF = pygame.display.set_mode((1439, 790))
YELLOW = (255, 255, 0)
PINK = (255, 0, 255)
BLUE = (120, 214, 208)
y1, y2 = (0, 0)
circx, circy = (1439//2, 790//2)
diffx, diffy = (15, 15)
pygame.key.set_repeat(1, 10)
while True:
        if pygame.key.get_pressed()[pygame.K_UP]:
                y1-=15
        if pygame.key.get_pressed()[pygame.K_DOWN]:
                y1+=15
        if pygame.key.get_pressed()[pygame.K_w]:
                y2-=15
        if pygame.key.get_pressed()[pygame.K_s]:
                y2+=15
        if (0 <= circy <= 780) == False:
                diffy*=-1
        if circx <= 60 and y1-10 <= circy <= y1+75:
                diffx*=-1
        if circx >= 1439-60 and y2-10 <= circy <= y2+75:
                diffx*=-1
        if (0 <= circx <= 1439) == False:
                circx, circy = (720, 395)
        DISPLAYSURF.fill((0, 0, 0))
        pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
        pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
        pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
        circx+=diffx
        circy+=diffy
        try:
                for event in pygame.event.get():
                        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
                                pygame.quit()
                                sys.exit()
        except AttributeError:
                pass
        pygame.display.flip()
ZenOfPython
  • 891
  • 6
  • 15
0

In a game that I'm making, where the character can move in all directions, I made it so that when you press a key, rather than directly changing the character's x and y, it simply makes one of the variables moveLeft, moveRight, moveUp, or moveDown (depending on which key is pressed) True. Then you have to make a seperate thing that turns it off when the key is lifted. Later in the loop it checks whether each variable is True, and moves the character based on which keys are pressed. Plus, I don't understand why the Try loop is there.

You could change the main loop to this:

p1moveup = False
p1movedown = False
p2moveup = False
p2movedown = False
while True:
    if (0 <= circy <= 780) == False:
            diffy*=-1
    if circx <= 60 and y1-10 <= circy <= y1+75:
            diffx*=-1
    if circx >= 1439-60 and y2-10 <= circy <= y2+75:
            diffx*=-1
    if (0 <= circx <= 1439) == False:
            circx, circy = (720, 395)
    DISPLAYSURF.fill((0, 0, 0))
    pygame.draw.rect(DISPLAYSURF, YELLOW, (50, y1, 10, 75))
    pygame.draw.rect(DISPLAYSURF, PINK, (1439-60, y2, 10, 75))
    pygame.draw.circle(DISPLAYSURF, BLUE, (circx, circy), 10)
    circx+=diffx
    circy+=diffy
    for event in pygame.event.get():
        if event.type == QUIT or event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w:
                p1moveup = True
            if event.key == pygame.K_s:
                p1movedown = True
            if event.key == pygame.K_UP:
                p2moveup = True
            if event.key == pygame.K_DOWN:
                p2movedown = True
        if event.type === pygame.KEYUP:
            if event.key == pygame.K_w:
                p1moveup = False
            if event.key == pygame.K_s:
                p1movedown = False
            if event.key == pygame.K_UP:
                p2moveup = False
            if event.key == pygame.K_DOWN:
                p2movedown = False
    if p1moveup == True:
        y1-=15
    if p1movedown == True:
        y1+=15
    if p2moveup == True:
        y2-=15
    if p2movedown == True:
        y2+=15
    pygame.display.flip()
Lighform
  • 13
  • 5