Adding an extra loop causes unresponsiveness - python

I'm making a simple game using Pygame where you have to get (by moving the cursor over them) all the circles that will appear on the screen (more will appear every second). The code is quite long so I made an example code. This code works fine, the Pygame window doesn't become unresponsive at all:
import pygame, random, sys
pygame.init()
window=pygame.display.set_mode((480,360))
end_program=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
pass
pygame.quit()
sys.exit()
However, in my game, in order to give the user the choice to play again, I need to wrap everything inside end_program in another loop. In the example shown, this is break_from_second_loop:
import pygame, random, sys
pygame.init()
window=pygame.display.set_mode((480,360))
end_program=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
break_from_second_loop=False
while not break_from_second_loop:
pass
pygame.quit()
sys.exit()
Now if this is run, the window becomes unresponsive! Anyone know why something as simple as wrapping the code in another loop (without altering the code at all) does this?

The problem is that the game can't respond, or do anything at all, if you're not running the event loop. And in the other loop, you're not running the event loop.
This is a general problem with event-loop-based programming. You can't do anything that takes a long time, and you can't do anything that has to run across multiple events.
So, you have to break your loop apart into steps, and do just one step (or a few of them) each time through the event loop.
In this particular case, it's actually pretty simple: just change that while to an if (and move the has_got_all_circles=False outside the main loop), and your logic now runs exactly once each time through the event loop.
Alternatively, change it to an if and also move it inside the for, so now it runs exactly once per event, instead of once per event loop iteration.
A third alternative is to factor out the whole thing into a function and set it as an idle or timer function, that runs whenever the event loop is idle, or once every frame, or once every 20ms, or whatever.
It's hard to know which of the three is appropriate in your case, but the basic idea is the same in all of them, so I'll just show the second one:
end_program=False
break_from_second_loop=False
while not end_program:
for event in pygame.event.get():
if event.type==pygame.QUIT or pygame.key.get_pressed()[pygame.K_ESCAPE]: #If the user either click the "x", or pressed the "esc" key
end_program=True
if not break_from_second_loop:
pass
This blog post explains the general problem in more detail—although most of it isn't really appropriate to this specific problem.

The issue you're having is that you're not nesting your event loop code within the while loop that does your game logic. Here's the general structure of what you want:
while not end_program:
while not end_game:
handle_events()
do_one_frame_of_game_logic()
offer_another_game()
It's possible that offer_another_game will also need to be run in its own loop, with its own event handling code.
Indeed, you might want to encapsulate the logic you want to use into a state machine system. You'd have states like PlayingGame, GameOver, and DoYouWantToPlayAgain, each of which would run for a while, then hand off to another state. Your main loop would then be something like:
state = StartState()
while state:
state.handle_events()
state.update()
state.draw()
state = state.next_state() # most of the time, the state will return itself

Related

What is the best way to make a player move at every interval in pygame?

Is there a library or a simple way to only loop something every 0.5 seconds without interrupting the rest of the program?
I have just started using pygame and have made a simple platformer and a Pong replica so far. I decided to try and make a Snake replica (I only currently have the head) and I need the snake to only move every 0.5 seconds while inputs can be registered at the 30 fps which I have the rest of the game running at. This is my current workaround:
while running: #this is tabbed back in my code
# keep loop running at the right speed
clock.tick(FPS)
# get time at each iteration
currentTime = str(time.time()).split(".")[0]
gameTime = int (currentTime) - int (startTime)
# this is used to check for something every 0.5 second (500 ms)
currentTimeMs = str(time.time()).split(".")[1]
# snake will move evry 0.5 second in a direction
if currentTimeMs[0] in ["5","0"] and moveDone == False:
moveDone = True
player1.move(direction)
elif currentTimeMs[0] not in ["5","0"]:
moveDone = False
There is more code within the while running: loop to get the direction and display the sprites but its not necessary for this. My current code works fine and will repeat the move function for my player1 every time that x in mm:ss:x is 0 or 5 (0.5 seconds apart) and will not repeat if it is that multiple times over a few frames.
This code needs to work within the running loop and not stop the program so time.sleep() doesn't work. I have also tried using the schedule library but it will not work as it cannot seem to allow the direction variable to change when passing it into the function.
My question therefore is; Is there a library or a shorter way to accomplish what I need?
Thanks in advance and I can message you the whole code if you need.
I suggest using pygames event mechanics and pygame.time.set_timer() (see here for docs).
You would do something like this:
pygame.time.set_timer(pygame.USEREVENT, 500)
and in the event loop look for the event type.
if event.type == pygame.USEREVENT:
If this is the only user defined event that you are using in your program you can just use USEREVENT.
When you detect the event the timer has expired and you move your snake or whatever. A new timer can be set for another 1/2 second. If you need more accuracy you can keep tabs on the time and set the timer for the right amount of time, but for you situation just setting it for 1/2 sec each time is okay.
If you need multiple timers going and need to tell them apart, you can create an event with an attribute that you can set to different values to track them. Something like this (though I have not run this particular code snippet, so there could be a typo):
my_event = pygame.event.Event(pygame.USEREVENT, {"tracker": something})
pygame.time.set_timer(my_event , 500)
You can store the moment of last move and then compare to actual time. To do it you can use perf_counter from time. Here is an example
last_move_time = perf_counter()
while running:
if perf_counter() - last_move_time > 0.5:
# move player
last_move_time = perf_counter()

ball animation is really junky while running pong game

the ball animation while running the program is very stuttery and i can't figure out why.
this is just the beggining of the program so ignore the fact that the game isn't ready to play yet.
this is the code: https://www.codepile.net/pile/dqKZa8OG
i want the ball to move smoothly without stuttering.
and in addition how do i make it so the program deletes the last location of the ball after each update?
It's not "junky" because of the timer, you can only see updates since you're probably moving the mouse in the meantime, then you're updating the ball position everytime you move it (which is wrong, as you're updating the position anytime any events is processed).
The problem is that you're using Clock in the wrong way: the pygame.time.Clock class creates an «object that can be used to track an amount of time», meaning that it is not a timer that can "react" once it times out. The tick method you're calling only updates the current Clock returning how many milliseconds have passed since the last call to tick itself, based on the fps argument you are providing.
What you need is to set a timer, possibly by using a specific eventid just for the updates. Also, since you're updating the ball position based on the events, you'll get more movements if you move the mouse (or any other event is called) making the ball move faster even if it shouldn't - and that's what you'll need the Clock object for.
# I'm using a smaller speed, otherwise it'll be too fast; it should
# depend on the window size to ensure that bigger windows don't get
# slower speeds
ball_velocity = .2
fps = 60
UPDATEEVENT = 24
[...]
def start_move_ball(angle):
ticks = clock.tick()
speed = ball_velocity * ticks
# "global ball" is not required, as you are assigning values to an
# existing object, not declaring it everytime.
# You can also use in-place operations to set the speeds, which is
# better for readability too.
ball[0] += angle[0] * speed
ball[1] += angle[1] * speed
def main_loop():
angle = choose_angle()
pygame.time.set_timer(UPDATEEVENT, 1000 // fps)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return False
# update only if necessary!
elif event.type == UPDATEEVENT:
window.fill((0,0,0))
start_move_ball(angle)
draw_mid(window)
draw_players(window)
draw_ball(window)
pygame.display.flip()
The timer is set outside the while cycle, as it automatically sends the event each time interval. You could also leave it within the while (the once=True argument is not really required in this case, as it automatically updates the timer based on the same eventid), but wouldn't make much sense, since set_timer always fires the event after the first time it's set.
I'm using 24 (pygame.USEREVENT) as an eventid, but you can set its id up to pygame.NUMEVENTS - 1, as suggested in the event documentation. If for any reason you want to stop the timer, just use pygame.time.set_timer(eventid, 0) and the timer for that eventid (24 in this case) will not be fired up again.
A couple of suggestions, besides all:
Remove the pygame.display.update() line in draw_mid(window), since it causes flickering while painting.
Avoid using external services to link code (it's not your case, but if the code is too long, first try to reduce it to minimal, reproducible example, eventually leaving all the relevant parts), as they could become unavailable sometime in the future, making your question hard to understand to somebody that is facing a similar issue and reads your answer some time after you posted it.

How to move from one screen to another, with any input from the user, in pygame? [duplicate]

I'm making a little game and I want to make another window separately from my main one.
I have the the main game in a main window, and I want to open a new window and do a little animation when the user does something.
In my example code below, when the user presses "a" I want it to open a new window and blit to there.
Here I set up the two windows: (I know this doesnt work, its what I'm asking how to do)
SCREEN_X = 400
SCREEN_Y = 400
BSCREEN_X = 240
BSCREEN_Y = 160
BATTLE_SCENE = pygame.display.set_mode((BSCREEN_X, BSCREEN_Y))
SCREEN = pygame.display.set_mode((SCREEN_X, SCREEN_Y))
and then the program:
def run_ani ():
#Do animation, blitting to BATTLE_SCENE
return
def main_game():
ending=False
while ending==False:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT: ending=True
if event.type == KEYDOWN: # key down or up?
if event.key == K_ESCAPE:
ending=True # Time to leave
print("Stopped Early by user")
elif event.key == K_a:
run_ani()
#Normal screen motion, blitting to SCREEN
if ending: pygame.quit()
return
So far what this does is draws the main screen, then when A is pressed, it stops drawing the main screen animations, but still draws the other animations on the main screen and draws in the top left corner.
I'm pretty sure it does this because I am setting BATTLE_SCENE to be smaller than the main screen, thus when blitting to BATTLE_SCENE it blits to the area I created (240x160) in the top corner of the main screen.
However I want BATTLE_SCENE to be a seperate window, so that when I press 'a' it will pop up, do its thing, then close or at least go behind the main screen.
How to do this? Is it even possible?
Do you really need multiple windows? I mean, do you really need them?
If yes, then you should probably use pyglet/cocos2d instead.
To have multiple windows in pygame, you need multiple processes (one for each window). While this is doable, it's not worth the efford. You'll need IPC to exchange data between the windows, and I guess your code will become error-prone and ugly.
Go with pyglet when you need more than one window.
The better solution is probably to divide your game into scenes. Create multiple scenes so that each one represent one stage of the game, something like MenuScene, MainScene, BattleScene, GameOverScene, OptionScene etc.
Then let each of those scenes handle input/drawing of that very part of the game.
MenuScene handles drawing and input etc. of the game's menu
MainScene handles drawing and input etc. of the running game
BattleScene handles drawing and input etc. of whatever you do in run_ani
In your mainloop, just pass control over to the current scene by implementing the methods draw(), handle_event(), and update().
Some example code to get the idea:
scenes = {'Main': MainScene(),
'Battle': BattleScene()} #etc
scene = scenes['Main']
class MainScene():
...
def handle_event(self, event):
if event.type == KEYUP:
if event.key == K_a:
scene = scenes['Battle']
...
class BattleScene():
...
def draw(self):
# draw your animation
def update(self):
# if animation is over:
scene = scenes['Main']
...
def main_game():
ending=False
While Not ending:
clock.tick(30)
for event in pygame.event.get():
scene.handle_event(event)
scene.update()
scene.draw()
This is an easy way to cleanly seperate the game logic and allow context switching.
======================================Edit=========================================
Actually it won't work. Apperantly pygame only supports one display screen, and when you initialize another, it will close the first. You will stay with two varibles, which in fact are the same surface. You can have instead the game increasing the window size and playing the battle scene on the side of it, to do this, you can call the pygame.display.set_mode() again with different values. The varible which references the display screen will still be usable, as it change its reference to the new one. After the scene is over you can decrease the window back the same way.
==================================================================================
What basically happens is you run a loop, and each iteration of it is rendering and displaying a new frame.
When you call a function inside a loop, it doesn't continue to run until you finish running the function.
One way to solve this problen is just keep calling the function that updates the battle scene in the main loop.
Another way is by using threading. Threading is basically running multiple scripts ("Threads") in the same time.
Luckily, python already implemented this for us with the threading module.
It's too long for me to explain the module here, but you can learn it here. It might be a little complex if you haven't use threads before, but after some time it will be easier.
And If you want to learn more about threading you can go here.
Specificly here, you can have two threads, one for each loop/window, and run them in the same time.
Hope I helped you!
Yes, that is possible. SDL2 is able to open multiple windows. In the example folder you can take a look at "video.py".
https://github.com/pygame/pygame/blob/main/examples/video.py
"This example requires pygame 2 and SDL2. _sdl2 is experimental and will change."

Tkinter, help a noob understand Tk's loops

I am Python 3 noob creating a checkers game, I have a bunch of functions... and my game's loop is as follows:
while EndGame==0:
PrintBoard()
PrintBoardGui()
print("################","\n","Player 1","\n","################","\n")
Player=1
PlayerSelectPiece()
MovePiece()
PrintBoard()
PrintBoardGui()
print("################","\n","Player 2","\n","################","\n")
Player=2
if NbrPlayer==2:
PlayerSelectPiece()
else:
AiSelectPiece()
MovePiece()
PrintBoardGui() that I run at the beggining of each turn and creates a Tkinter window and draws the board on a new Canvas in a Tkinter Frame.
After that, I have to close the window in order for the program to continue.
I know this is a sub-optimal solution.
I have looked around trying to understand Tkinter's loops and read a bit about the after() function but i really don't know how I could implement it in my code.
Ultimatly I would like my Tkinter window to stay open (mabye disabled or something) while I input stuff in the console to move the pieces. Can you help me?
At first, how do you want to interact with your game ?
With text in the console ?
With buttons ?
With keyboard/mouse event ?
The "after" method is used to refresh your screen after an amount of time.
It will call the method that you pass through the parameters.
You shouldn't have to put an infinite loop in it. But you will have to check with a simple condition the game ending, to display another screen.
If you have to use the console entry, it can be a bit hard for a beginner to manage the GUI update and the console.
I am not sure what ur question was but, If you would want to run the code forever (until you stop it). you would have to use the while loop like this:
while "thing" == True:
PrintBoard()
PrintBoardGui()
print("################","\n","Player 1","\n","################","\n")
Player=1
PlayerSelectPiece()
MovePiece()
PrintBoard()
PrintBoardGui()
print("################","\n","Player 2","\n","################","\n")
Player=2
if NbrPlayer==2:
PlayerSelectPiece()
else:
AiSelectPiece()
MovePiece()
thing = False
at the end you change thing to False otherwise it would be infinite and it would bug.

Resize pygame window without losing keyboard state

I'm facing a problem with pygame 1.9.2 based on SDL 1.2.15: the only mean of resizing window programmatically I see is pygame.display.set_mode(...). But when I call this method my keyboard state is reset, so all currently pressed keys and modifiers send KEYUP event, repeated keys are stopped too.
When the window is resized manually by user keyboard state is perfectly normal. The event queue is paused for the time of resizing, then VIDEORESIZE and VIDEOEXPOSE events come up and keys are still pressed, if they were so.
There are few questions:
Is there a way to keep normal keyboard state while resizing window?
If not, I would like to at least keep modifier keys but I can not just use key.set_mods() because I am unable to get the moment when user releases key.
At last is there a way to invoke resizing of SDL window directly?
Example program demonstrates undesired behaviour. Left and right arrows resize window to same size, yet if they are pressed, there is only one keypress. Other keys are repeated normally if held. Output is event log for everything that happens.
import pygame
import sys
# same size all the time
size = (200, 200)
pygame.init()
pygame.display.set_mode(size, pygame.RESIZABLE)
# if left or right arrow key is held nothing will happen despite this line
pygame.key.set_repeat(500, 200)
run = True
while run:
for event in pygame.event.get():
out = '%s\t%s'%(event.type, str(event.dict))
if event.type == pygame.QUIT:
pygame.display.quit()
run = False
if event.type == pygame.KEYDOWN:
# mods are also reset on set_mode() call
out += ', mods = %i' % (pygame.key.get_mods())
if event.key == pygame.K_LEFT:
pygame.display.set_mode(size, pygame.RESIZABLE)
if event.key == pygame.K_RIGHT:
pygame.display.set_mode(size, pygame.RESIZABLE)
print(out)
pygame.time.Clock().tick(60)
Tested on Win machine but soon will report if linux fails too.
UPD: I've been digging into source of pygame and can not see why, but the documentation bundled with sources of pygame states, that Pygame can only have a single display active at any time. Creating a new one with pygame.display.set_mode() will close the previous display which implies that there is no way to resize window, only to recreate it. Seems a little strange to me.
UPD: I have changed my progam behaviour so that resize does not actually happen until user releases keyboard completely. It works well and does not break usability.
Nevertheless, it would be great to know if there exists a solution to stated problem.
One solution is, like self. has mentioned, to store the states of the keys so that instead of using only the events, you could ignore this particular key up event. This could, however, have one problem. If you are trying to create a key control to resize the screen, which you probably are doing, then you will also be ignoring the key up event that is real. Because this kind of control is rarely implemented, there does not seem to be any kind of clean solution, like a function to just change the screen, or one to suppress the false key events. However, you could find a way around the key event problem. There are really two viable solutions. The first one (and probably the worse of the two) is to create a way to identify the fake events, and selectively ignore the key up events. Probably the easiest way to do this is to resize the screen periodically during a fixed amount of time, so that you can expect a false keyboard event at this time, and ignore it. Here is an example of the code to block a resize every second (specifically, it assumes a screen update occurs every time the system time has an exact second value, no fractions of a second) for the a key:
import pygame
import datetime
a_keydown = False
for event in pygame.event.get():
if item.type == pygame.KEYDOWN and item.key == pygame.K_a:
a_keydown = True
print "keydown event"
if item.type == pygame.KEYUP and item.key == pygame.K_a:
if datetime.datetime.now().time()[4] > 15: #leaves a window of fifteen microseconds for the screen resize, this may need to be adjusted
a_keydown = False
print "key up event"
The better of the two solutions is not this, however. Assuming the keyup event created by set_mode exists soley in python, you could get key events from elsewhere, like pywin32 for example. Try using the win32api module, and something like the GetKeyState() function for what you need. This will give you the last known state of a key. Not quite as convenient as pythons event stream, but it may be your best solution, as pygame doesn't supply an elegant solution to this.

Categories