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()
Related
I'm approaching PyGame but I stepped on a problem: I am trying to draw a grid made out of single rectangle drawings, I want each one of those rectangle to fill or empty when the left mouse button is released on said rectangle. The grid i represented by a matrix of tuples each one representing each rectangle formatted like this (column, row) to be in line with pygame pixel's coordinates, then I create another matrix of tuples to convert from mouse position tuples (colpixel, rowpixel) to rectangle tuples, I also created a dictionary that pairs rectangle tuples as keys with a value that can be 1 or 0, that value represents whether the rectangle is filled or not, that is the value that I change when the mouse button is released. Here is the code:
import pygame
pygame.init()
SCREEN_WIDTH = 40
SCREEN_HEIGHT = 20
DIM = 20
ROWS = int(SCREEN_HEIGHT / DIM)
COLUMNS = int(SCREEN_WIDTH / DIM)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Game of Life')
# filling rect_matrix with tuple, each one representing a rectangle of the grid (columns, rows).
# filling rect_map by pairing the tuples from rect_matrix with a value that can be 0 or 1, default 1.
rect_matrix = []
rect_map = {}
for i in range(ROWS):
temp = []
for j in range(COLUMNS):
temp.append(pygame.Rect((j * DIM, i * DIM), (DIM, DIM)))
rect_map[(j, i)] = 1
rect_matrix.append(temp)
# filling mouse_to_rectangle, sort of a decoder to convert from groups of tuples
# (representing mouse coordinates in pixels) to a single tuple (representing single rectangles)
mouse_to_rectangle = []
ii = 0
for i in range(SCREEN_WIDTH):
jj = 0
temp = []
for j in range(SCREEN_HEIGHT):
temp.append((ii, jj))
if ((j + 1) % DIM == 0):
jj += 1
if ((i + 1) % DIM == 0):
ii += 1
mouse_to_rectangle.append(temp)
is_running = True
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONUP:
if rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] == 1:
rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] = 0
else:
rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] = 1
for i, ii in zip(rect_matrix, range(len(rect_matrix))):
for j, jj in zip(i, range(len(i))):
pygame.draw.rect(screen, (100, 100, 100), j, rect_map[(jj, ii)])
pygame.display.update()
I managed to fill (relative value in rect_map goes to 1) a rectangle upon releasing the left button when pointing it but when I try to click again on a rectangle that is now filled it won't empty. I represented the "filled or not" property with ones or zeroes beacuse the rectangle is filled when the width parameter in pygame.draw.rect is set to 0, if a >0 value is used then the rectangle will have a border as thick as the specified value in pixel.
You missed to clear the display before drawing the grid. Thus all the rectangles which hav been draw stayed there for ever:
screen.fill((0, 0, 0))
Anyway you've over complicated the algorithm. The position in the grind can be computed by dividing the components of the mouse position by DIM (// (floor division) operator):
mousepos = pygame.mouse.get_pos()
gridpos = mousepos[0] // DIM, mousepos[1] // DIM
Finally it the grid state can be changed with ease and mouse_to_rectangle is not need at all:
if rect_map[gridpos] == 1:
rect_map[gridpos] = 0
else:
rect_map[gridpos] = 1
See the example:
import pygame
pygame.init()
SCREEN_WIDTH = 200
SCREEN_HEIGHT = 200
DIM = 20
ROWS = int(SCREEN_HEIGHT / DIM)
COLUMNS = int(SCREEN_WIDTH / DIM)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Game of Life')
# filling rect_matrix with tuple, each one representing a rectangle of the grid (columns, rows).
# filling rect_map by pairing the tuples from rect_matrix with a value that can be 0 or 1, default 1.
rect_matrix = []
rect_map = {}
for i in range(ROWS):
temp = []
for j in range(COLUMNS):
temp.append(pygame.Rect((j * DIM, i * DIM), (DIM, DIM)))
rect_map[(j, i)] = 1
rect_matrix.append(temp)
is_running = True
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONUP:
mousepos = pygame.mouse.get_pos()
gridpos = mousepos[0] // DIM, mousepos[1] // DIM
if rect_map[gridpos] == 1:
rect_map[gridpos] = 0
else:
rect_map[gridpos] = 1
screen.fill((0, 0, 0))
for i, ii in zip(rect_matrix, range(len(rect_matrix))):
for j, jj in zip(i, range(len(i))):
pygame.draw.rect(screen, (100, 100, 100), j, rect_map[(jj, ii)])
pygame.display.update()
I'm approaching PyGame but I stepped on a problem: I am trying to draw a grid made out of single rectangle drawings, I want each one of those rectangle to fill or empty when the left mouse button is released on said rectangle. The grid i represented by a matrix of tuples each one representing each rectangle formatted like this (column, row) to be in line with pygame pixel's coordinates, then I create another matrix of tuples to convert from mouse position tuples (colpixel, rowpixel) to rectangle tuples, I also created a dictionary that pairs rectangle tuples as keys with a value that can be 1 or 0, that value represents whether the rectangle is filled or not, that is the value that I change when the mouse button is released. Here is the code:
import pygame
pygame.init()
SCREEN_WIDTH = 40
SCREEN_HEIGHT = 20
DIM = 20
ROWS = int(SCREEN_HEIGHT / DIM)
COLUMNS = int(SCREEN_WIDTH / DIM)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Game of Life')
# filling rect_matrix with tuple, each one representing a rectangle of the grid (columns, rows).
# filling rect_map by pairing the tuples from rect_matrix with a value that can be 0 or 1, default 1.
rect_matrix = []
rect_map = {}
for i in range(ROWS):
temp = []
for j in range(COLUMNS):
temp.append(pygame.Rect((j * DIM, i * DIM), (DIM, DIM)))
rect_map[(j, i)] = 1
rect_matrix.append(temp)
# filling mouse_to_rectangle, sort of a decoder to convert from groups of tuples
# (representing mouse coordinates in pixels) to a single tuple (representing single rectangles)
mouse_to_rectangle = []
ii = 0
for i in range(SCREEN_WIDTH):
jj = 0
temp = []
for j in range(SCREEN_HEIGHT):
temp.append((ii, jj))
if ((j + 1) % DIM == 0):
jj += 1
if ((i + 1) % DIM == 0):
ii += 1
mouse_to_rectangle.append(temp)
is_running = True
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONUP:
if rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] == 1:
rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] = 0
else:
rect_map[mouse_to_rectangle[pygame.mouse.get_pos()[0]][pygame.mouse.get_pos()[1]]] = 1
for i, ii in zip(rect_matrix, range(len(rect_matrix))):
for j, jj in zip(i, range(len(i))):
pygame.draw.rect(screen, (100, 100, 100), j, rect_map[(jj, ii)])
pygame.display.update()
I managed to fill (relative value in rect_map goes to 1) a rectangle upon releasing the left button when pointing it but when I try to click again on a rectangle that is now filled it won't empty. I represented the "filled or not" property with ones or zeroes beacuse the rectangle is filled when the width parameter in pygame.draw.rect is set to 0, if a >0 value is used then the rectangle will have a border as thick as the specified value in pixel.
You missed to clear the display before drawing the grid. Thus all the rectangles which hav been draw stayed there for ever:
screen.fill((0, 0, 0))
Anyway you've over complicated the algorithm. The position in the grind can be computed by dividing the components of the mouse position by DIM (// (floor division) operator):
mousepos = pygame.mouse.get_pos()
gridpos = mousepos[0] // DIM, mousepos[1] // DIM
Finally it the grid state can be changed with ease and mouse_to_rectangle is not need at all:
if rect_map[gridpos] == 1:
rect_map[gridpos] = 0
else:
rect_map[gridpos] = 1
See the example:
import pygame
pygame.init()
SCREEN_WIDTH = 200
SCREEN_HEIGHT = 200
DIM = 20
ROWS = int(SCREEN_HEIGHT / DIM)
COLUMNS = int(SCREEN_WIDTH / DIM)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Game of Life')
# filling rect_matrix with tuple, each one representing a rectangle of the grid (columns, rows).
# filling rect_map by pairing the tuples from rect_matrix with a value that can be 0 or 1, default 1.
rect_matrix = []
rect_map = {}
for i in range(ROWS):
temp = []
for j in range(COLUMNS):
temp.append(pygame.Rect((j * DIM, i * DIM), (DIM, DIM)))
rect_map[(j, i)] = 1
rect_matrix.append(temp)
is_running = True
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
pygame.quit()
exit()
if event.type == pygame.MOUSEBUTTONUP:
mousepos = pygame.mouse.get_pos()
gridpos = mousepos[0] // DIM, mousepos[1] // DIM
if rect_map[gridpos] == 1:
rect_map[gridpos] = 0
else:
rect_map[gridpos] = 1
screen.fill((0, 0, 0))
for i, ii in zip(rect_matrix, range(len(rect_matrix))):
for j, jj in zip(i, range(len(i))):
pygame.draw.rect(screen, (100, 100, 100), j, rect_map[(jj, ii)])
pygame.display.update()
I've already programmed a grid, but now I want to change the color of a single rectangle in the grid.
x = 5
y = 5
height = 30
width = 50
size = 20
color = (255,255,255)
new_color = (255,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
rects.append((rect,color))
y += 20
rects = []
colored_rects = []
while 1:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
draw_grid()
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)
colored_rects.append((rect,new_color))
for rect,color in rects:
pygame.draw.rect(screen,color,rect)
for rect,new_color in colored_rects:
pygame.draw.rect(screen,new_color,rect)
pygame.display.flip()
clock.tick()
Now I only want to change one rectangle when I click on it, but later they must change automatically (for example when there are three rectangles touching in the same color, they all must become white). I've updated a little bit, but there are still some problems. For example: You have to click on the rectangle till it changes color, and it takes to much time te change color.
One solution would be to store the rects together with their color in tuples. If the mouse button is pressed, you iterate over the rectangles list, if a rectangle collides with the mouse, you create a tuple with the rect and the new color and replace the tuple at the current index.
import sys
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
height = 30
width = 50
size = 20
color = (255, 255, 255)
new_color = (255, 255, 0)
rectangles = []
for y in range(height):
for x in range(width):
rect = pg.Rect(x * (size+1), y * (size+1), size, size)
# The grid will be a list of (rect, color) tuples.
rectangles.append((rect, color))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if pg.mouse.get_pressed()[0]:
mouse_pos = pg.mouse.get_pos()
# Enumerate creates tuples of a number (the index)
# and the rect-color tuple, so it looks like:
# (0, (<rect(0, 0, 20, 20)>, (255, 255, 255)))
# You can unpack them directly in the head of the loop.
for index, (rect, color) in enumerate(rectangles):
if rect.collidepoint(mouse_pos):
# Create a tuple with the new color and assign it.
rectangles[index] = (rect, new_color)
screen.fill((30, 30, 30))
# Now draw the rects. You can unpack the tuples
# again directly in the head of the for loop.
for rect, color in rectangles:
pg.draw.rect(screen, color, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
I'm trying to make a heat bar which grows every time I press 'x' and I can't figure out how. What can I do?
heatBar = [45, 30]
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_x:
for pos in heatBar:
pygame.draw.rect(DISPLAYSURF, GREEN,(pos[0],pos[1],10,50))
You can pass an area argument to Surface.blit. The area has to be a rect or rect-style tuple (x_coord, y_coord, width, height) and allows you to control the visible area of the surface. So if you have a surface that is 150 pixels wide and pass a rect with a width of 100, then the surface will only be rendered up to the 100 pixel border.
Now just use the heat value (which is the percentage of the image width) to calculate the current width, heat_rect.w/100*heat, and pass it to the blit method.
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
BG_COLOR = pg.Color(30, 30, 50)
HEAT_BAR_IMAGE = pg.Surface((150, 20))
color = pg.Color(0, 255, 0)
# Fill the image with a simple gradient.
for x in range(HEAT_BAR_IMAGE.get_width()):
for y in range(HEAT_BAR_IMAGE.get_height()):
HEAT_BAR_IMAGE.set_at((x, y), color)
if color.r < 254:
color.r += 2
if color.g > 1:
color.g -= 2
def main():
clock = pg.time.Clock()
heat_rect = HEAT_BAR_IMAGE.get_rect(topleft=(200, 100))
# `heat` is the percentage of the surface's width and
# is used to calculate the visible area of the image.
heat = 5 # 5% of the image are already visible.
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
if keys[pg.K_x]:
heat += 4 # Now 4% more are visible.
heat -= 1 # Reduce the heat every frame.
heat = max(1, min(heat, 100)) # Clamp the value between 1 and 100.
screen.fill(BG_COLOR)
screen.blit(
HEAT_BAR_IMAGE,
heat_rect,
# Pass a rect or tuple as the `area` argument.
# Use the `heat` percentage to calculate the current width.
(0, 0, heat_rect.w/100*heat, heat_rect.h)
)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
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.