3
\$\begingroup\$

I am a bit new to pygame, and I made this simple pixel art game. Is it possible to simplify, and is it possible to save your drawings and load them back again?

import pygame
from sys import exit


def ConvertPos(pos):
    x, y = pos
    return x//SQUARE_SIZE, y//SQUARE_SIZE


def DrawGrid():
    for x in range(0, WIDTH, SQUARE_SIZE):
        for y in range(0, HEIGHT, SQUARE_SIZE):
            rect = pygame.Rect(x, y, SQUARE_SIZE, SQUARE_SIZE)
            pygame.draw.rect(screen, "Black", rect, 1)


def SelectColor():
    global draw_color
    mouse_pressed = pygame.mouse.get_pressed()
    bg = pygame.Rect(WIDTH, 0, SIDE, HEIGHT)
    pygame.draw.rect(screen, "Orange", bg)
    pygame.draw.rect(screen, "Purple", bg, 5)
    for item, color in enumerate(colors):
        select_color = pygame.Rect(0, 0, 120, 30)
        select_color.center = WIDTH+SIDE//2, HEIGHT*(item+1)/len(colors)-(HEIGHT/len(colors))/2
        pygame.draw.rect(screen, color, select_color)
        if select_color.collidepoint(pygame.mouse.get_pos()) and mouse_pressed[0]:
            draw_color = color
        if draw_color == color:
            pygame.draw.rect(screen, "DarkBlue", select_color, 5)


def DrawSquare():
    global draw_color
    mouse_pos = pygame.mouse.get_pos()
    mouse_pressed = pygame.mouse.get_pressed()
    grid_x, grid_y = grid_pos = ConvertPos(mouse_pos)
    cursor = (grid_x*SQUARE_SIZE, grid_y*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
    for (x, y), color in pressed.items():
        square = pygame.Rect(x*SQUARE_SIZE, y*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
        pygame.draw.rect(screen, color, square)
    if 0 < mouse_pos[0] < WIDTH-1 and 0 < mouse_pos[1] < HEIGHT-1:
        pygame.draw.rect(screen, draw_color, cursor, 3)
        if mouse_pressed[0]:
            pressed[grid_pos] = draw_color
        if mouse_pressed[2] and grid_pos in pressed:
            pressed.pop(grid_pos)
    if mouse_pressed[1]:
        pressed.clear()


pygame.init()
clock = pygame.time.Clock()

WIDTH = 800
SIDE = 200
HEIGHT = 400
MARGIN = 50
SQUARE_SIZE = 25
screen = pygame.display.set_mode((WIDTH+SIDE, HEIGHT))
pygame.display.set_caption("PXLART")

pressed = {}
colors = ["Black", "Blue", "Green", "Red", "Pink", "Yellow", "White", "DarkGreen", "Brown", "Grey", "Cyan"]
draw_color = "Black"

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    screen.fill("White")

    DrawGrid()
    SelectColor()
    DrawSquare()

    pygame.display.update()
    clock.tick(60)
\$\endgroup\$
1
  • 3
    \$\begingroup\$ is it possible to save your drawings and load them back again? - Probably, though if that isn't done now, this isn't the site to explain how. As for the simplify bit, that can be answered here. \$\endgroup\$
    – Reinderien
    Commented Feb 28, 2022 at 22:20

1 Answer 1

1
\$\begingroup\$

ConvertPos should be convert_pos and so on for your other methods due to PEP8.

Don't keep global state. There are a few alternatives, a class being the easiest.

Separate out your mouse handling from your drawing, and don't keep a clock or a "frame loop". You don't have any animations, so use an event loop instead. This is far less wasteful. In other words, 60 times a second, if absolutely nothing is happening from the user, the program should just wait for a new event instead of churning through hundreds of draw calls.

Add PEP484 type hints.

Add a __main__ guard.

Don't exit() on program termination - that isn't very nice to your callers; just return.

It's more typical to have y in your outer loop rather than your inner loop - this will produce iteration in the same pattern as a typewriter (left-right fast, up-down slow).

Suggested

import pygame
from pygame.event import Event

WIDTH = 800
SIDE = 200
HEIGHT = 400
MARGIN = 50
SQUARE_SIZE = 25
COLOURS = (
    "Black", "Blue", "Green", "Red", "Pink", "Yellow",
    "White", "DarkGreen", "Brown", "Grey", "Cyan",
)


def convert_pos(pos: tuple[int, int]) -> tuple[int, int]:
    x, y = pos
    return x//SQUARE_SIZE, y//SQUARE_SIZE


class Game:
    def __init__(self) -> None:
        pygame.display.set_caption("PXLART")

        self.screen: pygame.Surface = pygame.display.set_mode((WIDTH + SIDE, HEIGHT))
        self.pressed: dict[tuple[int, int], str] = {}
        self.draw_color = "Black"

    def draw_grid(self) -> None:
        for y in range(0, HEIGHT, SQUARE_SIZE):
            for x in range(0, WIDTH, SQUARE_SIZE):
                rect = pygame.Rect(x, y, SQUARE_SIZE, SQUARE_SIZE)
                pygame.draw.rect(self.screen, "Black", rect, 1)

    def draw_select_colour(self) -> None:
        background = pygame.Rect(WIDTH, 0, SIDE, HEIGHT)
        pygame.draw.rect(self.screen, "Orange", background)
        pygame.draw.rect(self.screen, "Purple", background, 5)

        for item, colour in enumerate(COLOURS):
            select_colour = pygame.Rect(0, 0, 120, 30)
            select_colour.center = (
                WIDTH + SIDE/2,
                HEIGHT/len(COLOURS)*(item + 0.5)
            )
            pygame.draw.rect(self.screen, colour, select_colour)

            if self.draw_color == colour:
                pygame.draw.rect(self.screen, "DarkBlue", select_colour, 5)

    def draw_square(self) -> None:
        mouse_x, mouse_y = mouse_pos = pygame.mouse.get_pos()
        grid_x, grid_y = convert_pos(mouse_pos)
        cursor = (grid_x*SQUARE_SIZE, grid_y*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)

        for (x, y), color in self.pressed.items():
            square = pygame.Rect(x*SQUARE_SIZE, y*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
            pygame.draw.rect(self.screen, color, square)

        if 0 < mouse_x < WIDTH-1 and 0 < mouse_y < HEIGHT-1:
            pygame.draw.rect(self.screen, self.draw_color, cursor, 3)

    def handle_mouse(self, pos: tuple[int, int], left: bool, middle: bool, right: bool) -> None:
        mouse_x, mouse_y = pos
        if 0 <= mouse_x < WIDTH and 0 <= mouse_y < HEIGHT:
            grid_pos = convert_pos(pos)
        else:
            grid_pos = None

        if left:
            if grid_pos is None:
                for item, colour in enumerate(COLOURS):
                    select_colour = pygame.Rect(0, 0, 120, 30)
                    select_colour.center = (
                        WIDTH + SIDE / 2,
                        HEIGHT / len(COLOURS) * (item + 0.5)
                    )

                    if select_colour.collidepoint(pygame.mouse.get_pos()):
                        self.draw_color = colour
            else:
                self.pressed[grid_pos] = self.draw_color

        elif middle:
            self.pressed.clear()

        elif right:
            self.pressed.pop(grid_pos, None)

    def draw(self) -> None:
        self.screen.fill("White")
        self.draw_grid()
        self.draw_select_colour()
        self.draw_square()
        pygame.display.update()

    def run(self) -> None:
        while True:
            event: Event = pygame.event.wait()

            if event.type == pygame.QUIT:
                return

            if event.type == pygame.MOUSEBUTTONDOWN:
                self.handle_mouse(event.pos, *(
                    event.button == b for b in (
                        pygame.BUTTON_LEFT,
                        pygame.BUTTON_MIDDLE,
                        pygame.BUTTON_RIGHT,
                    )
                ))

            if event.type == pygame.MOUSEMOTION:
                self.handle_mouse(event.pos, *event.buttons)

            if event.type in {pygame.MOUSEBUTTONDOWN, pygame.MOUSEMOTION, pygame.WINDOWSHOWN}:
                self.draw()


if __name__ == '__main__':
    Game().run()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.