Making a clickable grid using Pygame

Question:

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?

Asked By: SXH7

||

Answers:

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)
Answered By: Jerry

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()
Answered By: Rabbid76

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 10×10 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!

Answered By: Aman
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.