Im trying to add graphics for my simple sudoku solver program. I want the program to update the displayed sudoku board in real time as it solves it (I thought I would do this by calling my draw_number function after every correctly solved number, and then delaying the program, so that it would draw the number, pause, then continue solving.)
However, instead the program solves the whole thing while freezing, then displays the whole solution at once, when its done.
Here is a small-scale example of what im trying to do, which illustrates the problem:
import pygame
pygame.init()
window = pygame.display.set_mode((600, 600))
board = [
[1,2,3],
[7,5,6],
[4,9,8],
]
def draw_number(r, c, num):
font = pygame.font.Font('freesansbold.ttf', 26)
text = font.render(str(num), True, (0, 0, 0), (255, 255, 255))
text_rect = text.get_rect()
text_rect.center = ((c+1)*48+11, (r+1)*48+11)
window.blit(text, text_rect)
print("Drawing " + str(num) + " at " + str(r+1) + ", " + str(c+1))
pygame.display.update()
run = True
while run:
for i in board:
for j in range(0, 3):
draw_number(board.index(i), j, board[board.index(i)][j])
pygame.display.update()
pygame.time.delay(20)
run = False
pygame.time.delay(5000)
When we run this, the simple 3x3 grid should draw individually, with pauses, but instead it finishes the for loops, then pauses for 5000ms, then shows the result for a split second, then closes the program.
I know I am doing something wrong here, but I am new to pygame and not sure what the correct way to do this is.
The issue is that when you do pygame.time.delay() it freezes the whole pygame window. In order to prevent this you need to import the time module and then use time.sleep(seconds) instead of the pygame.time.delay()
Here is some code:
import pygame
import time
Then(skipping the irrelevant parts):
run = True
while run:
for i in board:
for j in range(0, 3):
draw_number(board.index(i), j, board[board.index(i)][j])
time.sleep(5)
PyGame uses an event-driven model for programs. The code should never call time.sleep(), or pygame.time.delay() etc. because it pauses the program. If it pauses for long enough, the window manager will consider the program to have stopped responding.
An easy way around this is to time operations with the pygame.time.get_ticks(), which returns an every-increasing number of milliseconds since the pygame program started. Design your program such that it looks at the clock to decide what to do next.
Say you only want to perform an operation every 3 seconds. Look at the start-time, do the operation, but then don't do anything more (except poll for events and update the screen) until 3000 milliseconds have elapsed.
For example:
def doTheThing():
pass # TODO: some super-interesting function
time_we_did_the_thing = 0 # When did we last do the thing
clock = pygame.time.Clock()
running = True
while running:
# check for events
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
# paint the screen
screen.fill( ( 0, 40, 200 ) ) # bluish
# Do the thing, but only every 3 seconds
time_now = pygame.time.get_ticks()
if ( time_now > time_we_did_the_thing + 3000 ):
doTheThing()
time_we_did_the_thing = time_now
# flush the screen-updates
pygame.display.flip()
clock.tick_busy_loop(60) # max FPS=60
pygame.quit()
Related
import pygame
import time
# WINDOW SETUP
window = pygame.display.set_mode((900, 500))
pygame.display.set_caption("Pong")
time.sleep(5)
FPS = 60
# RGB VALUE VARIABLES
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
def background(window):
window.fill(WHITE)
pygame.display.update()
# FRAMERATE AND EVENT LOOP INITIALIZATION
def main():
run = True
clock = pygame.time.Clock()
while run:
clock.tick(FPS)
background(window)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if running == False:
pygame.quit()
Above, is my code. I'm trying to make a pong game with pygame. The text editor I am coding with is Visual Studios Code (VSCODE).
Firstly, you need to call the main. You should also make you're code nice and easy to read when possible. I imported pygame as pg which makes typing pygame functions a bit faster as you have less to type. Also, it's better to use global variables to hold variables that won't change through the program, like screen width, height, colours, etc. Then make sure you initialise the module.
As well as that, the only update you have is in background(). You should put the update at the bottom of the loop and remove it from background(). This way everything above will update each loop.
I apologise for not adding you're FPS counter in here as well but I think this should be enough to help you get you're window running with more readable code and a more efficient loop.
import pygame as pg
# Global Variables
screen_width = 900
screen_height = 500
screen = pg.display
window = screen.set_mode((screen_width, screen_height))
colour = 'red'
def main():
# Initialise module
pg.init()
pg.display.set_caption('PONG')
running = True
while running:
# This is a better way of writing your loop
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
# Call background function
background()
# Updates window
# place this inside the loop near the bottom
# so everything is updated at the end of each loop
screen.flip()
def background():
window.fill(colour)
# Remember to call your main function
# This if statement is good practise but not required
# You can just place main() here
if __name__ == '__main__':
main()
I can't get python to work when I am using my Raycast function to display images, how do I fix this?
I tried moving some variables and played around with the function, but I can't seem to get it to work.
import pygame
pygame.init()
Screen = "Title"
DB = 0
Width = 800
Height = 600
Frame = pygame.display.set_mode((Width,Height))
pygame.display.set_caption("GAME")
FPS = pygame.time.Clock()
def Raycast(RayXPos, RayYPos):
RaycastThis = pygame.image.load(TTR)
Frame.blit(RaycastThis, (RayXPos, RayYPos))
Loop = True
while Loop == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
pygame.display.update()
FPS.tick(60)
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
I expected the frame to display the image (the same size as the window) and it instead crashed, and I can't run the program
Your problem is the infinite loop:
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
Since the loop control variables Screen and DB never change, you have no way to exit the loop. You're stuck here, eternally repeating a function that does very little, and includes no changes to observe.
See this lovely debug blog for help.
I just wrote some code that works in the commandline, so now I'd like to give it some graphics. Now, this is my very first programming project so bear with me as I try to explain the problem:
I'm using PyGame and initialised the window as follows:
import pygame, pygame.midi,pygame.font, random
(width, height) = (600, 400)
background = (220,220,220)
pygame.midi.init()
pygame.font.init()
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Chord trainer")
screen.fill(background)
pygame.display.flip()
Then I attempt to render text (which does not give any errors):
myfont = pygame.font.SysFont("Arial", 80)
letter = myfont.render("SOME WEIRD TEST TO TRY AND GET THINGS WORKING",0,(0,0,0))
screen.blit(letter,(100,100))
And because I'd like to actually see my text before the program closes, I set up an infinite loop:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
When I run it, I just get the grey screen I wanted, but without any sort of text, which would make me think that there's something wrong with my blit command, but I can't figure out what.
Initially I had the rendering set in a loop, but that just made the program unresponsive so I took is out for debugging. For completeness, here is that loop:
while True:
# Decide on random chord
c1 = random.choice(chords)
# Make sure that no repitition takes place.
if c1==c2:
while c1==c2:
c1=random.choice(chords)
c2 = c1
myfont = pygame.font.SysFont("Arial", 80)
letter = myfont.render(str(c1),0,(0,0,0))
screen.blit(letter,(100,100))
# Listen to Midi device and search for c.
midi_listen(inp,sorted(c1))
score += 1
You need to add
pygame.display.flip()
after
letter = myfont.render("SOME WEIRD TEST TO TRY AND GET THINGS WORKING",0,(0,0,0))
screen.blit(letter,(100,100)
It will update your screen and normally you will be able to see your text.
I have created some sort of menu navigation system in my game. All the screens are blitted in. The "Play" and "Quit" and "Controls" button works just fine but when I try to press menu from the controls screen, nothing happens. On the controls screen, you can faintly see the first menu screen from before. That might be the problem. I think that as the return to menu button is over the previous controls page button, it somehow is pressing the controls button from before. The button and menu segment of my code will be pasted here and the full thing will be pasted in a pastebin.
def text_to_button(msg,color,buttonx,buttony,buttonwidth,buttonheight,size = "small"):
textSurf, textRect = text_objects(msg,color,size)
textRect.center = ((buttonx + buttonwidth/2)), buttony+(buttonheight/2)
gameDisplay.blit(textSurf, textRect)
def button(text,x,y,width,height,inactive_color,active_color,size = "small",action = None):
cur = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
#print(click)
if x + width > cur[0] > x and y + height > cur[1] > y:
pygame.draw.rect(gameDisplay, active_color,(x,y,width,height))
if click[0] == 1 and action != None:
if action == "quit":
pygame.quit()
quit()
if action == "controls":
game_controls()
if action == "play":
gameLoop()
if action == "main":
game_intro()
else:
pygame.draw.rect(gameDisplay, inactive_color,(x,y,width,height))
text_to_button(text,black,x,y,width,height,size)
def game_controls():
gcont = True
while gcont:
gameDisplay.blit(cont,(0,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
button("Play",150,500,100,50,white,gray,"small",action = "play")
button("Main Menu",320,500,150,50,white,gray,"tiny", action = "main")
button("Quit",550,500,100,50,white,gray,"small", action = "quit")
pygame.display.update()
clock.tick(15)
def game_intro():
intro = True
while intro:
gameDisplay.blit(imggg,(0,0))
button("Play",150,500,100,50,white,gray,"small",action = "play")
button("ControLs",320,500,150,50,white,gray,"tiny", action = "controls")
button("Quit",550,500,100,50,white,gray,"small", action = "quit")
pygame.display.update()
clock.tick(15)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
intro = False
Full Code: https://pastebin.com/jrd82gkJ
You will have very hard time to debug your code in order to achieve the behavior you want for one simple reason:
The logic you use to switch between different screens providing different functionality is causing much trouble you can't directly see if you only run the game.
So you think: "oh ... how come the button doesn't work, there must be an issue with the button".
You are probably not aware of the fact that using functions having own while loops you go deeper and deeper into recursive calls with increasing recursion depth with each switch from one view to another - it is not how pygame is thought to be programmed.
I suggest you add some print() commands into your code to see in the console output that the code doesn't really do what you expect even if it appears to be OK at the first glance because it works.
Then I suggest you REWRITE your entire code so that you have one main while notGameExit: loop, and don't use any other looping in the helper functions. If you want use looping in your helper functions at least don't call from the helper functions another functions with own loops (and so on), but RETURN from them with an explicit return to avoid recursion.
If you leave the in the main loop called function with return your main loop will continue running and depending on some switches you can display in it different things on the screen and react differently to user actions.
Maybe looking at a minimal working pygame script showing "action" without usage of a loop you will gain better understanding and some deep "enlightenment" about how pygame works and then start a total rewrite of your game using another approach as this one you have used in the current code? Then come back with what you have achieved if you have further questions, but you won't probably have any, because it would be much easier to debug it yourself if the code will become more straightforward.
import pygame
pygame.init() # start PyGame (necessary because 'import pygame' doesn't start PyGame)
winDisplay = pygame.display.set_mode((1024, 768)) # set PyGame window size to 1024x768 pixel
pygame.display.set_caption("Minimal PyGame Test Script")
# Time in pygame is measured in milliseconds (1/1000 seconds) (defined by TIMER_RESOLUTION constant):
pygame.TIMER_RESOLUTION = 1000 # assure 1000 explicit, don't relay on default value
colorWhite = (255, 255, 255) # RGB color in Pygame format (valueRed=255, valueGreen=255, valueBlue=255)
colorRed = (255, 0, 0)
colorGreen = ( 0, 255, 0)
colorBlue = ( 0, 0, 255)
winDisplay.fill(colorWhite)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorRed)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorGreen)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorBlue)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorWhite)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
I'm new to PyGame and I am learning using the book Beginning Game Development with Python and PyGame. There is an example (Listing 4-9) where the author says a script will draw ten randomly placed, randomly colored rectangles on the PyGame screen. Here is the code from the book:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
screen.unlock()
pygame.display.update()
What happens when I do this (and this is what I would expect to happen logically) is that it draws infinitely many rectangles. It just keeps doing the for loop because the while loop is always True. I have searched online about this, and I tried moving the display update around, but those things didn't work. It is driving me crazy!
Thanks!
Looks like you already knew why it were drawing infinite with rectangles.
I guess you want to draw 10 random rectangles with random size, pos and color once.
Then you can do like this:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
class Rectangle:
def __init__(self, pos, color, size):
self.pos = pos
self.color = color
self.size = size
def draw(self):
pygame.draw.rect(screen, self.color, Rect(self.pos, self.size))
rectangles = []
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
rectangles.append(Rectangle(random_pos, random_color, random_size))
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
for rectangle in rectangles:
rectangle.draw()
screen.unlock()
pygame.display.update()
The code does run an infinitely.
However, the inner loop does create 10 rectangles.
So technically the script does draw ten randomly placed, randomly colored rectangles.
It just does that an infinite amount of times.
Note: This is the part that draw ten randomly placed, randomly colored rectangles.
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
It will draws many rectangles, and its endless yes.It is not drawing 10 times, because there is no arguments says break the while loop except -sys exit-. So you have to define a variable for that.
Normally there should be an if statement in the event statement, that will change the boolean of while to False and it will stop the loop.
You can simply change your codes to:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
count=0 #new variable for stop the loop
while count<10: #new limit
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
count+=1 #variable updating after drawing each rectangle
screen.unlock()
pygame.display.update()
Its maybe a misstyping in the book I dont know, but that loop is infinite so you can change it to this :)
Yes, this is a poorly written example, but it's not "broken." Everyone so far has overlooked this bit:
for event in pygame.event.get():
if event.type == QUIT:
exit()
PyGame includes the notion of processing events. This first line gets all the events that your game has access to, and looks at each of them. If one of them is a "QUIT" event, then it calls exit() (more commonly known as sys.exit(0)) directly, which immediately ends the application.
The "QUIT" type event is generated when you quit the application, such as by clicking the red X. There are likely other ways to end the application that will also generate this event, but I'm not sure what they are. There are also ways to end the application that will NOT generate this event. So yes, if you never quit the application it will run forever-ish.
A much better way to write this functionality is as follows:
done = False
while not done:
for event in pygame.event.get():
if event.type == QUIT: # or other types of events
done = True
//do other game logic and drawing stuff after this
pygame.display.update()
This makes sure that the loop is handled consistently, and that the exit point is always at the same place: the top of the loop. If there's only one place to exit the loop, then you have an easier time knowing how much of the code is getting run (all of it). It also allows you to have some more sophisticated ways of deciding when and whether to end the loop.
However, even this is going to be a very confusing "example" because (just like you and #user3679917 noted) it seems to do something different than described. It seems like it's just drawing random rectangles forever. Let's see if we can make it more predictable...
//pre-roll the random numbers before the while loop
random_attributes = []
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
random_attributes.append(random_color, random_pos, random_size)
//then when you draw, just draw what you already rolled each time, without rolling new stats
done = False
while not done:
for event in pygame.event.get():
if event.type == QUIT: # or other types of events
done = True
//do other game logic and drawing stuff after this
for color, position, size in random_attributes:
pygame.draw.rect(screen, color, Rect(position, size))
pygame.display.update()
That will draw the same thing infinitely, until you close the application. Incidentally, this is also what (almost) all visual applications do all the time, i.e. draw everything every time. It's not as if you can draw it on the screen once and it stays. Every time the screen is drawn, the screen asks everyone "Okay guys, what's going on the screen right now," and unless your application says "draw this stuff," then it's not going to get drawn, even if it was drawn last time.
Hope this helps.