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.
Related
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.
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.
I am trying to recreate Pong in pygame and have tried to change the color of the net to red or green, based on who scores. I am able to keep it red or green after someone scores, until a different person scores, however, I want to change the net color back to black after 3 seconds. I tried using time.sleep(3) but whenever I did it, the net will stay as black. `
elif pong.hitedge_right:
game_net.color = (255,0,0)
time.sleep(3)
scoreboard.sc1 +=1
print(scoreboard.sc1)
pong.centerx = int(screensize[0] * 0.5)
pong.centery = int(screensize[1] * 0.5)
scoreboard.text = scoreboard.font.render('{0} {1}'.formatscoreboard.sc1,scoreboard.sc2), True, (255, 255, 255))
pong.direction = [random.choice(directions),random.choice(directions2)]
pong.speedx = 2
pong.speedy = 3
pong.hitedge_right = False
running+=1
game_net.color=(0,0,0)
Ideally, it should turn red for 3 seconds, then update the scoreboard and restart the ball, however, instead, the entire thing pauses and it skips straight to changing the net color to black. I believe there is a better way of doing this, or maybe I am using time.sleep totally wrong, but I have no idea how to fix this.
You can't use sleep() in PyGame (or any GUI framework) because it stops mainloop which updates other elements.
You have to remember current time in variable and later in loop compare it with current time to see if 3 seconds left. Or you have to create own EVENT which will be fired after 3 second - and you have to check this event in for event.
It may need more changes in code so I can show only how it can look like
Using time/ticks
# create before mainloop with default value
update_later = None
elif pong.hitedge_right:
game_net.color = (255,0,0)
update_later = pygame.time.get_ticks() + 3000 # 3000ms = 3s
# somewhere in loop
if update_later is not None and pygame.time.get_ticks() >= update_later:
# turn it off
update_later = None
scoreboard.sc1 +=1
print(scoreboard.sc1)
# ... rest ...
Using events
# create before mainloop with default value
UPDATE_LATER = pygame.USEREVENT + 1
elif pong.hitedge_right:
game_net.color = (255,0,0)
pygame.time.set_timer(UPDATE_LATER, 3000) # 3000ms = 3s
# inside `for `event` loop
if event.type == UPDATE_LATER:
# turn it off
pygame.time.set_timer(UPDATE_LATER, 0)
scoreboard.sc1 +=1
print(scoreboard.sc1)
# ... rest ...
I am currently working on a game where I wish to give the player an option of four characters to play. Here is my current code to do this:
running = 1
charactersChoice = ['char.png', 'char2.png', 'char3.png', 'char4.png']
choice = ''
while choice == '':
screen.fill((47, 79, 79))
screen.blit(pygame.image.load(charactersChoice[0]), (100,100))
screen.blit(pygame.image.load(charactersChoice[1]), (700,100))
screen.blit(pygame.image.load(charactersChoice[2]), (100,600))
screen.blit(pygame.image.load(charactersChoice[3]), (700,600))
keys = pygame.key.get_pressed()
#Choose character
if keys[pygame.K_1]:
choice = charactersChoice[0]
if keys[pygame.K_2]:
choice = charactersChoice[1]
if keys[pygame.K_3]:
choice = charactersChoice[2]
if keys[pygame.K_4]:
choice = charactersChoice[3]
pygame.display.flip()
while running == 1:
#rest of code for game here
As you can see, I blit the four different character profiles onto the screen and then check if the player has pressed the keys 1-4 to select their option. After selecting their option, it should move onto the main loop. Currently, if I press the key '1' while the code is running, it will not change the variable choice to what is intended.
Hope I have explained my problem well enough.
You have to call pygame.event.pump() at least once before using pygame.key.get_pressed() so that pygame can check what keys have been pressed.
Below is a small piece of my program. Currently, the program takes the user's input and converts it into a binary number in the Python Shell. I am trying to use that input so that I can graphically display the binary number. At the moment, I am unable to get anything to appear in the pygame screen. It is just white, no circles, no text. I am not sure why it is not working. I was advised by my teacher to turn this piece of code into a procedure, see if I have any luck, then get back to him. I was hoping somebody could pick out what is wrong with my procedure and either point it out to me or correct it. Any help would be much appreciated. I apologize if my formatting for the question is not spectacular, this is my first post.
from pygame import*
font.init()
comicFont=font.SysFont("ComicSansMS",12)
screen = display.set_mode((500,500))
binaryWord = str(100101)
binaryDigits = len(binaryWord)
binaryBlit = range(0,10)
binaryGraphicX = 0
color = (0,0,0)
color2 = (125,125,125)
pos = (binaryGraphicX,200)
radius = 15
width = 0
while True:
for binaryDigit in range (0,binaryDigits):
TxtPic = []
binaryGraphicX = binaryGraphicX + 25
if binaryWord[binaryDigit] == 1:
running=True
while running:
for evnt in event.get():
if evnt.type==QUIT:
running = False
event.get()
draw.circle(screen,color,pos,radius)
display.flip()
TxtPic[binaryDigit]=comicFont.render(str(2**binaryBlit),True,(0,0,0))
screen.blit(TxtPic[binaryDigit],(binaryGraphicX,220))
elif binaryWord[binaryDigit] == 0:
running=True
while running:
for evnt in event.get():
if evnt.type==QUIT:
running = False
event.get()
draw.circle(screen,color2,pos,radius)
display.flip()
TxtPic[binaryDigit]=comicFont.render(str(2**binaryBlit),True,(0,0,0))
screen.blit(TxtPic[binaryDigit],(binaryGraphicX,220))
quit()
Below is where I call on the procedure
running=True
while running:
for evnt in event.get():
if evnt.type==QUIT:
running = False
event.get()
screen.fill((255,255,255))
TxtPic1=comicFont.render(str(solution),True,(255,255,255))
screen.blit(TxtPic1,(200,200))
binaryGraphics(binaryNumber)
display.flip()
display.quit()
I have been looking through your code and the first obvious problem I found was that you are comparing a character/string to an int in your if statements.
ie.
String:
binaryWord = str(100101)
String to Int comparison: (would return false and skip)
if binaryWord[binaryDigit] == 1:
Try this instead:
if binaryWord[binaryDigit] == "1":
Your program never has a chance to get to your code that draws text/circles.