Interaction with AI in PyGame [duplicate] - python

I am currently developing a simple Tower of Hanoi animation in Pygame, that should show the correct solution of Tower of Hanoi, moving one piece per second.
However, in my hanoi solving algorithm, I'm trying to update the display and use pygame.time.wait() after each movement; and instead of updating one movement and waiting one second, the program waits the total number of movements amount of seconds and then displays the tower with all the movements done at once.
What I would like to know is if I am using the wait function wrongly or if there is any other useful function in this situation that I am missing.
Here's the code:
def hanoi(n, origin, destination, aux):
# solves the game with n pieces
if n == 1:
positions[0] = destination
# updates and waits
printBackground()
printPieces(positions)
pg.time.wait(1000)
else:
hanoi(n-1, origin, aux, destination)
positions[n-1] = destination
#updates and waits
printBackground()
printPieces(positions)
pg.time.wait(1000)
hanoi(n-1, aux, destination, origin)
and the loop:
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
if running:
hanoi(numPieces, 0, 2, 1)
running = False
Thank you!

You need to seperate your algorithm from the drawing aspect of your code.
A simple way to update your code would be to use a coroutine that, at every step of your recursive hanoi function, gives the control back to the main loop, which in turn draws the screen, and gives control back to the hanoi coroutine every second.
Here's a simplified example that just counts down:
#-*- coding-utf8 -*-
import pygame
import pygame.freetype
pygame.init()
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
font = pygame.freetype.SysFont(None, 30)
def hanoi(num):
# We calculated something and want to print it
# So we give control back to the main loop
yield num
# We go to the next step of the recursive algorithm
yield from hanoi(num-1) #
steps = hanoi(1000)
ticks = None
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
exit()
# step every second only
if not ticks or pygame.time.get_ticks() - ticks >= 1000:
ticks = pygame.time.get_ticks()
screen.fill((200, 200, 200))
# the value from the next step of the coroutine
value = str(next(steps))
# render stuff onto the screen
font.render_to(screen, (100, 100), value)
pygame.display.flip()
clock.tick(60)
In your code, you should replace
# updates and waits
printBackground()
printPieces(positions)
pg.time.wait(1000)
with yield positions to give control back to the main loop and
hanoi(n-1, aux, destination, origin)
with
yield from hanoi(n-1, aux, destination, origin)
to keep the coroutine running and call
...
screen.fill((200, 200, 200))
positions = next(steps)
printBackground()
printPieces(positions)
...
inside the if in the main loop.
(If the algorithm finish, it will raise a StopIterationException which you probably want to catch).

Related

moving a rectangle when cursor is in an area

this script is supposed to move 2 rectangles whenever the cursor gets in one of them, i see them flashing sometimes, but they're not moving, they move right 30 and then they go back to 0
import pygame
pygame.init()
screen= pygame.display.set_mode((700,500))
while True:
ex = pygame.Rect(30,30,60,60)
exz= pygame.Rect(0,30,30,60)
for event in pygame.event.get():
if event.type == 256:
pygame.quit()
if event.type == 1024:
cursor_pos=pygame.mouse.get_pos()
print(cursor_pos[1].__str__()+"=y")
print(cursor_pos[0].__str__()+"=x")
print(exz.x.__str__()+"exz.x"+", "+exz.y.__str__()+"exz.y")
if(cursor_pos[0]+cursor_pos[1]) < ((exz.x+30)+exz.y*3) and (cursor_pos[0]+cursor_pos[1])>30 and cursor_pos[1]<=90 and cursor_pos[1]>=30:
exz.right+=30
ex.right+=30
print("exz:"+exz.x.__str__()+", "+exz.y.__str__())
print("exs:"+ex.x.__str__()+", "+ex.y.__str__())
pygame.display.set_caption("Cursor is in area")
else:
pygame.display.set_caption("Cursor is not in area")
pygame.draw.rect(screen,(255,0,0),ex)
pygame.draw.rect(screen,(0,255,0),exz)
pygame.display.update()
screen.fill((50,50,50))
your last 5 lines should be inside the while loop block, to update the screen.
I don't know if this is what you wanted, but:
import pygame
pygame.init()
screen= pygame.display.set_mode((700,500))
ex = pygame.Rect(30,30,60,60) #this has to be outside of the while loop, otherwise the position resets every time
exz= pygame.Rect(0,30,30,60) #same of ex
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: #I don't know what 256 and 1024 means, but this is way better
pygame.quit()
#This has to be outside the for event loop
cursor_pos=pygame.mouse.get_pos() #you shall update the cursor every fps, and not by a if statement
if ex.collidepoint(cursor_pos) or exz.collidepoint(cursor_pos): #Idk what you did here, but i think this is just way simpler
exz.x+=30
ex.x+=30
pygame.display.set_caption("Cursor is in area")
else:
pygame.display.set_caption("Cursor is not in area")
pygame.draw.rect(screen,(255,0,0),ex) #the draws methods were outside the while loop
pygame.draw.rect(screen,(0,255,0),exz)
pygame.display.update()
screen.fill((50,50,50))
This code moves the two rects whenever the mouse gets inside of one of them.
Your glitching is because ex and exz are inside the while loop, so you were re-setting the position every time. I removed the prints but those were not a problem

Pygame screen not updating after each element is added

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()

How to fix counter flashing

I am creating a very simple game to try and ease into things, but ran into a problem pretty early on.
I have tried re-positioning where the timer is updated, but it just keeps flashing/flickering.
def update_timer():
global timer, timerrect
# make timer text
displaytext(f"{counter}", 50, 50)
# dispay timer
pygame.display.flip()
return timer, timerrect
def displaytext(text, a, b):
x = font.render(text, True, BLACK, WHITE)
xrect = x.get_rect()
xrect.center = (a, b)
screen.blit(x, xrect)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.USEREVENT:
counter-=1
update_timer()
# not-so-elegant displaying of timer
try:
screen.blit(timer, timerrect)
pygame.display.flip()
except:
pygame.display.flip()
# some more code here
# draw all sprites
all_sprites.draw(screen)
# fps
clock.tick(60)
# update screen
pygame.display.flip()
As I said, the timer is constantly flickering. I'd like for it to not induce seizures /s.
Most of the time, when something is flickering in Pygame, there is one cause: You are calling pygame.display.flip() more than once per frame.
You should remove all calls to pygame.display.flip() except the one in your main loop (the one you have commented # update screen). This means that the display is updated only once per frame.
When there are additional flips the screen updates on top of itself, and some frames may not end up with the timer visible.
Also, it looks like your update_timer function would work, if you remove the extra flip. Then you would not need the "not-so-elegant" code that you probably used to try debugging this problem.

Pygame buttons not working

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

Python PyGame draw rectangles using for loop

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.

Categories

Resources