Python / Pygame - Get a square to change colour with classes

Viewed 1596

I am making a little project for a Battleships game, but I am having a little trouble with getting the code to understand when I click on the object, I want it to change colour.

So far I have this class:

class Tile:
def __init__(self, xstart, ystart, game_display, colour):
    self.tile = pygame.Rect(xstart, ystart, 50, 50)
    pygame.draw.rect(game_display, colour,  self.tile, 1)
    print(self.tile)

def ChangeColour(self, game_display, colour):
    pygame.draw.rect(game_display, colour,  self.tile, 0)

I create the board using this function I made:

def new_board():
global board
xstart = 70
ystart = 50
#pygame.draw.rect(game_display, BLACK, (70, 50, 500, 500), 3)
for square in range(PLAYER_NUMBER_SQUARES):
    board.append(Tile(xstart, ystart, game_display, BLACK))
    xstart += 50
    if xstart == 570:
        ystart += 50
        xstart = 70
return board

So far, I has added the square / rectangle objects to the list "board", when the player clicks, I get the pixel and use this method to get which square it is that has been clicked:

def get_x_number(x):
index = None
if x >= 70 and x < 120:
    index = 70
if x >= 120 and x < 170:
    index = 120
if x >= 170 and x < 220:
    index = 170
if x >= 220 and x < 270:
    index = 220
if x >= 270 and x < 320:
    index = 270
if x >= 320 and x < 370:
    index = 320
if x >= 370 and x < 420:
    index = 370
if x >= 420 and x < 470:
    index = 420
if x >= 470 and x < 520:
    index = 470
return index

def get_y_number(y):
index = None
if y >= 50 and y < 100:
    index = 70
if y >= 100 and y < 150:
    index = 120
if y >= 150 and y < 200:
    index = 170
if y >= 200 and y < 250:
    index = 220
if y >= 250 and y < 300:
    index = 270
if y >= 300 and y < 350:
    index = 320
if y >= 350 and y < 400:
    index = 370
if y >= 400 and y < 450:
    index = 420
if y >= 450 and y < 500:
    index = 470
return index

(I know this is a slightly more long winded way, but if there is a better way, let me know, tried loops, couldn't get it to work.)

Finally, I get to where it will get the clicked square, however it doesn't change the square, this is because when I index the square out of the list, it returns this: <__main__.Tile object at 0x068A5590>

How can I get it to return the pygame.Rect rather than this? I have had a look into classes, tried __repr__ and __str__ and had no joy, if someone can assist me, that would be great!

Thank you!

2 Answers

I recommend making your Tile class a pygame.sprite.Sprite subclass and then adding all tiles to a sprite group. That allows you to update and draw all sprites by calling sprite_group.update() and sprite_group.draw(screen). Pygame sprites need an image (which has to be a pygame.Surface instance) and a rect attribute.

To check for collisions you can first iterate over the sprites in the sprite group and then use the collidepoint method of the sprite's rect to see if it collides with the mouse position, e.g. sprite.rect.collidepoint(event.pos) or pygame.mouse.get_pos(). If it collides with the mouse, call the change_color method and fill the image of the sprite with the new color (or you could also replace the image).

import pygame as pg


GREEN = pg.Color(0, 200, 90)
BLUE = pg.Color(0, 90, 200)


class Tile(pg.sprite.Sprite):

    def __init__(self, position):
        super().__init__()
        self.image = pg.Surface((50, 50))
        self.color = GREEN
        self.image.fill(self.color)
        self.rect = self.image.get_rect(topleft=position)

    def change_color(self, color):
        self.color = color
        self.image.fill(self.color)


def main():
    screen = pg.display.set_mode((800, 600))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    # Create the tiles and add them to the all_sprites group.
    for y in range(10):
        for x in range(12):
            all_sprites.add(Tile((x*51, y*51)))

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            # If a mouse button was pressed.
            elif event.type == pg.MOUSEBUTTONDOWN:
                # Iterate over the sprites in the group.
                for sprite in all_sprites:
                    # Check if the sprite's rect collides with the mouse pos.
                    if sprite.rect.collidepoint(event.pos):
                        # Finally change the color.
                        sprite.change_color(BLUE)

        all_sprites.update()
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

So, I think it'd be substantially easier to use a loop (as you asked for suggestions). In this method you get access to all tiles that were pressed, then you can execute changeColor.

import pygame

class Tile:
    def __init__(self, xstart, ystart, game_display, colour):
        self.tile = pygame.Rect(xstart, ystart, 50, 50)
        self.game_display = game_display
        self.colour = colour
        self.draw()

    def draw(self):
        pygame.draw.rect(game_display, colour,  self.tile, 1)

    def changeColour(self, colour):
        self.colour = colour
        self.draw()

    def doesHit(self,point):
        return self.tile[0] <= point[0] and self.tile[2] > point[0] and self.tile[1] < point[1] and self.tile[4] >= point[1]

def getTile(x,y):
    global board
    point = (x,y)
    for tile in board:
        if tile.doesHit(point):
            print("hit detected!")
            #now we have direct access to all tile information via tile!
Related