PyGame, waiting for computer to decide its move - python-3.x

Okay
So I am building a tic-tac-toe on pygame in which A player can play against player, computer vs player or computer vs computer.
I have already developed the code for a MiniMaxAgent() which takes its input as a 2D matrix and returns the (row, col) where it would play a move. The thing is that this code can take several seconds to execute on an nxn board. Because of this the pyGame Code hangs.
Sample event loop:
while running:
mouseClicked = False
DISPLAYSURF.fill(BGCOLOR)
renderBoard()
#event handlers
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEMOTION:
mouseX, mouseY = event.pos
elif event.type == MOUSEBUTTONUP:
mouseX, mouseY = event.pos
mouseClicked = True
row, col = players[currentPlayer].turn(currentPlayer*board.state)
board.state[row][col] = currentPlayer
currentPlayer = currentPlayer * -1
pygame.display.update()
As you can see, when I call the function players[currentPlayer].turn(), It should return me the optimal move in some seconds. But PyGame freezes.
How should I implement this?

A simple solution is to run the blocking function in question in a Thread. This will allow your game loop to keep running.
Here's a simple example. Note how the loop keeps running while the turn function wastes some time.
import pygame
import pygame.freetype
import random
import time
import threading
pygame.init()
screen = pygame.display.set_mode((800, 300))
PLAYER_TURN, AI_TURN = 0, 1
font = pygame.freetype.Font(None, 30)
state = PLAYER_TURN
running = True
result = None
def turn(boardstate):
print('thinking about ' + boardstate + '....')
time.sleep(1)
print('thinking very hard....')
time.sleep(1)
global result
global state
result = random.randint(10, 100)
state = PLAYER_TURN
r = pygame.rect.Rect((0, 250, 100, 50))
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
elif e.type == pygame.MOUSEBUTTONUP:
if state == PLAYER_TURN:
state = AI_TURN
threading.Thread(target=turn, args=['an argument']).start()
screen.fill(pygame.color.Color('grey'))
if state == PLAYER_TURN:
font.render_to(screen, (50, 50), 'It is your turn. Click anything')
if result:
font.render_to(screen, (50, 180), 'AI said: ' + str(result))
elif state == AI_TURN:
font.render_to(screen, (50, 50), 'Waiting for AI')
pygame.draw.rect(screen, pygame.color.Color('red'), r)
r.move_ip(1, 0)
if not screen.get_rect().contains(r):
r.x = 0
pygame.display.update()

Related

Pygame, how do you fix a part of the screen being black when i make a game?

A part (specifically the top middle) of the screen is black, once i have added a background, but i can see the rest of the background.
I have tried running it a few times and i have tried searching what might have caused it, i don't have a game loop yet, because nothing is supposed to happen, that might be the problem... sorry the code is long...
import pygame
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
SCREEN_TITLE = "road cross game thing"
WHITE_COLOR = (255,255,255)
clock = pygame.time.Clock()
pygame.font.init()
font = pygame.font.SysFont("comicsans", 75)
title = SCREEN_TITLE
width = SCREEN_WIDTH
height = SCREEN_HEIGHT
class Game:
TICK_RATE = 60
title = SCREEN_TITLE
width = SCREEN_WIDTH
height = SCREEN_HEIGHT
image = pygame.image.load("photo.jpg")
game_screen = pygame.display.set_mode((width, height))
game_screen.fill(WHITE_COLOR)
game_screen.fill(WHITE_COLOR)
game_screen.blit(image, (0, 0))
pygame.display.set_caption(title)
is_game_over = False
did_win = False
direction = 0
while not is_game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_game_over = True
elif event.type == pygame.MOUSEBUTTONUP:
pos= pygame.mouse.get_pos()
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
direction = 0
print(event)
game_screen.fill(WHITE_COLOR)
pygame.display.update()
clock.tick(TICK_RATE)
object_image1 = pygame.image.load("photo.jpg")
image1 = pygame.transform.scale(object_image1, (50, 50))
x_pos = 50
y_pos = 50
game_screen.blit(image1,(x_pos, y_pos))
pygame.init()
Game()
pygame.quit()
quit()
i expected the code to make the whole background my picture.
Your code is mostly there, it's just a bit un-organised.
The reason the screen updating is not what you expect is because the code is drawing and then updating in out-of-order. In a nutshell, it should make all the drawing operations first, then update() them all to the display. Obviously if you draw and image, then call fill(), the first draw operation is not going to be visible, since it's been over-painted.
The image1 does not fill the entire display because it's only stretched to ( 50, 50 ) whereas the screen is 1500 x 800.
While I was debugging the code, I moved the initialisation functions into a member function Game.__init__(), and the main game loop into a Game.run() function.
The OP's indentation is a little messed-up, I expect this is caused by the paste into SO (otherwise the program would not produce the described result). But it looks like you're loading images inside the main game loop. It's best to load resources like images, sounds, etc. only once before the user-event loop begins.
import pygame
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
TICK_RATE = 60
SCREEN_TITLE = "road cross game thing"
WHITE_COLOR = (255,255,255)
# Initialise PyGame
pygame.init()
pygame.display.set_caption( SCREEN_TITLE )
game_screen = pygame.display.set_mode( ( SCREEN_WIDTH, SCREEN_HEIGHT ) )
clock = pygame.time.Clock()
pygame.font.init()
font = pygame.font.SysFont("comicsans", 75)
class Game:
def __init__( self, game_screen ):
self.game_screen = game_screen
self.image = pygame.image.load( "photo.jpg" )
self.object_image1 = pygame.image.load( "photo.jpg" )
#self.image1 = pygame.transform.scale( self.object_image1, (50, 50) )
# Scale to fill window
self.image1 = pygame.transform.scale( self.object_image1, ( SCREEN_WIDTH, SCREEN_HEIGHT) )
def run( self ):
is_game_over = False
did_win = False
direction = 0
while not is_game_over:
# Handle user input events
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_game_over = True
elif event.type == pygame.MOUSEBUTTONUP:
pos= pygame.mouse.get_pos()
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
direction = 0
print(event)
# Update the screen
#game_screen.fill( WHITE_COLOR )
#game_screen.blit( self.image, (0, 0) )
#x_pos = 50
#y_pos = 50
#game_screen.blit( self.image1, (x_pos, y_pos) )
# Fill the window with image1
game_screen.blit( self.image1, ( 0, 0 ) )
pygame.display.update()
clock.tick( TICK_RATE )
# Run the Game
game = Game( game_screen )
game.run()
pygame.quit()
quit()

How to do a PyGame countdown without overlaps? [duplicate]

I started using pygame and I want to do simple game. One of the elements which I need is countdown timer.
How can I do the countdown time (eg 10 seconds) in PyGame?
Another easy way is to simply use pygame's event system.
Here's a simple example:
import pygame
pygame.init()
screen = pygame.display.set_mode((128, 128))
clock = pygame.time.Clock()
counter, text = 10, '10'.rjust(3)
pygame.time.set_timer(pygame.USEREVENT, 1000)
font = pygame.font.SysFont('Consolas', 30)
run = True
while run:
for e in pygame.event.get():
if e.type == pygame.USEREVENT:
counter -= 1
text = str(counter).rjust(3) if counter > 0 else 'boom!'
if e.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
screen.blit(font.render(text, True, (0, 0, 0)), (32, 48))
pygame.display.flip()
clock.tick(60)
On this page you will find what you are looking for http://www.pygame.org/docs/ref/time.html#pygame.time.get_ticks
You download ticks once before beginning the countdown (which can be a trigger in the game - the key event, whatever).
For example:
start_ticks=pygame.time.get_ticks() #starter tick
while mainloop: # mainloop
seconds=(pygame.time.get_ticks()-start_ticks)/1000 #calculate how many seconds
if seconds>10: # if more than 10 seconds close the game
break
print (seconds) #print how many seconds
In pygame exists a timer event. Use pygame.time.set_timer() to repeatedly create an USEREVENT. e.g.:
timer_interval = 500 # 0.5 seconds
timer_event = pygame.USEREVENT + 1
pygame.time.set_timer(timer_event , timer_interval)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event.
To disable the timer for an event, set the milliseconds argument to 0.
Receive the event in the event loop:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == timer_event:
# [...]
The timer event can be stopped by passing 0 to the time parameter.
See the example:
import pygame
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
counter = 10
text = font.render(str(counter), True, (0, 128, 0))
timer_event = pygame.USEREVENT+1
pygame.time.set_timer(timer_event, 1000)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == timer_event:
counter -= 1
text = font.render(str(counter), True, (0, 128, 0))
if counter == 0:
pygame.time.set_timer(timer_event, 0)
window.fill((255, 255, 255))
text_rect = text.get_rect(center = window.get_rect().center)
window.blit(text, text_rect)
pygame.display.flip()
pygame.time.Clock.tick returns the time in milliseconds since the last clock.tick call (delta time, dt), so you can use it to increase or decrease a timer variable.
import pygame as pg
def main():
pg.init()
screen = pg.display.set_mode((640, 480))
font = pg.font.Font(None, 40)
gray = pg.Color('gray19')
blue = pg.Color('dodgerblue')
# The clock is used to limit the frame rate
# and returns the time since last tick.
clock = pg.time.Clock()
timer = 10 # Decrease this to count down.
dt = 0 # Delta time (time since last tick).
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
timer -= dt
if timer <= 0:
timer = 10 # Reset it to 10 or do something else.
screen.fill(gray)
txt = font.render(str(round(timer, 2)), True, blue)
screen.blit(txt, (70, 70))
pg.display.flip()
dt = clock.tick(30) / 1000 # / 1000 to convert to seconds.
if __name__ == '__main__':
main()
pg.quit()
There are several ways you can do this- here's one. Python doesn't have a mechanism for interrupts as far as I know.
import time, datetime
timer_stop = datetime.datetime.utcnow() +datetime.timedelta(seconds=10)
while True:
if datetime.datetime.utcnow() > timer_stop:
print "timer complete"
break
There are many ways to do this and it is one of them
import pygame,time, sys
from pygame.locals import*
pygame.init()
screen_size = (400,400)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("timer")
time_left = 90 #duration of the timer in seconds
crashed = False
font = pygame.font.SysFont("Somic Sans MS", 30)
color = (255, 255, 255)
while not crashed:
for event in pygame.event.get():
if event.type == QUIT:
crashed = True
total_mins = time_left//60 # minutes left
total_sec = time_left-(60*(total_mins)) #seconds left
time_left -= 1
if time_left > -1:
text = font.render(("Time left: "+str(total_mins)+":"+str(total_sec)), True, color)
screen.blit(text, (200, 200))
pygame.display.flip()
screen.fill((20,20,20))
time.sleep(1)#making the time interval of the loop 1sec
else:
text = font.render("Time Over!!", True, color)
screen.blit(text, (200, 200))
pygame.display.flip()
screen.fill((20,20,20))
pygame.quit()
sys.exit()
This is actually quite simple. Thank Pygame for creating a simple library!
import pygame
x=0
while x < 10:
x+=1
pygame.time.delay(1000)
That's all there is to it! Have fun with pygame!
Another way to do it is to set up a new USEREVENT for a tick, set the time interval for it, then put the event into your game loop
'''
import pygame
from pygame.locals import *
import sys
pygame.init()
#just making a window to be easy to kill the program here
display = pygame.display.set_mode((300, 300))
pygame.display.set_caption("tick tock")
#set tick timer
tick = pygame.USEREVENT
pygame.time.set_timer(tick,1000)
while 1:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.USEREVENT:
if event.type == tick:
## do whatever you want when the tick happens
print('My tick happened')

PyGame Screen Returning Black when run

While trying to run a game in which is being developed for a project, the code would not work and causes the screen to be black. The Mycharacter is a character, enemy is for a enemy character, obstacle is for obstacles within the game. The design is supposed to be a character chasing the enemy while the enemy is chasing the character to gain points with obstructions in the way.
We are importing other classes into this function and main class.
All help will be appricated.
Heres how the code looks:
import pygame
import time
import random
import Mycharacter
import enemy
import obstacle
import json
GREEN = (0, 255, 0)
class Controller:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 800))
self.background = pygame.Surface(self.screen.get_size()).convert()
#self.button_image = pygame.image.load("start_button.png").convert
self.button = pygame.Rect(350, 50, 100, 100)
#self.button_image.get_rect()
self.font = pygame.font.Font("Shark_Soft_Bites.TTF", 60)
self.text = pygame.font.Font.render(self.font,"Start", True, (255, 0, 0))
#self.score_text = pygame.font.Font.render("Highscore: "+ str())
self.file = open("highscores.json", "r")
self.currentState = "Start"
self.obstacles = []
for i in range(5):
x = random.randrange(50, 600)
y = random.randrange(60, 700)
self.obstacles.append(obstacle.Obstacle((x, y), 'rock.png' ))
self.enemies = []
for i in range(3) :
if i == 0:
x = 725
y = 100
elif i == 1:
x = 75
y = 700
elif i == 2:
x = 725
y = 700
self.enemies.append(enemy.Enemy((x, y), "enemy.png"))
self.Mycharacter = Mycharacter.Mycharacter((75, 100), "head6.png")
self.mysprites = pygame.sprite.Group((self.Mycharacter,) + tuple(self.enemies) + tuple(self.obstacles))
self.mysprites2 = pygame.sprite.Group(tuple(self.enemies) + tuple(self.obstacles))
self.score = 0
self.end_time = 0
self.start_time = 0
self.time = (self.end_time-self.start_time) * 1000
def mainLoop(self):
"""
This is the main loop for the game that calls the other functions in this class
in order to create a start screen, run the game, and present the high score and player
score at the end of the game.
Param list: None
Return list: None
"""
self.done = False
while not self.done:
if self.currentState == "start":
self.startGame(self)
elif self.currentState == "running":
self.start_time = pygame.time.get_ticks()
self.runGame(self)
elif self.currentState == "end":
self.endGame(self)
pygame.quit
def startGame(self):
"""
This is the function for the start of the game. It fills the screen
with a different color background and creates the start button that the user
can click on to begin running the game.
Param list: None
Returns: None
"""
start = True
while start:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
start = False
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if self.button.collidepoint(mouse_pos):
self.currentState = "running"
start = False
self.screen.fill((255, 0, 255))
pygame.draw.rect(self.screen, (0, 0, 0), self.button)
self.screen.blit(self.text, (350,50))
pygame.display.flip()
pygame.quit
def runGame(self):
run = True
clock = pygame.time.Clock
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
run = False
if event.type == pygame.KEYDOWN:
#Under here is the game logic and i don't know if i should have the random enemy movement under here
if(event.key == pygame.K_UP):
self.Mycharacter.move_up()
elif(event.key == pygame.K_DOWN):
self.Mycharacter.move_down()
elif(event.key == pygame.K_LEFT):
self.Mycharacter.move_left()
elif(event.key == pygame.K_RIGHT):
self.Mycharacter.move_right()
self.score += 1
if self.score > 400:
if self.score > 800:
if self.score > 1200:
self.enemy.speed(18)
else:
self.enemy.speed(16)
else:
self.enemy.speed(14)
else:
self.enemy.speed(12)
self.mysprites.update()
#self.mysprites2.group_collide
#possibly sprite.groupcollide to save myself these two loops but I like the loops
#also idk if the dokill means it will .kill() or something else
for i in range(len(self.obstacles)):
if(pygame.sprite.collide_rect(self.Mycharacter, self.obstacle[i])):
self.Mycharacter.kill()
self.currentState = "end"
self.end_time = pygame.time.get_ticks()
run = False
for i in range(len(self.enemies)):
if(pygame.sprite.collide_rect(self.Mycharacter, self.enemies[i])):
self.Mycharacter.kill()
self.currentState = "end"
self.end_time = pygame.time.get_ticks()
run = False
self.screen.fill(GREEN)
#self.obstacles.draw(self.screen)
#self.enemies.draw(self.screen)
self.screen.blit(self.background, (0, 0))
self.mysprites.draw()
#self.screen.blit(self.Mycharacter,(self.Mycharacter.rect.x, self.Mycharacter.rect.y))
#drawing code
#update screen with what has been drawn
pygame.display.flip()
clock.tick(60)
pygame.quit
def endGame(self):
#self.screen = pygame.display.set_mode(800, 800)
#self.background = pygame.Surface(self.screen.get_size()).convert()
end = False
while not end:
for event in pygame.event.get():
if event == pygame.QUIT:
end = True
self.done = True
else:
line = json.load(self.file.readline())
num = line["one"]
self.file.close()
if self.time > int(num):
self.file = open("highscores.json", "w")
newstr = "highscore:"+str(num)
jsonstr = json.dump(newstr)
self.file.write(newstr)
self.file.close
self.file = open("highscores.json", "r")
newline = json.load(self.file.readline())
score = newline["highscore"]
self.file.close()
self.screen.fill((0, 0, 255))
#create text and rect to blit onto screen to display high score
self.screen.blit("Highscore: " + str(score), (350, 50))
self.screen.blit("Your Score: " + str(num), (350, 150))
pygame.display.flip()
pygame.quit
def main():
the_game = Controller()
the_game.mainLoop()
main()
The first problem that I can see is that you pass self as an argument to the different scene methods self.startGame(self) in the mainLoop. Just remove the self.
In the runGame method, you have indented a lot of your code incorrectly. The game logic and drawing code shouldn't be in the event loop but in the outer while loop. It should still draw everything, though, but only if events are in the queue (for example if the mouse gets moved).
Also, the self.background surface is just black because you never fill it with another color.
Side notes: You forgot some parentheses behind clock = pygame.time.Clock an pygame.quit.
In the endGame method, you load the json file every time an event occurs (f.e. mouse movements). You should better do that once per mouse click or something similar.
Here's a minimal example that works correctly:
import pygame
GREEN = pygame.Color('green')
class Controller:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 800))
self.background = pygame.Surface(self.screen.get_size()).convert()
self.background.fill(GREEN) # Fill the background if it shouldn't be black.
self.clock = pygame.time.Clock()
self.currentState = "start"
self.button = pygame.Rect(350, 50, 100, 100)
def mainLoop(self):
self.done = False
while not self.done:
if self.currentState == "start":
self.startGame()
elif self.currentState == "running":
self.start_time = pygame.time.get_ticks()
self.runGame()
def startGame(self):
start = True
while start:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
start = False
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
if self.button.collidepoint(mouse_pos):
self.currentState = "running"
return # Back to the mainloop
self.screen.fill((255, 0, 255))
pygame.draw.rect(self.screen, (0, 0, 110), self.button)
pygame.display.flip()
self.clock.tick(60)
def runGame(self):
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
run = False
self.screen.blit(self.background, (0, 0))
pygame.draw.rect(self.screen, (200, 0, 0), [20, 30, 50, 90])
pygame.display.flip()
self.clock.tick(60)
def main():
the_game = Controller()
the_game.mainLoop()
main()
pygame.quit()

How do I create a button that changes sprites when clicked in pygame?

I am currently having trouble with buttons in pygame. At the moment I am testing myself in pygame and I am trying to create a flappy bird type of game. What I am trying to achieve is that when I click on the play button on the main menu it will change the button sprite and run the main game.
I have managed to define the button function and get it to switch to the main game when I click. The only problem is that it doesn't show a different sprite when clicked and I can click anywhere in the application to switch to the main game instead of having to click on the button.
Any help would be much appreciated.
Thanks in Advance
import time
import random
import pygame
from pygame.locals import *
pygame.init()
#Predefined Colors
white = (255,255,255)
black = (0,0,0)
red = (200,0,0)
light_red = (255,0,0)
yellow = (200,200,0)
light_yellow = (255,255,0)
green = (34,177,76)
light_green = (0,255,0)
blue = (0,0,255)
light_blue = (0, 0, 200)
player_list = (
# Red Bird
('assets/sprites/redbird-upflap.png', 'assets/sprites/redbird-midflap.png',
'assets/sprites/redbird-downflap.png'),
# Blue Bird
('assets/sprites/bluebird-upflap.png', 'assets/sprites/bluebird-midflap.png',
'assets/sprites/bluebird-downflap.png'),
# Yellow Bird
('assets/sprites/yellowbird-upflap.png', 'assets/sprites/yellowbird-midflap.png',
'assets/sprites/yellowbird-downflap.png')
)
background_list = (
('assets/sprites/background-day.png', 'assets/sprites/background-night.png')
)
pipe_list = (
('assets/sprites/pipe-green.png', 'assets/sprites/pipe-red.png')
)
FPS = 30
images, sounds = {}, {}
def main():
global base_x, base_y, clock, gameDisplay, display_height, display_width
display_width = 288
display_height = 512
base_x = 0
base_y = display_height * 0.79
clock = pygame.time.Clock()
gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption("Flappy Bird")
#Loading icon sprite
images['Icon'] = pygame.image.load('assets/sprites/yellowbird-midflap.png')
pygame.display.set_icon(images['Icon'])
#Loading all the Numbers sprites
images['Numbers'] = (
pygame.image.load('assets/sprites/0.png').convert_alpha(),
pygame.image.load('assets/sprites/1.png').convert_alpha(),
pygame.image.load('assets/sprites/2.png').convert_alpha(),
pygame.image.load('assets/sprites/3.png').convert_alpha(),
pygame.image.load('assets/sprites/4.png').convert_alpha(),
pygame.image.load('assets/sprites/5.png').convert_alpha(),
pygame.image.load('assets/sprites/6.png').convert_alpha(),
pygame.image.load('assets/sprites/7.png').convert_alpha(),
pygame.image.load('assets/sprites/8.png').convert_alpha(),
pygame.image.load('assets/sprites/9.png').convert_alpha()
)
#Game Over Sprite
images['Game Over'] = pygame.image.load('assets/sprites/gameover.png').convert_alpha()
#Starting Game sprite
images['Starting Game'] = pygame.image.load('assets/sprites/startgame-screen.png').convert_alpha()
#Flappy Bird Logo sprite
images['Flappy Bird Logo'] = pygame.image.load('assets/sprites/flappybird-logo.png').convert_alpha()
#Base Ground sprite
images['Base Ground'] = pygame.image.load('assets/sprites/base.png').convert_alpha()
#Play Button Up sprite
images['Play Button Up'] = pygame.image.load('assets/sprites/playbutton-up.png').convert_alpha()
#Play Button Down sprite
images['Play Button Down'] = pygame.image.load('assets/sprites/playbutton-down.png').convert_alpha()
#Quit Button Up sprite
#images['Quit Button Up'] = pygame.image.load('assets/sprites/quitbutton-up.png').convert_alpha()
#Quit Button Down sprite
#images['Quit Button Down'] = pygame.image.load('assets/sprites/quitbutton-down.png').convert_alpha()
#Sounds
# sounds['Die'] = pygame.mixer.Sound('assets/audio/die.wav')
# sounds['Hit'] = pygame.mixer.Sound('assets/audio/hit.wav')
# sounds['Point'] = pygame.mixer.Sound('assets/audio/point.wav')
# sounds['swoosh'] = pygame.mixer.Sound('assets/audio/swoosh.wav')
# sounds['wing'] = pygame.mixer.Sound('assets/audio/wing.wav')
while True:
#Select random Background sprites
random_background = random.randint(0, len(background_list) - 1)
images['Background'] = pygame.image.load(background_list[random_background]).convert()
#Select random Player sprites
random_player = random.randint(0, len(player_list) - 1)
images['Player'] = (
pygame.image.load(player_list[random_player][0]).convert_alpha(),
pygame.image.load(player_list[random_player][1]).convert_alpha(),
pygame.image.load(player_list[random_player][2]).convert_alpha()
)
#Select random Pipe sprite
random_pipe = random.randint(0, len(pipe_list) - 1)
images['Pipe'] = pygame.image.load(pipe_list[random_pipe])
main_menu()
pygame.display.update()
clock.tick(FPS)
def button(action = None):
cur = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if action == 'Play':
button = images['Play Button Up'].get_rect()
for event in pygame.event.get():
if click[0] == 1:
if button.collidepoint(cur):
print ('Mouse Over')
images['Play Button Down']
main_game()
else:
gameDisplay.blit(images['Play Button Up'], (0, -10))
def main_menu():
global player_index, player_x, player_y
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'] [0].get_height()) / 2)
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Flappy Bird Logo'], (50, -30))
gameDisplay.blit(images['Player'][player_index], (125, 140))
gameDisplay.blit(images['Play Button Up'], (10, 10))
button(action = 'Play')
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_x = 0
player_y = 0
while not gameExit:
if gameOver == True:
gameDisplay.blit(images['Game Over'], 50, 50)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Starting Game'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Player'][player_index], (player_x, player_y))
pygame.display.update()
clock.tick(FPS)
main()
Here's a minimal example to demonstrate how you can switch between different button images.
When the user presses a mouse button (a pygame.MOUSEBUTTONDOWN event is added to the queue), check if the event.pos collides with the button rect and if it collides, set the image to the "down" version.
When the user releases the button (pygame.MOUSEBUTTONUP event), just set the image back to the original version.
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
GRAY = pg.Color('gray15')
BLUE = pg.Color('dodgerblue1')
LIGHTBLUE = pg.Color('lightskyblue1')
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(LIGHTBLUE)
def main():
clock = pg.time.Clock()
font = pg.font.Font(None, 30)
# Currently selected button image.
button_image = BUTTON_UP_IMG
button_rect = button_image.get_rect(topleft=(200, 200))
x = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if button_rect.collidepoint(event.pos):
button_image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
button_image = BUTTON_UP_IMG
if button_rect.collidepoint(event.pos):
print('Button pressed.')
x += 1
screen.fill(GRAY)
screen.blit(button_image, button_rect)
txt = font.render(str(x), True, BLUE)
screen.blit(txt, (260, 206))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
I would actually recommend to use classes, pygame sprites and sprite groups instead of just rects and images. Then you can easily create as many instances of the button class as you want.
import pygame as pg
pg.init()
GRAY= pg.Color('gray12')
BLUE = pg.Color('dodgerblue1')
FONT = pg.font.Font(None, 30)
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(pg.Color('lightskyblue1'))
# The Button is a pygame sprite, that means we can add the
# instances to a sprite group and then update and render them
# by calling `sprite_group.update()` and `sprite_group.draw(screen)`.
class Button(pg.sprite.Sprite):
def __init__(self, pos, callback):
pg.sprite.Sprite.__init__(self)
self.image = BUTTON_UP_IMG
self.rect = self.image.get_rect(topleft=pos)
self.callback = callback
def handle_event(self, event):
"""Handle events that get passed from the event loop."""
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if self.rect.collidepoint(event.pos):
self.image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
self.image = BUTTON_UP_IMG
if self.rect.collidepoint(event.pos):
print('Button pressed.')
# Call the function that we passed during the
# instantiation. (In this case just `increase_x`.)
self.callback()
class Game:
def __init__(self):
self.screen = pg.display.set_mode((800, 600))
self.clock = pg.time.Clock()
self.x = 0
self.buttons = pg.sprite.Group(
Button((200, 200), callback=self.increase_x),
Button((500, 200), callback=self.decrease_x))
self.done = False
# A callback function that we pass to the button instance.
# It gets called if a collision in the handle_event method
# is detected.
def increase_x(self):
"""Increase self.x if button is pressed."""
self.x += 1
def decrease_x(self):
"""Decrease self.x if button is pressed."""
self.x -= 1
def run(self):
while not self.done:
self.handle_events()
self.run_logic()
self.draw()
self.clock.tick(30)
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
for button in self.buttons:
button.handle_event(event)
def run_logic(self):
self.buttons.update()
def draw(self):
self.screen.fill(GRAY)
self.buttons.draw(self.screen)
txt = FONT.render(str(self.x), True, BLUE)
self.screen.blit(txt, (360, 206))
pg.display.flip()
if __name__ == "__main__":
Game().run()
pg.quit()
Thanks for the Answer to my previous question your answer solved my previous issue, but now I have ran into another issue.
When I press my button on the main menu it changes sprites correctly and loads the main game correctly, but as soon as I move my mouse it switches back to the main menu. My best guess is that it is because it is looping the whole button sequence, but I am not entirely sure.
import pygame
pygame.init()
display_width = 288
display_height = 512
def main_menu():
done = False
play_button_image = images['Play Button Up']
play_button_rect = play_button_image.get_rect(topleft=(30,15))
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if play_button_rect.collidepoint(event.pos):
play_button_image = images['Play Button Down']
elif event.type == MOUSEBUTTONUP:
if event.button == 1:
play_button_image = images['Play Button Up']
if play_button_rect.collidepoint(event.pos):
main_game()
gameDisplay.blit(play_button_image, play_button_rect)
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'][0].get_height()) / 2)
player_index_gen = cycle([0, 1, 2, 1])
loop_iter = 0
starting_game_x = int((display_width - images['Starting Game'].get_width()) / 2)
starting_game_y = int(display_height * 0.12)
base_x = 0
base_shift = images['Base Ground'].get_width() - images['Background'].get_width()
player_move_vals = {'val': 0, 'dir': 1}
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN or pygame.K_SPACE or pygame.K_UP:
sounds['Wing']
return {
'player_y': player_y + player_move_vals['val'],
'base_x': base_x,
'player_index_gen': player_index_gen
}
if (loop_iter + 1) % 5 == 0:
player_index = next(player_index_gen)
loop_iter = (loop_iter + 1) % 30
base_x = -((-base_x + 4) % base_shift)
player_move(player_move_vals)
# draw sprites
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Player'][player_index],
(player_x, player_y + player_move_vals['val']))
gameDisplay.blit(images['Starting Game'], (starting_game_x, starting_game_y))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
pygame.display.update()
clock.tick(FPS)
if __name__ == '__main__':
main()

Switching from game to menu then back creates lag

The game starts out working at the right speed. However, when I press escape and go back to the main menu, and then go back to the game, the game creates a little lag (same with when I get game over screen and then go back to main menu and start the game). When I keep doing that over and over again, it creates more and more lag.
In more "coding" terms, when I press Start on the menu, it calls reset(), which resets the positions of all the sprites. When there is a Game over, it shows the game over screen, and then the user presses escape to go back to the menu.
menuOn = 1
Rooms = []
running = True #Flags game as on
screen = None
(x1, y1) = (0, 0)
(x2, y2) = (0, -screenHeight)
allsprites = pygame.sprite.Group()
clock = pygame.time.Clock()
def placeholder1(screen):
Rooms = []
Rooms.append(Room())
global menuOn
menuOn = 0
pass
class Menu(object):
def __init__(self,screen):
self.screen = screen
self.title = startMenu
self.menu = genmenu(['START', lambda: placeholder1(screen)],['INFO', lambda: placeholder2(screen)], ['QUIT', lambda: quit()])
self.clock = pygame.time.Clock()
event = pygame.event.get()
self.menu.create(self.screen)
self.menu.choose(event)
self.main_loop()
def main_loop(self):
while menuOn == 1:
self.clock.tick(60)
events = pygame.event.get()
self.menu.choose(events)
self.screen.blit(self.title, (0, 0))
self.menu.create(self.screen)
pygame.display.flip()
class Room():
def __init__(self):
global wall, player, redFish, redfishes, greenFish, greenfishes, sharks, shark
for x in range(29):
wall = Wall()
wall.rect.topleft = (x*32,0) #top walls
walls.append(wall)
for x in range(29):
wall = Wall()
wall.rect.topleft = (x*32,screenHeight-32) #bottom walls
walls.append(wall)
for y in range(17):
wall = Wall()
wall.rect.topleft = (0, (y*32)+32) #left walls
walls.append(wall)
for y in range(17):
wall = Wall()
wall.rect.topleft = (screenWidth-32, (y*32)+32) #right walls
walls.append(wall)
reset()
def reset():
global playerpos
playerpos = [screenWidth/2, screenHeight/2]
player.rect.topleft = (playerpos[0], playerpos[1])
for greenFish in greenfishes:
greenFish.image = images["spr_greenfish"]
greenScoreList[greenfishes.index(greenFish)] = 0
greenFish.rect.topleft = random.randint(35,screenWidth-80),random.randint(35,screenHeight-80)
for redFish in redfishes:
redFish.rect.topleft = random.randint(35,screenWidth-80),random.randint(35,screenHeight-80)
sharks[0].rect.topleft = (100, 500)
sharks[1].rect.topleft = (300, 500)
sharks[2].rect.topleft = (500, 500)
sharks[3].rect.topleft = (700, 500)
while running:
clock.tick(60)
if menuOn == 1:
Menu(screen)
elif menuOn == 2:
GameOver(screen)
for event in pygame.event.get():
if event.type == QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == K_ESCAPE:
updateScoreToScreen(score)
menuOn = 1
reset()
allsprites.update()
screen.blit(ground, (0,screenHeight-100))
allsprites.draw(screen)
pygame.display.update()
Thanks to the comment, the solution is to put:
global walls
walls = []
in the function placeholder1. Thank you!

Resources