pygame: detect Joystick disconnect, and wait for it to be reconnected - python

I'm using a pygame.joystick.Joystick object and want to be able to print a message asking the user to reconnect a usb joystick once it's been unplugged.
right now I have (roughly):
js = pygame.joystick.Joystick(0)
#... some game code and stuff
pygame.joystick.quit()
pygame.joystick.init()
while pygame.joystick.get_count() == 0:
print 'please reconnect joystick'
pygame.joystick.quit()
pygame.joystick.init()
js = pygame.joystick.Joystick(0)
js.init()
but it doesn't reconnect properly, idk what exactly it's doing, but it's definitely wrong. Any direction on this would be helpful

Here is pygame events for device add and remove.
joystick = pygame.joystick.Joystick(0)
while True:
event = pygame.event.wait()
if event.type == pygame.JOYDEVICEREMOVED:
joystick.quit()
elif event.type == pygame.JOYDEVICEADDED:
joystick.init()
https://www.pygame.org/docs/ref/joystick.html

Had to fire up the old xbox pad but I made a function that checks for disconnections and seems to work ok:
discon = False
def check_pad():
global discon
pygame.joystick.quit()
pygame.joystick.init()
joystick_count = pygame.joystick.get_count()
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
if not joystick_count:
if not discon:
print "reconnect you meat bag"
discon = True
clock.tick(20)
check_pad()
else:
discon = False
So if you run this function in your main loop it'll just keep running itself until it gets a joystick connection back. It works for the little test code I found:
http://programarcadegames.com/python_examples/show_file.php?file=joystick_calls.py
Also found:
http://demolishun.net/?p=21
Where I stole the idea from, he didn't have any code examples which was lame
And lastly, because you should always check the docs:
http://www.pygame.org/docs/ref/joystick.html

I managed to get mine working with Noelkd's suggestion, but I had a similar issue described by Ryan Haining
I had something like this at first, but it doesn't work because the it loses track of the gamepads actions with all the quiting and initing. This works initially to check if a controller is plugged in at all, but not to effectively check while running
I had this issue too. I think you are correct, calling quit too often doesn't give the pad enough time to re-initialise - at least on my computer. I found that if you limit the calls to every second, it works.
It can cause the player input to temporarily disconnect though, so any calls on a joystick won't work.
It's better to only run this code if you detect that there has been no input for a while (say 5 seconds or something). This way you won't quit while a user is actually using the device
import pygame
import time
INACTIVITY_RECONNECT_TIME = 5
RECONNECT_TIMEOUT = 1
class ControllerInput():
def __init__(self):
pygame.joystick.init()
self.lastTime = 0
self.lastActive = 0
def getButtons(self, joystickId):
joystick = pygame.joystick.Joystick(joystickId)
joystick.init()
buttons = {}
for i in range(joystick.get_numbuttons()):
buttons[i] = joystick.get_button(i)
if buttons[i]:
self.lastActive = time.time()
return buttons
def hasController(self):
now = time.time()
if now - self.lastActive > INACTIVITY_RECONNECT_TIME and now - self.lastTime > RECONNECT_TIMEOUT:
self.lastTime = now
pygame.joystick.quit()
pygame.joystick.init()
return pygame.joystick.get_count() > 0
Usage
# ... some constructor
controller = ControllerInput()
# ... game loop
if not controller.hasController():
# handle disconnect
print('reconnect')
return
buttons = controller.getButtons(0)
if buttons[0]:
# buttons[0] was pressed!

Related

Playing a Sound at Timed Intervals Within a Loop Whilst Having Other Code Continue to Execute

I need to play a sound within an if statement within a loop, however, I need it played at 1 second intervals whilst still allowing for the following code within said loop to execute during the time in which the sound is being played. I need this to be able to occur an infinite number of times. Below is the relevant component of my code.
import pygame as p
import math as m
walkSound = p.mixer.Sound('walk.mp3')
while True:
...
keysPressed = p.key.get_pressed()
if keysPressed[p.K_UP]:
walkSound.play()
# the other code I need to execute
tX += 2 * m.sin(startRot)
tY += 2 * m.cos(startRot)
This of course loops that sound at very short intervals, so I tried using multi-threading to solve the issue.
import pygame as p
import math as m
walkSound = p.mixer.Sound('walk.mp3')
while True:
...
keysPressed = p.key.get_pressed()
if keysPressed[p.K_UP]:
thread = T(target=walk_sound())
thread.start()
tX += 2 * m.sin(startRot)
tY += 2 * m.cos(startRot)
def walk_sound():
if keysPressed[p.K_UP]:
walkSound.play()
i.sleep(1)
Whilst this fixes the aforementioned problem, the rest of the code will not execute until after second has elapsed.
You can use a custom event and set a timer for it :
# Custom event
PLAY_SOUND = pg.USEREVENT
pg.time.set_timer(PLAY_SOUND, 1000)
Then in your event handler you will receive a PLAY_SOUND event at the interval you set :
for evt in pg.event.get():
if evt.type == pg.QUIT:
exit()
if evt.type == PLAY_SOUND:
play_sound()

Window freeze for single thread in while loop of Pygame

I am writing a short program to display cards in a round. I suspect that it is the length of the code which prevents the final 'OK' submit on P3 (the last player's submission) from executing properly: at which point the program sometimes will evaluate the winner and clear the round, but most of the time instead will freeze.
I have tried clock.tick(low fps), pygame.event.pump(), and pygame.event.clear(). Any leads would be much appreciated.
# Round loop begins. Finish until all hands are empty.
while not self.game.get_is_last_round():
player = self.game.get_player(self.game.get_player_turn())
hand = player.order_hand(player.get_hand(),
self.game.get_round_level(),
self.game.get_round_trump_suit())
ok_clicked_2 = False
pygame.event.pump()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.deal_running = False
self.is_running = False
pygame.display.quit()
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
play = player.get_play()
click = pygame.mouse.get_pressed(num_buttons=3)
pos = pygame.mouse.get_pos()
# Used DeMorgan's law to resolve error
ok_clicked_2 = (OK1_X < pos[0] < OK1_X + B_W) and (OK1_Y < pos[1] < OK1_Y + B_H) and click[0]
b1, card = self.check_hand(pos, player)
b2, play_card = self.check_play(pos, player)
if b1:
hand.remove(card)
play.append(card)
player.set_play(
player.order_hand(play, self.game.get_round_level(),
self.game.get_round_trump_suit()))
player.set_hand(
player.order_hand(hand, self.game.get_round_level(),
self.game.get_round_trump_suit()))
if b2:
play.remove(play_card)
hand.append(play_card)
player.set_play(
player.order_hand(play, self.game.get_round_level(),
self.game.get_round_trump_suit()))
player.set_hand(player.order_hand(hand, self.game.get_round_level(),
self.game.get_round_trump_suit()))
clock.tick(100)
surface.blit(background, (0, 0))
if len(self.game.get_player(0).get_hand()) == 25:
self.game.set_is_first_round(True)
else:
self.game.set_is_first_round(False)
if len(self.game.get_player(0).get_hand()) == 0:
self.game.set_is_last_round(True)
else:
self.game.set_is_last_round(False)
if self.game.get_play_in_turn() != NUM_PLAYERS:
pygame.event.pump()
clock.tick(100)
if len(hand) <= 1:
width = 0
x = (BG_WIDTH - CARD_WIDTH) // 2
elif len(hand) >= 8:
width = (BG_WIDTH - SIDE_W - CARD_WIDTH) // (len(hand) - 1)
x = BG_WIDTH // 2 - (CARD_WIDTH + (width * (len(hand) - 1))) // 2
else:
width = CARD_WIDTH
x = (BG_WIDTH - (CARD_WIDTH * len(hand))) // 2
surface.blit(background, (0, 0))
self.blit_backs()
self.blit_round()
self.show_ok()
self.show_hand(x, ROW3h, width, hand)
self.show_hand(CARD_POSITIONS[0][0], CARD_POSITIONS[0][1], SLIM_WIDTH, play)
if ok_clicked_2:
for card in play:
hand.append(card)
player.set_hand(player.order_hand(hand, self.game.get_round_level(),
self.game.get_round_trump_suit()))
# If player is first to start a round, he/she has a different validity check.
# (Sets the pattern for the cycle)
if player.get_begins_cycle():
valid = self.game.check_validity(True) # is_first
else:
valid = self.game.check_validity(False) # Is not first to play in the round
if not valid: # Clear holding if invalid
if (play == []) or (player.get_play() == []):
print("\nYou must make a play.\n")
else:
print("Invalid play. Try again.")
if not player.get_begins_cycle():
valid_plays = player.get_valid_plays(self.game.get_pattern(),
self.game.get_round_trump_suit())
print("Valid plays: \n")
for temp_play_idx in range(len(valid_plays)):
temp_play = valid_plays[temp_play_idx]
print("[", end='')
for temp_card_idx in range(len(temp_play)):
valid_plays[temp_play_idx][temp_card_idx].show_card("", '')
if temp_card_idx != len(temp_play) - 1:
print(", ", end='')
print("]")
# Clear the current player's selection and restore hand to its original content
cycle_order = self.game.get_cycle_order()
cycle = self.game.get_cycle()
for player_order in range(len(cycle_order)):
if player == cycle_order[player_order]:
cycle_order.remove(player)
cycle.pop()
self.game.set_cycle_order(cycle_order)
self.game.set_cycle(cycle)
else: # Valid play on submit
# Special case for HIGH_SUIT play, play lowest card if another player has greater
play = self.game.check_high_suit(play)
# If friend card played, establish and print teammates
# TODO: auto-designate friends if the last round
# has been reached (friends buried in treasure case)
# TODO: determine whether friend is "dead"
self.game.check_for_friends()
cycle = self.game.get_cycle()
cycle.append(play)
self.game.set_cycle(cycle)
cycle_order = self.game.get_cycle_order()
cycle_order.append(player)
self.game.set_cycle_order(cycle_order)
# self.clear_positions()
for card in play:
hand.remove(card)
player.set_hand(
player.order_hand(hand, self.game.get_round_level(),
self.game.get_round_trump_suit()))
self.game.next_player_turn()
self.game.set_play_in_turn(self.game.get_play_in_turn() + 1)
print(self.game.get_play_in_turn())
play = []
else:
self.game.set_play_in_turn(0)
# Distribute any points in the round to round winner
self.update_cycle_points()
for p in self.game.get_players():
for card in p.get_play():
discard = self.game.get_discard()
discard.append(card)
p.set_play([])
pygame.event.clear()
clock.tick(100)
pygame.display.update()
I think it's time for a code-cleanup, then your issue will go away (or you'll find it).
Currently the main loop is a big mix-up of event handling, screen-painting and game engine. Try to separate these parts out.
Move some of the in-loop processing out to functions - like the block after if ok_clicked_2:. It may help to make a data structure in which you store the game-state, then have the events change that game state. When it comes time to draw the game to the screen, the painting code can query the state, acting accordingly.
In terms of your actual lockup, if self.game.get_play_in_turn() == NUM_PLAYERS nothing is painted to the screen. Is this intentional? Add some print()s to your code so you can know the execution flow (or learn to use the python debugger).
I think the biggest step forward would be to move all the screen painting to one section of the main loop, something like:
# Render the screen
print( "Rendering Screen" )
surface.blit(background, (0, 0))
self.blit_backs()
self.blit_round()
# etc. for all other things, score, buttons, ...
clock.tick(60)
pygame.display.update()
You seem to be handling the events OK, so it would probably be better to remove the calls to pygame.event.pump() and pygame.event.clear(). You don't need these.
Following Kingsley's advice, I organized the code by function: rendering screen, game engine, and event handling. I would provide MRE as Random Davis suggests, but that would include 5 integrated files which would take too long to pare down.
It turns out that the problem lay in a piece of code which is called separately: "update_cycle_points()". Within, there is a while loop which does not contain an event handler. The solution was to change it to a for loop, which Pygame seems to process without error (does not freeze because it does not expect event handling there).
I also removed pygame.event.clear() and pump() functions without problems.

A bug disappears when I print something in pygame

I know the title is not very clear, but I don't know what else I can say.
I have a player attacking, and when he is done attacking, I start a timer for 1 second, so we have to wait one second before attacking again. It wasn't working (we could attack only once) and I didn't know why, so I added print(self.between_two_attacks())and everything worked fine, I could attack, wait one second and attack again.
Here is the program, I don't know if it is enough because I have no idea where the bug comes from.
def between_two_attacks(self):
if self.after_attack_max == 0:
self.after_attack_max = pg.time.get_ticks() + 1000
print("set timer")
else:
after_attack = pg.time.get_ticks()
print("start timer")
if after_attack >= self.after_attack_max:
print("can attack again")
self.can_attack = True
self.after_attack_max = 0
def draw(self, win):
print(self.between_two_attacks())
if (self.attackcount + 1) >= 5:
self.attackcount = 0
self.between_two_attacks()
self.action = STAND_UP
self.arme = LIGHTSABER_OUT
self.stops_attacking = True
self.can_attack = False
if self.action == ATTACKING:
win.blit...
Run = True
While Run:
for event in pg.event.get():
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE and player.can_attack == True:
player.action = ATTACKING
If anything isn't clear in this part of the program, just tell me and I'll try to explain. Thanks for your help :)
The method between_two_attacks has to be invoked, before the state of self.can_attack is retrieved. self.can_attack is set in between_two_attacks. If the method is not called, the self.can_attack will never become True.
When you do print(self.between_two_attacks()), the self.between_two_attacks() is called.
Furthermore, the method can be simplified:
self.can_attack has to be set if self.after_attack_max == 0 or if the current time is greater than self.after_attack_max.
If self.can_attack is set then compute the restart the timer. If it is not set then it has to be evaluated. Initially self.after_attack_max is 0. If the current time is greater than self.after_attack_max, attacks have to be allowed and the timer has to be started again:
def between_two_attacks(self):
current_time = pg.time.get_ticks()
if self.can_attack:
self.after_attack_max = current_time + 1000
elif current_time > self.after_attack_max:
self.can_attack = True
self.after_attack_max = current_time + 1000
Note, self.after_attack_max is only set in between_two_attacks, do not reset it anywhere else.
When you print self.between_two_attacks() the self function gets called and executed. Now, if you added the whole line it means that this function was not executed at this point before and now it is, so if you remove print and left only function at the same location you should get the same behaviour.
I do not know if I manage to explain my thought so here as a quick example:
x = 5
def change_x():
global x
x+=10
print(x)
print(change_x())
print(x)
if you remove print() you will get same result.

pygame multithread client socket GUI Blocking

I want to write a gomoku game with server and client. The terminal version works well, but pygame version just blocked and can't rend anything.
Here is the game execute function
First it start a socket connection
self._running = True in init, package get from server is like {"grid":GRID(strings with 0 and 1), "x":X, "y":Y, "player":LAST_PLAYER, "next_player":CURRENT_PLAYER, "gameover":IS_GAMEOVER}
In the the loop:
parse the package
check if gameover, if so show information about gameover and close the socket
if current player is me, call on_event and let user move( I suspect this step is wrong, so the parse package step blocked the main thread? But how should I fix this ) and then send the new move package to server
if current player is not me, then send a special package to server I am waiting(I know this is a bit stupid)
This is the loop like
while self._running:
data = self.client_thread.reply_q.get(True)
if data:
self.last_player = data["player"]
self.grid = self.grid_str_2_matrix(data["grid"])
self.lastPosition = [data["x"], data["y"]]
self.gomoku_board_init()
if data["gameover"] == -1:
if data["next_player"] != self.player:
self.client_thread.cmd_q.put(ClientCommand(ClientCommand.SEND, {"wait": True}))
print("waiting")
else:
for event in pygame.event.get():
self.on_event(event)
print("new move")
else:
print("game over")
self._running = False
if data["gameover"] == 0:
self.winner = 0
else:
self.winner = data["player"]
self.client_thread.cmd_q.put(ClientCommand(ClientCommand.CLOSE))
break
self.on_render()
self.on_cleanup()
and the on_event function be called in the middle to accepet user's next move
if data["gameover"] == -1:
if data["next_player"] != self.player:
...
else:
for event in pygame.event.get():
self.on_event(event)
code as this
def on_event(self, event):
print(event.type == pygame.MOUSEBUTTONUP)
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos()
r = (pos[0] - PADDING + WIDTH // 2) // (WIDTH + MARGIN)
c = (pos[1] - PADDING + WIDTH // 2) // (WIDTH + MARGIN)
print(r, c)
if 0 <= r < self.board_row and 0 <= c < self.board_column and self.grid[r][c] == 0:
self.grid[r][c] = self.player
data = {"grid":self.grid, "x":r, "y":c, "player":self.player}
self.client_thread.cmd_q.put(ClientCommand(ClientCommand.SEND, data))
What I've tried:
I added a print in on_event print(event.type == pygame.MOUSEBUTTONUP) and of course MOUSEBUTTONUP never happened(But I wonder why?)
So I decide just skip this using random input
code as follows
#for event in pygame.event.get():
# self.on_event(event)
x, y = self.random_position()
self.grid[x][y] = self.player
data = {"grid":self.grid, "x":x, "y":y, "player":self.player}
self.client_thread.cmd_q.put(ClientCommand(ClientCommand.SEND, data))
The result is package goes all right BUT GUI still blocking and even I add sleep in the while loop, it rended only when gameover
I am new in python multithread and also pygame with socket, I wrote a just pygame part it works good, so does terminal+socket.
You are reading from a reply queue with a param block=True. Which means that it is blocking whole while loop. And that on_render call is blocked as well by reply_q.get(True). So if you are not constantly feeding reply_q with messages then your screen is not going to be re-rendered. By the way, your events processing code is also gonna be blocked.
docs.python#Queue.get
Usually those kind of issues in Pygame and other GUI frameworks come from the special role the main thread has. Quoting from pygame's docs:
Pygame handles all its event messaging through an event queue. The routines in this module help you manage that event queue. The input queue is heavily dependent on the pygame.displaypygame module to control the display window and screen module. If the display has not been initialized and a video mode not set, the event queue may not work properly. The event subsystem should be called from the main thread. If you want to post events into the queue from other threads, please use the pygame.fasteventpygame module for interacting with events and queues module.

Pygame : force playing next song in queue even if actual song isn't finished? (aka "Next" button)

I want to make with pygame the same functionalities as a walkman : play, pause , queuing is ok.But how to do the previous/next buttons?
How can I ,with pygame, force the play of the next song which has been queued (and pass the ong which is actually playing?)
Have a list of song titles, and keep track of where you are in the list with a variable. Whether you are using pygame.mixer.music or pygame.mixer.Sound, when the "next" button is clicked, just have the variable change by one, and then stop the song, and have song the variable corresponds to play instead.
Code example for pygame.mixer.Sound:
#setup pygame above this
#load sounds
sound1 = pygame.mixer.Sound("soundone.ogg")
sound2 = pygame.mixer.Sound("soundtwo.ogg")
queue = [sound1, sound2] #note that the list holds the sounds, not strings
var = 0
sound1.play()
while 1:
if next(): #whatever the next button trigger is
queue[var].stop() # stop current song
if var == len(queue - 1): # if it's the last song
var = 0 # set the var to represent the first song
else:
var += 1 # else, next song
queue[var].play() # play the song var corresponds to
This was an example i got working for myself to wrap my head around the behavior of the program.
from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' # noqa This is used to hide the default pygame import print statement.
import pygame
import time
# Credit: https://forums.raspberrypi.com/viewtopic.php?t=102008
pygame.mixer.init()
pygame.display.init()
screen = pygame.display.set_mode((420, 240)) # Shows PyGame Window.
playlist = list()
playlist.append('/Music/Tom MacDonald - Angels (Explicit).mp3')
playlist.append('/Music/Falling In Reverse - Bad Girls Club (Explicit).mp3')
pygame.mixer.music.load(playlist.pop()) # Get the first track from the playlist
pygame.mixer.music.queue(playlist.pop()) # Queue the 2nd song
pygame.mixer.music.set_endevent(pygame.USEREVENT) # Setup the end track event
pygame.mixer.music.play() # Play the music
running = True
while running:
time.sleep(.1)
for event in pygame.event.get():
if event.type == pygame.USEREVENT: # A track has ended
if len(playlist) > 0: # If there are more tracks in the queue...
pygame.mixer.music.queue(playlist.pop()) # Queue the next one in the list
elif event.type == pygame.QUIT: # Create a way to exit the program.
running = False

Categories