Python 2D Array - Referencing specific part of grid - python

I have a 10x10 grid currently filled with zeros and rendered to the screen using Pygame's freetype module.
How would I render the list contents in my for loop rather than hardcode a '0' string?
import pygame
import pygame.freetype
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
# Create a 2 dimensional array. A two dimensional
# array is simply a list of lists.
grid = [[0 for x in range(10)] for y in range(10)]
# Initialize pygame
pygame.init()
# Set the HEIGHT and WIDTH of the screen
WINDOW_SIZE = [255, 255]
screen = pygame.display.set_mode(WINDOW_SIZE)
# Loop until the user clicks the close button.
done = False
# -------- Main Program Loop -----------
while not done:
# Set the screen background
screen.fill(BLACK)
# Draw the grid
for row in range(10):
for column in range(10):
pygame.freetype.Font(None, 32).render_to(screen, (column * 32, row * 32), '0', WHITE)
pygame.display.flip()
pygame.quit()

You can use enumerate here to get the coordinates of your grid with any size you want:
for i, row in enumerate(grid):
for j, item in enumerate(row):
pygame.freetype.Font(None, 32).render_to(screen, (j * 32, i * 32), str(item), WHITE)
This way you don't need to know the size of the grid upfront.
Then simply get the string representation of cell item with str().

Related

How do I color in specific blocks for a chess board? [duplicate]

This question already has an answer here:
Nested for loop chess board coloring not working Python
(1 answer)
Closed 1 year ago.
I made a chess board, but I need help with coloring the different squares. I only have white squares but I need black squares. Here is my code.
import pygame
def board():
width=480 # measurements for the window
height=480
block_size=59
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
for y in range(height):
for x in range(width):
rect = pygame.Rect(x*(block_size+1), y*(block_size+1), block_size, block_size)
pygame.draw.rect(window, (255,255,255), rect) # Leaves space for lines to be visible.
pygame.display.flip()
board()
# It's just the board.
I already know I violated PEP 8.
You can do it like: I changed your coord-manipulation to benefit directly from the given range values not using a mult inside it. Color is flipped on each rect drawn and also on line-change to get alternating row colorings:
import pygame
def board():
def flipColor(color):
white = (240,240,240)
black = (30,30,30)
if not color or color == white:
color = black
else:
color = white
return color
width=480 # measurements for the window
height=480
block_size= 60
window = pygame.display.set_mode((width,height))
background_color = (0,0,0) # This is how I make the lines
window.fill(background_color)
c = None
pygame.draw.rect(window,(255,0,0),pygame.Rect(0,0,width,height)) # red background
for y in range(0,height,block_size):
c = flipColor(c)
for x in range(0,width,block_size):
c = flipColor(c)
rect = pygame.Rect(x , y , x+block_size , y+block_size )
pygame.draw.rect(window, c , rect, 0) # Leaves space for lines to be visible.
for i in range(0,height+1,block_size):
pygame.draw.line(window,(233,33,187),(i,0),(i,width),2)
pygame.draw.line(window,(233,33,187),(0,i),(height,i),2)
pygame.draw.line(window,(233,33,187),(height-2,0),(height-2,width),2) # fix for out of window line
pygame.draw.line(window,(233,33,187),(0,width-2),(height,width-2),2) # fix for out of wondow line
pygame.display.flip()
board()
You can also use itertools.cycle, pass an iterable with the colors and then just call next to cycle through them. I'd create the background surface when the program starts and then just blit it in the while loop. If you need a background with extra lines, just draw them on the same background surface as well or create a copy.
import itertools
import pygame as pg
pg.init()
screen = pg.display.set_mode((480, 480))
clock = pg.time.Clock()
width, height = screen.get_size()
block_size = 60
# Create a surface onto which we'll blit the rectangles.
background = pg.Surface((width, height))
colors = itertools.cycle((pg.Color('white'), pg.Color('black')))
for y in range(0, height, block_size):
for x in range(0, width, block_size):
rect = (x, y, block_size, block_size)
pg.draw.rect(background, next(colors), rect)
next(colors) # Skip the next color.
# Then you can just blit the background in the while loop.
screen.blit(background, (0, 0))
pg.display.flip()
You can also calculate the color.
If the index of the column and row are both equal or not equal the color is white, else black.
For example field A8:
Index of A is 0 --> equal
8 --> equal
==> Field is white
Example field A1:
Index of A is 0 --> equal
1 --> unequal
==> Field is black

Pyqtgraph. Draw text label next to the rectangle

I am trying to draw a grid of rectangles with text labels on each rectangle. I am using this code:
import pyqtgraph as pg
win = pg.GraphicsWindow()
vb = win.addViewBox(col=0, row=0)
board = ['1234',
'abcd',
'efgh']
def draw_board(board):
for j, row in enumerate(board):
for i, cell in enumerate(row):
r = pg.QtGui.QGraphicsRectItem(i, -j, 0.9, 0.9)
r.setPen(pg.mkPen((0, 0, 0, 100)))
r.setBrush(pg.mkBrush((50, 50, 200)))
vb.addItem(r)
t = pg.TextItem(cell, (255, 255, 255), anchor=(i, -j))
vb.addItem(t)
pg.QtGui.QApplication.exec_()
draw_board(board)
For some reason labels seem to be drawn in a completely different coordinate system that even uses different scale. What is the easy way to put my labels in the middle of the respective rectangles?
Solved the problem with this code:
t_up = pg.TextItem(cell, (255, 255, 255), anchor=(0, 0))
t_up.setPos(i + 0.5, -j + 0.5)

White spots when drawing mutliple lines close to each other

How can I get rid of white spots when drawing multiple circles close to each other in pygame?
Here is my code:
import pygame
from pygame import gfxdraw
from math import pow, atan2
def getColor(r, col1, col2, fun="lin"):
if fun =="pol2":
r = pow(r,2)
col1_r = tuple([r*x for x in col1])
col2_r = tuple([(1-r)*x for x in col2])
final_col = tuple(sum(i) for i in zip(col1_r, col2_r))
return final_col
def draw(sizeX, sizeY):
# Initialize the game engine
pygame.init()
screen = pygame.display.set_mode([sizeX, sizeY])
#Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
while not done:
# This limits the while loop to a max of 10 times per second.
# Leave this out and we will use all CPU we can.
clock.tick(10)
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
screen.fill(WHITE)
for y in range(200,500):
for x in range(0,10):
gfxdraw.arc(screen, 400, 400, y, x*15, (x+1)*15, getColor(x/10,(0,0,(y-200)/2),(255,255,(y-200)/2), fun="lin"))
pygame.display.flip()
pygame.quit()
This phenomenon is called aliasing and happens when you take a continuous signal and samples it. In your case, gfx.draw() uses continuous functions (the trigonometric functions) to calculate which pixel to draw the color onto. Since theses calculations are in floats and have to be rounded to integers, it may happen that some pixels are missed.
To fix this you need an anti-aliasing filter. There are many different types such as low pass (blurring), oversampling etc.
Since these holes almost always are one pixel I'd create a function that identifies these holes and fills them with the average of it's neighbours colors. The problem is that Pygame is not very good at manually manipulating pixels, so it can be slow depending on the size of the image. Although, Pygame has a module called surfarray that's built on numpy which allows you to access pixels easier and faster, so that will speed it up some. Of course, it'll require you to install numpy.
I couldn't get your program to work, so next time make sure you really have a Minimal, Complete, and Verifiable example. The following code is just based on the image you provided.
import numpy as np
import pygame
pygame.init()
RADIUS = 1080 // 2
FPS = 30
screen = pygame.display.set_mode((RADIUS * 2, RADIUS * 2))
clock = pygame.time.Clock()
circle_size = (RADIUS * 2, RADIUS * 2)
circle = pygame.Surface(circle_size)
background_color = (255, 255, 255)
circle_color = (255, 0, 0)
pygame.draw.circle(circle, circle_color, (RADIUS, RADIUS), RADIUS, RADIUS // 2)
def remove_holes(surface, background=(0, 0, 0)):
"""
Removes holes caused by aliasing.
The function locates pixels of color 'background' that are surrounded by pixels of different colors and set them to
the average of their neighbours. Won't fix pixels with 2 or less adjacent pixels.
Args:
surface (pygame.Surface): the pygame.Surface to anti-aliasing.
background (3 element list or tuple): the color of the holes.
Returns:
anti-aliased pygame.Surface.
"""
width, height = surface.get_size()
array = pygame.surfarray.array3d(surface)
contains_background = (array == background).all(axis=2)
neighbours = (0, 1), (0, -1), (1, 0), (-1, 0)
for row in range(1, height-1):
for col in range(1, width-1):
if contains_background[row, col]:
average = np.zeros(shape=(1, 3), dtype=np.uint16)
elements = 0
for y, x in neighbours:
if not contains_background[row+y, col+x]:
elements += 1
average += array[row+y, col+x]
if elements > 2: # Only apply average if more than 2 neighbours is not of background color.
array[row, col] = average // elements
return pygame.surfarray.make_surface(array)
def main():
running = True
image = pygame.image.load('test.png').convert()
# image = circle
pos = image.get_rect(center=(RADIUS, RADIUS))
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_1:
print('Reset circle.')
image = circle
elif event.key == pygame.K_2:
print('Starting removing holes.')
time = pygame.time.get_ticks()
image = remove_holes(image, background=(255, 255, 255))
time = pygame.time.get_ticks() - time
print('Finished removing holes in {:.4E} s.'.format(time / 1000))
screen.fill(background_color)
screen.blit(image, pos)
pygame.display.update()
if __name__ == '__main__':
main()
Result
Before
After
Time
As I said before, it's not a very fast operation. Here are some benchmarks based on the circle in the example:
Surface size: (100, 100) | Time: 1.1521E-02 s
Surface size: (200, 200) | Time: 4.3365E-02 s
Surface size: (300, 300) | Time: 9.7489E-02 s
Surface size: (400, 400) | Time: 1.7257E-01 s
Surface size: (500, 500) | Time: 2.6911E-01 s
Surface size: (600, 600) | Time: 3.8759E-01 s
Surface size: (700, 700) | Time: 5.2999E-01 s
Surface size: (800, 800) | Time: 6.9134E-01 s
Surface size: (900, 900) | Time: 9.1454E-01 s
And with your image:
Time: 1.6557E-01 s

pygame rectangles drawn in loop scroll across screen

Here is my full code:
import pygame
pygame.init()
i = 0
x = 0
# Define the colors we will use in RGB format
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
# Set the height and width of the screen
size = [600, 300]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Test")
#Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
while not done:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done=True
screen.fill(WHITE)
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)
pygame.display.flip()
pygame.quit()
10 squares are drawn but they scroll across the window to the right and i dont know why. Is there any way I could stop this?
Thanks.
I realise now that it is not about the rectangle loop, but i have changed that to what has been suggested anyway.
Now that you have added more code I see where the problem is coming from and it's just as I suspected - you are doing a static translation the incorrect way namely you use the x (which you have defined outside your main loop) in your for x in range(x, x+100, 10):.
If you add a print(x) statement inside your for-loop you will be able to see that the x gets bigger and bigger, and bigger...This is perfect for adding dynamics to your scene but my guess is (based on your question) that you want to add 10 static rectangles to your scene.
In order to do that you need to reset your x every single time a new iteration of the while loop begins:
while not done:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done=True
screen.fill(WHITE)
# Every single iteration of the while loop will first reset the x to its initial value
# You can make x be any value you want your set of rectangles to start from
x = 0
# Now start adding up to x's initial value
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)
pygame.display.flip()
You can also omit the definition of x as a variable outside the for loop if you won't be using it to change the x coordinate where your first rectangle will start from and replace the range(x, x+100, 10) with range(CONST, CONST+100, 10) where CONST is a given value such as 0, 100, 1000 etc.
Every time your for loop runs the range is incremented, because the x is persistent. If you remove the x=0, reset it inside the while, or use a different variable in the for loop I think it will work.
x=0
while not done:
for x in range(x, x+100, 10):
pygame.draw.rect(screen, BLACK, [x, 0, 10, 10], 1)

How to redraw text on gradient background in pygame?

I am using python 2.7.1 with pygame 1.9.1 on 64-bit win7. I am using the gradient code from http://www.pygame.org/wiki/GradientCode to draw my background. I then display text like so:
countText = font.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(countText, countRect)
pygame.display.flip()
I use this to display a countdown timer, but the problem is the numbers draw on top of one another. I can specify a background color in the font.render() call that will solve this, but then I get another problem where the solid background doesn't match the gradient background.
I think this can be solved by saving a copy of the gradient background in another surface, and then drawing the relevant portion of the saved surface onto the background before drawing the next number, but I am not sure how to do this.
I can save a copy of the gradient background like this:
# save a surface with same size and gradient as background
bg_image = pygame.Surface(screen.get_size())
fill_gradient(bg_image, BG_COLOR, GRADIENT_COLOR)
But how do I select the relevant portion of bg_image and draw it to my main screen background? I tried something like this, doing a screen.blit() to try and erase the current countdown number before blitting the new number, but it doesn't work:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.blit(bg_image, (0,0), countRect)
screen.blit(countText, countRect)
pygame.display.flip()
Would this be the best approach (with code that works)? Or is there a better way to do this?
Thanks for your help.
use pygame.display.set_mode(size, 0, 32) to get your screen surface, it supports transparent.
Use a surface to save your background, we name it bg_surface
You get a new text surface every time you do font.render, name it txt_surface
When each time before pygame.display.flip(), blit the bg_surface and then txt_surface to screen.
It's that what you need? If you are rendering a counter, it's enougth to blit the whole background every frame.
I can use Surface.set_clip() to restrict the area being updated to just the rectangle containing the countdown text:
countText = usefont.render( "%d" % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.y, countRect.centerx = yPos, screen.get_rect().width/2
screen.set_clip(countRect) # Allow updates only to area in countRect
screen.blit(bg_image, (0,0)) # bg_img will only be drawn within countRect
screen.blit(countText, countRect)
pygame.display.flip()
Found this post while researching a minor variation of the same question. Here is a running generic solution created after combining information from various places. In this case, the bg_image can be used directly, since the title that is (also) placed on top does not overlap with the count. I used a different method of generating the gradient, but that is not important. Especially when using screen.copy() to create a reference surface to use during the restore.
#!python3
import os
import numpy as np
import pygame
from pygame.locals import *
size = 640, 480
pygame.init()
os.environ['SDL_VIDEO_CENTERED'] = '1'
screen = pygame.display.set_mode(size, NOFRAME, 0)
width, height = screen.get_size()
pygame.event.set_blocked(MOUSEMOTION) # keep our queue cleaner
# build gradient background
colors = np.random.randint(0, 255, (2, 3))
topcolor = np.array(colors[0], copy=0)
bottomcolor = np.array(colors[1], copy=0)
diff = bottomcolor - topcolor
column = np.arange(height, dtype=np.float32) / height # create array from 0.0 to 1.0 triplets
column = np.repeat(column[:, np.newaxis], [3], 1)
column = topcolor + (diff * column).astype(np.int) # create a single column of gradient
column = column.astype(np.uint8)[np.newaxis, :, :] # make column a 3d image column by adding X
column = pygame.surfarray.map_array(screen, column) # 3d array into 2d array
gradient = np.resize(column, (width, height)) # stretch the column into a full image
bg_image = pygame.surfarray.make_surface(gradient)
screen.blit(bg_image, (0, 0))
usefont = pygame.font.Font(None, 144)
# add content that does not get erased with the count down value
title_surf = usefont.render('Counting…', True, (200, 100, 50))
title_rect = title_surf.get_rect()
title_rect.topleft = (20, 5)
screen.blit(title_surf, title_rect)
pygame.display.flip()
savedSurface = screen.copy() # when no convenient surface to restore from
pygame.time.set_timer(USEREVENT, 1000)
screen_center = int(width / 2), int(height / 2)
savedRect = screen_center, (0, 0) # First time, nothing to restore
secs_left = 11
while secs_left > 0:
event = pygame.event.wait()
if event.type in (QUIT, KEYDOWN, MOUSEBUTTONDOWN):
break
if event.type == USEREVENT:
# screen.blit(bg_image, savedRect, savedRect) # restore background
screen.blit(savedSurface, savedRect, savedRect) # restore background
secs_left -= 1
countText = usefont.render('%d' % secs_left, 1, (255, 0, 0))
countRect = countText.get_rect()
countRect.center = screen_center
savedRect = screen.blit(countText, countRect)
pygame.display.flip()

Categories