Pygame program is running slow. Why? - python

I'm making sort of a Tamagotchi project on pygame, and at this early stage the program is running really slow. Do you have any hints as to why?
Also, is there any way to speed it up?
This is my code so far:
import pygame
import time
pygame.init()
def draw_grid(grid):
for i in range(0,len(grid)):
for j in range(0,len(grid[i])):
area = (j*10, i*10, 8, 8)
if grid[i][j] == 0:
fill = True
color = 0,0,0
elif grid[i][j] == 1:
fill = False
color = 0,0,0
elif grid[i][j] == 2:
fill = False
color = 255,0,0
square = pygame.draw.rect(screen, color, area, fill)
def make_empty_grid(width,height):
grid = []
for i in range(height):
zeros = []
for j in range(width):
zeros.append(0)
grid.append(zeros)
return grid
def draw_item(item, x, y):
for i in range(0, len(item)):
for j in range(0, len(item[i])):
grid[i + x][j + y] = item[i][j]
size = width, height = 600, 400
#rects =
screen = pygame.display.set_mode(size)
running = True
#beige background
background = 213, 230, 172
black = 0, 0, 0
red = 255, 0, 0
image1 = [
[1,0,1,1,1,0,1],
[0,1,0,0,0,1,0],
[1,0,1,0,1,0,1],
[1,0,0,0,0,0,1],
[1,0,1,1,1,0,1],
[1,0,0,0,0,0,1],
[0,1,1,1,1,1,0]
]
image2 = [
[1,0,1,1,1,0,1],
[0,1,0,0,0,1,0],
[1,0,1,0,1,0,1],
[1,0,0,0,0,0,1],
[1,0,0,1,0,0,1],
[1,0,0,0,0,0,1],
[0,1,1,1,1,1,0]
]
bars = [
[2,2,2,2,2,2,2,2,2,2],
[2,2,2,2,2,2,2,2,2,2]
]
tamaPosX = 27
tamaPosY = 8
curImage = image1
while running:
# Get the window input
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
tamaPosX -= 1
if event.key == pygame.K_RIGHT:
tamaPosX += 1
if event.key == pygame.K_UP:
tamaPosY -= 1
if event.key == pygame.K_DOWN:
tamaPosY += 1
screen.fill(background)
grid = make_empty_grid(width,height)
if curImage is image1:
curImage = image2
else:
curImage = image1
draw_item(curImage, tamaPosY, tamaPosX)
draw_item(bars, 35, 1)
draw_grid(grid)
pygame.display.flip()

You could try profiling the code:
https://docs.python.org/2/library/profile.html
Mostly I expect it's because you have many nested for loops. Some of them seem unnecessary. Nested for loops are not fast in Python.
Your width and height are constant, you don't really need to create a whole new empty grid at each tick.
import copy
empty_grid = [[0] * width for _ in range(height)]
def make_empty_grid():
return copy.deepcopy(empty_grid)
Instead of copying your items into the grid, then drawing the grid, why not have draw_item call pygame.draw directly? As it is you are iterating over the entire grid twice - once to put the items in the grid, once the draw the grid.
Edit: That's maybe a bad suggestion, I see that even the grid elements with value 0 are drawn a different colour than the background. I'm not sure what you're doing.

You don't need to recalculate your empty grid each time. Store it in a surface as a "background image", then you can just blit it to the screen each time instead of clearing it, recalculating it, and then blitting it. Also when drawing items just draw items not the whole screen. You can use display.update and pass the items rects old and new since they are the only things that are changing.
You should also store your items as surfaces or sprites instead of recalculating them every time through the loop too.

Related

Trouble making a Simple video renderer with the python module Pygame

I need some help creating a video renderer in Pygame. It has a 48x36 display and has 3 colours. Black, white and grey. The data that goes into the project is in the form of a .txt file and each frame of the video in a line(as an example my video uses 6567 frames so there are 6567 lines in the text file). To render the frames I am using the rectangle function.
pygame.draw.rect(canvas, rect_color, pygame.Rect(30,30,60,60))
(By the way in the .txt file 0=black, 1=white and 2=grey)
After finally making a window pop up it stayed a rather boring black color...
After finally making a window pop up it stayed a rather boring black color...
If you could give any help it would be greatly needed!
(The code is messy i know)
import time
from datetime import datetime
import pygame
file= open(r"C:\Users\User\Downloads\video Data.txt","r")
lines = file.readlines()
current_l = 1
start_time = time.perf_counter()
pygame.init()
surface = pygame.display.set_mode((480,360))
color = (150,75,75)
def start_vid():
current_l = 1
for frame in range(1, 6572):
xpos = 0
ypos = 0
now = datetime.now()
count = 0
seconds = now.second
frame_data = lines[current_l]
current = frame_data[count]
for y in range(0, 36):
for x in range(0, 48):
if current == '0':
pygame.draw.rect(surface, (0, 0, 255),[xpos, xpos+10, ypos, ypos+10], 0)
elif current == '1':
pygame.draw.rect(surface, (255, 255, 255),[xpos, ypos, xpos, ypos], 0)
else:
pygame.draw.rect(surface, (130, 130, 130),[xpos, ypos, xpos, ypos], 0)
#print(current)
#pygame.display.update()
xpos = xpos + 10
current = frame_data[count]
count = count + 1
timer = round(abs((start_time - time.perf_counter())), 1)
current_l = seconds*30
current_l = int(timer*30)
ypos = ypos + -10
print(current_l)
pygame.display.update()
start_vid()
The reason why you can't see anything is because in Pygame, the origin (0, 0) of the screen is in the top left. The right side of the screen is where the x increases, and the bottom is where the y increases. So, the line:
ypos = ypos + -10
draws everything "above" the screen, hence invisible. You need to remove the - sign.
I also saw a couple of things in your code that could be improved, such as:
The fact that you never close the data file. To do that automatically, you could use the with statement:
with open('video.txt') as file:
lines = file.readlines()
# the file is closed
This will allow other applications to access the file.
You are drawing empty rects
You are using the pygame.draw.rect method incorrectly. You should use rect objects like such:
rect = pygame.Rect(x, y, width, height)
pygame.draw.rect(surface, color, rect)
You don't need the time and datetime modules, Pygame handles time like this. For example:
framerate = 30
start = pygame.time.get_ticks()
... # main loop
current_frame = int((pygame.time.get_ticks()-start) / 1000 * framerate)
And, most importantly
In Pygame, you need a main loop with events handling:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
...
pygame.quit()
quit()
A correct implementation of your code could look like this:
import pygame
with open('video.txt') as file:
lines = file.readlines()
pygame.init()
surface = pygame.display.set_mode((480,360))
framerate = 30
start = pygame.time.get_ticks()
run = True
while run:
# get pygame events
for event in pygame.event.get():
if event.type == pygame.QUIT: # user closes the window
run = False
current_frame = int((pygame.time.get_ticks()-start) / 1000 * framerate)
if current_frame < len(lines): # is the video still running?
count = 0
for y in range(36):
for x in range(48):
current_pixel = lines[current_frame][count] # current pixel data
if current_pixel == '0':
color = (0, 0, 0)
elif current_pixel == '1':
color = (127, 127, 127)
else:
color = (255, 255, 255)
pygame.draw.rect(surface, color, pygame.Rect(x*10, y*10, 10, 10))
count += 1 # next pixel
pygame.display.flip()
# user closed the window
pygame.quit()
quit()
(In this example, you can reduce the amount of resources used since you know the framerate of the video using pygame.time.Clock)

Making a clickable grid using Pygame

I'm making a minesweeper game using python. I have the game working in a text only form and I'm adding a GUI now.
I have three difficulty options with different dimensions. Is there any way to generate a grid without having to make it manually. This is how I generated a square matrix in text form:
for count in range(side):
count2 = 0
temp1 = []
temp2 = []
for count2 in range(side):
temp1.append(0)
temp2.append("x")
grid.append(temp1)
field.append(temp2)
count+=1
Is there any way to automatically generate a grid like this in pygame? I am also using custom sprites and every cell needs to be clickable. Will I just have to make the grids manually and use them according to the difficulty?
I came across a similar issue, here is the solution I found worked well and is fairly simple to implement.
gridstate = {}
class Button:
def __init__(self, x, y, w, h, *get_co, action=False):
self.x = x
self.y = y
self.w = w
self.h = h
self.get_co = get_co
self.colour = (255, 255, 255)
self.clicked = False
self.box = py.draw.rect(screen, self.colour, (x, y, w, h))
self.action = action
def draw(self):
pos = py.mouse.get_pos()
if self.box.collidepoint(pos):
if py.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
if self.action == False:
self.action = True
self.colour = (255, 0, 0)
elif self.action == True:
self.action = False
self.colour = (255, 255, 255)
if py.mouse.get_pressed()[0] == 0:
self.clicked = False
self.box = py.draw.rect(screen, self.colour,
(self.x, self.y, self.w, self.h))
gridstate.update({self.get_co: self.action})
return self.action
I created the class for the button as above, this accepts the argument "get_co" to which I put the 'co ordinate' relative to the rest of the grid so I am able to identify which button was pressed. I pass in the data in like so - This creates a 10x10 grid
resx = 10
resy = 10
buttonsize = 25
grid = []
for x in range(resx):
for y in range(resy):
grid.append(Button((x*buttonsize), (y*buttonsize),
buttonsize, buttonsize, x, y))
Within the loop of game I simply run
activecells = []
for key, value in gridstate.items():
if value == True:
activecells.append(key)
To find the clicked buttons and add them to a list to reference later.
for row in grid:
row.draw()
To draw the grid to the screen.
This seemed to do the trick for me. Apologies ahead of time for any actual programmers who will probably be horrified by my variable naming!
There is no simple solution to draw a grid. You have to loop over each cell as you did in your example. You can find all functions to draw in the pygame wiki.
Example for grid creation:
size = (100, 100)
cell_width = 10
cell_height = 10
grid_surface = pygame.Surface(size) # here you draw
grid = [] # simple list to save your data
for x in range(side):
grid.append([]) # new column for the list
for y in range(side):
# save data to grid
grid[x][y] = "x"
# draw something
# e.g.: this draws a red rectangle in each cell
pygame.draw.rect(grid_surface, (200, 0, 0), (x * cell_width, y * cell_height, cell_width, cell_height)
Potential event loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = (event.pos[0] // cell_width, event.pos[1] // cell_height)
# do something
print(grid[x][y])
grid[x][y] = "a"
pygame.draw.rect(grid_surface, (0, 200, 0), (x * cell_width, y * cell_height, cell_width, cell_height)
I will give you a very simple start. You should create a class that represents a cell in the grid. Create a matrix of cells to represent your board:
class Cell:
def __init__(self):
self.clicked = False
grid_size = 10
board = [[Cell() for _ in range(grid_size)] for _ in range(grid_size)]
Calculate the index of a cell when the mouse button is clicked and change the attributes in the cell:
for event in pygame.event.get():
# [...]
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
row = event.pos[1] // 20
col = event.pos[0] // 20
board[row][col].clicked = True
Draw the board in 2 nested loops:
for iy, rowOfCells in enumerate(board):
for ix, cell in enumerate(rowOfCells):
color = (64, 64, 64) if cell.clicked else (164, 164, 164)
pygame.draw.rect(window, color, (ix*20, iy*20, 20, 20))
Minimal example:
import pygame
class Cell:
def __init__(self):
self.clicked = False
grid_size = 10
board = [[Cell() for _ in range(grid_size)] for _ in range(grid_size)]
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
row = event.pos[1] // 20
col = event.pos[0] // 20
board[row][col].clicked = True
window.fill(0)
for iy, rowOfCells in enumerate(board):
for ix, cell in enumerate(rowOfCells):
color = (64, 64, 64) if cell.clicked else (164, 164, 164)
pygame.draw.rect(window, color, (ix*20+1, iy*20+1, 18, 18))
pygame.display.flip()
pygame.quit()
exit()

How to fix pygame blinking gemetric shape

Hi I want to make a game with rhombus what I want to fix is that I set the clock to XY and in while true cycles I have function with one built me a cube
I want to create a game with levels on each level a cube will be made again with another random variation of colors. I have not levels method created yet so I want to create a cube with some colors and after a restarting code, It should be another color variation. But I can not fix a blinking.
my code:
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode((500, 800))
clock = pg.time.Clock()
BG_COLOR = pg.Color('black')
WHITE = pg.Color('white')
ORANGE = pg.Color('orange')
BLUE = pg.Color('blue')
RED = pg.Color('red')
PURPLE = pg.Color('purple')
m = 8
def rhumbus(screen,color,plus):
point1=(170, 500-plus)
point2=(350, 500-plus)
point3=(300, 550-plus)
point4=(120, 550-plus)
points=[point1, point2, point3, point4]
return pg.draw.polygon(screen,color, points)
def cube(rando):
plus = -72
old_col = None
for x in range(0,10):
rando = random.choice([RED,ORANGE,RED,BLUE,WHITE])
if rando != old_col:
rhumbus(screen,rando,plus)
plus += 8
old_col = rando
else:
rhumbus(screen,PURPLE,plus)
plus += 8
old_col = rando
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
screen.fill(BG_COLOR)
cube(m)
pg.display.flip()
clock.tick(10)
Instead of choosing a random sequence of colors on each frame, pick them beforehand.
The new make_random_sequence function returns a list chosen randomly from the given sequence, making sure there are no consecutive repeats of the same item (which seemed to be your logic with old_col too?)
You were not actually using the rando parameter given to cube.
Using enumerate to loop over the colors and their indices is shorter than dealing with a plus variable.
import pygame as pg
import random
pg.init()
screen = pg.display.set_mode((500, 800))
clock = pg.time.Clock()
BG_COLOR = pg.Color('black')
WHITE = pg.Color('white')
ORANGE = pg.Color('orange')
BLUE = pg.Color('blue')
RED = pg.Color('red')
PURPLE = pg.Color('purple')
def rhumbus(screen, color, plus):
point1 = (170, 500 - plus)
point2 = (350, 500 - plus)
point3 = (300, 550 - plus)
point4 = (120, 550 - plus)
points = [point1, point2, point3, point4]
return pg.draw.polygon(screen, color, points)
def make_random_sequence(options, n):
seq = []
while len(seq) < n:
choice = random.choice(options)
if seq and choice == seq[-1]: # skip if same as last
continue
seq.append(choice)
return seq
def cube(colors):
for i, color in enumerate(colors):
rhumbus(screen, color, -72 + i * 8)
colors = make_random_sequence([RED, ORANGE, BLUE, WHITE], 8)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
screen.fill(BG_COLOR)
cube(colors)
pg.display.flip()
clock.tick(10)

Change element in matrix from 0 to 1 when event takes place

I've recently written a program with a list, but now I need to change the list into a matrix. I programmed a grid by drawing rectangles. Now I want to change the color of the rectangle when I click on it. In my program with the list everything worked just fine, but now I have to use a matrix, because I need a matrix for the rest of my program. I've already got a matrix with all zeros, but now I want to change the 0 in to a 1 when I click on a rectangle.
x = 5
y = 5
height = 30
width = 50
size = 20
color = (255,255,255)
new_color = (0,255,0)
screen.fill((0,0,0))
def draw_grid():
for y in range(height):
for x in range(width):
rect = pygame.Rect(x * (size + 1),y * (size + 1),size,size)
pygame.draw.rect(screen,color,rect)
x += 20
y += 20
rects = [[0 for i in range(width)] for j in range(height)]
draw_grid()
while 1:
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if menu == 'start':
if pygame.mouse.get_pressed()[0]:
mouse_pos = pygame.mouse.get_pos()
for i,(rect,color) in enumerate(rects):
if rect.collidepoint(mouse_pos):
rects[i] = (rect,new_color)
for rect,color in rects:
pygame.draw.rect(screen,color,rect)
pygame.display.flip()
This is the code I used with the list, but I've already replaced the list with a matrix. When I run this code it gives an error:
ValueError: too many values to unpack
What is the best way to solve this problem?
To draw the rects you can iterate over the matrix and depending on the value (0 or 1) draw a white rect or a green rect. (You could also store the colors directly in the matrix, but I don't know if you want to do something else with it.)
To change the color of the clicked cells, you can easily calculate the indexes of the cells by floor dividing the mouse coords by (size+1), e.g. x = mouse_x // (size+1). Then just set matrix[y][x] = 1.
import sys
import pygame
WHITE = pygame.Color('white')
GREEN = pygame.Color('green')
def draw_grid(screen, matrix, size):
"""Draw rectangles onto the screen to create a grid."""
# Iterate over the matrix. First rows then columns.
for y, row in enumerate(matrix):
for x, color in enumerate(row):
rect = pygame.Rect(x*(size+1), y*(size+1), size, size)
# If the color is white ...
if color == 0:
pygame.draw.rect(screen, WHITE, rect)
# If the color is green ...
elif color == 1:
pygame.draw.rect(screen, GREEN, rect)
def main():
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
height = 30
width = 50
size = 20 # Cell size.
matrix = [[0 for i in range(width)] for j in range(height)]
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if pygame.mouse.get_pressed()[0]:
# To change the color, calculate the indexes
# of the clicked cell like so:
mouse_x, mouse_y = pygame.mouse.get_pos()
x = mouse_x // (size+1)
y = mouse_y // (size+1)
matrix[y][x] = 1
screen.fill((30, 30, 30))
# Now draw the grid. Pass all needed values to the function.
draw_grid(screen, matrix, size)
pygame.display.flip()
clock.tick(30)
if __name__ == '__main__':
pygame.init()
main()
pygame.quit()
sys.exit()

I get an incorrect position from .get_rect()?

I'm currently working on a school project where I'm making a "hexcells" similar game in pygame and now I'm trying to blit an a new image if the user has clicked a current image. It will blit an image in the top left area, if clicked in the top left area, but not if I click any of the existing images. I told the program to print the coordinates from the images with help of the .get_rect() function, but it remains the same whereever I click and the coordinates aren't even where a image is. Can someone help me understand how this works and help me blit the new images on top of the existing images? Code below is not the entire document, however there is so much garbage/trash/unused code so I'd thought I spare you the time of looking at irrelevant code. Also sorry if the formatting is wrong or the information isn't enough, I tried my best.
import pygame, sys
from pygame.locals import *
#Magic numbers
fps = 30
winW = 640
winH = 480
boxSize = 40
gapSize = 75
boardW = 3
boardH = 3
xMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
yMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
#Lil bit o' color R G B
NAVYBLUE = ( 60, 60, 100)
correctCords = [[175,275,375],[375,275,175]]
bgColor = NAVYBLUE
unC = pygame.image.load("unC.png")
cor = pygame.image.load("correct.png")
inc = pygame.image.load("wrong.png")
correct = "Correct"
inCorrect = "Incorrect"
def main():
global FPSCLOCK, DISPLAYSURF
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((winW, winH))
mousex = 0 #stores x-coordinate of mouse event
mousey = 0 #stores y-coordinate of mouse event
pygame.display.set_caption("Branches")
DISPLAYSURF.fill(bgColor)
gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize)
while True:
mouseClicked = False
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION:
mousex,mousey = event.pos
elif event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
pos = pygame.mouse.get_pos()
mouseClicked = True
unCa = unC.get_rect()
corA = cor.get_rect()
print unCa
print corA
print pos
if unCa.collidepoint(pos):
DISPLAYSURF.blit(cor,(mousey,mousex))
"""lada = unC.get_rect()
lada =
if mousex and mousey == lada:
for x in correctCords:
for y in x:
for z in x:
if mousey and mousex == z and y:
DISPLAYSURF.blit(cor,(mousey,mousex))
print lada"""
pygame.display.update()
FPSCLOCK.tick(fps)
def gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize):
grid = []
cordX = []
cordY = []
correctRecs = []
#cordinates = []
#cordinates.append([])
#cordinates.append([])
#cordinates.append([])
#this is basically getBoard() all over again
#This part will arrange the actual backend grid
for row in range(3):
grid.append([])
#cordinates[0].append(gapSize+(row+1)*100)
#cordinates[1].append(gapSize+(row+1)*100)
#cordinates[2].append(gapSize+(row+1)*100)
for column in range(3):
grid[row].append(inCorrect)
for row in range(3):
cordX.append([])
for column in range(3):
cordX[row].append(gapSize+(row+1)*100)
for row in range(3):
cordY.append([])
for column in range(3):
cordY[row].append(gapSize+(column+1)*100)
#print cordX[0][0], cordY[0][0]
grid[0][2] = correct
grid[1][1] = correct
grid[2][0] = correct
#Y-AXEL SKRIVS FoRST ([Y][X])
#print cordinates[2][1]
DISPLAYSURF.blit(cor,(100,100))
#Let's draw it as well
for row in range(3):
for column in range(3):
DISPLAYSURF.blit(unC,(gapSize+(row+1)*100,gapSize+(column+1)*100))
main()
Also real sorry about the horrible variable naming and occasional swedish comments.
unCa = unC.get_rect() gives you only image size - so use it only once at start (before while True) - and later use the same unCa all the time to keep image position and change it.
btw: better use more readable names - like unC_rect
ie.
# move 10 pixel to the right
unC_rect.x += 10
# set new position
unC_rect.x = 10
unC_rect.right = 100
unC_rect.topleft = (10, 200)
unC_rect.center = (10, 200)
# center on screen
unC_rect.center = DISPLAYSURF.get_rect().center
etc.
And then use this rect to blit image
blit(unC, unC_rect)
and check collision with other rect
if unC_rect.colliderect(other_rect):
or with point - like mouse position
elif event.type == MOUSEMOTION:
if unC_rect.collidepoint(pygame.mouse.get_pos()):
hover = True
# shorter
elif event.type == MOUSEMOTION:
hover = unC_rect.collidepoint(pygame.mouse.get_pos()):

Categories

Resources