Как организовать и сформировать мой код с соблюдением всех принципов ООП

Я хочу создать игру-змейку с pygame на python, которая подходит для нескольких игроков. вначале я создал классы для змейки, кнопки и змейки … Теперь я хочу создать графический интерфейс с pygame, который поможет пользователю определять свойства игры, например экран настроек для определения таких вещей, как скорость змей, и если змеи будут дисквалифицированы, когда они выходят из экрана, или что они будут выходить с другой стороны от него. Главный экран, содержащий приветственное сообщение и кнопки, которые определяют, сколько игроков будет в игре. Экран, определяющий свойства змей, такие как цвет и ключи. И, конечно же, кнопки на каждом экране, которые позволяют пользователю переключаться между экранами. Но я не уверен, где выполнять все эти экраны, в каком классе? Где это должно быть? Может, в новом классе? Может в одиночных функциях? Где лучше всего реализовать это, сохранив при этом принципы ООП, такие как единственная ответственность, разделение интерфейсов, поддержание высокой сплоченности и низкой связи …? И как я могу всегда знать, где это место? Как я могу хорошо разработать свой код? Любой ответ мне очень поможет!

Это мой код:

class Snake:
    drafting_score_mess = "{name} score: {score}"

    def __init__(self, name, color: Tuple[int, int, int], speed: float, start_point: Tuple[int, int],
                 block, keys, disqualification_violation_boundaries=True):
        self.name = name
        self._color = color

        self._speed = 1 / speed
        self._last_measure = time.time()
        self.disqualification_violation_boundaries = disqualification_violation_boundaries
        self.score = 0
        self.disqualified = False
        self._length = []
        self._block = block

        self.start_point = start_point
        self._x = start_point[0]
        self._y = start_point[1]
        self._x_cha = 0
        self._y_cha = 0
        self._d = -1

        keys = {k.lower(): v for k, v in keys.items()}
        self._l_key = keys["left"]
        self._r_key = keys["right"]
        self._u_key = keys["up"]
        self._d_key = keys["down"]

    def update_pos(self, screen: pygame.Surface) -> None:
        if not self.disqualified:
            pressed_keys = pygame.key.get_pressed()
            if pressed_keys[self._l_key] and self._d != 0:
                self._x_cha = -self._block
                self._y_cha = 0
                self._d = 0
            elif pressed_keys[self._r_key] and self._d != 0:
                self._x_cha = self._block
                self._y_cha = 0
                self._d = 0
            elif pressed_keys[self._u_key] and self._d != 1:
                self._y_cha = -self._block
                self._x_cha = 0
                self._d = 1
            elif pressed_keys[self._d_key] and self._d != 1:
                self._y_cha = self._block
                self._x_cha = 0
                self._d = 1

        self._x = self._x // 10 * 10
        self._y = self._y // 10 * 10
        current_measure = time.time()
        if current_measure - self._last_measure >= self._speed:
            self._x += self._x_cha
            self._y += self._y_cha

            self._length.append((self._x, self._y))
            self._length = self._length[len(self._length) - (self.score + 1):]
            self._last_measure = current_measure

        for snake_block_pos in self._length:
            pygame.draw.rect(screen, self._color,
                             [snake_block_pos[0], snake_block_pos[1], self._block, self._block])

    def dis_score(self, font: pygame.font.Font, pos: Tuple[int, int], screen: pygame.Surface) -> None:
        score_mass = font.render(self.drafting_score_mess.format(name=self.name, score=self.score),
                                 True, self._color)
        screen.blit(score_mass, pos)

    def update_status_if_encountered_himself(self):
        if (self._x, self._y) in self._length[:-1]:
            self.disqualified = True

    def update_status_if_encountered_margins(self, scr_size: Tuple[int, int]):
        x, y = scr_size
        if self.disqualification_violation_boundaries:
            if self._x >= x or self._x < 0 or self._y >= y or self._y < 0:
                self.disqualified = True
        else:
            if self._x > x:
                self._x = 0
            elif self._x < 0:
                self._x = x
            elif self._y > y:
                self._y = 0
            elif self._y < 0:
                self._y = y

    def get_head_pos(self):
        return tuple((self._x, self._y))

    def reset(self, start_point: Tuple[int, int] = None):
        if start_point is None:
            start_point = self.start_point
        keys = {"up": self._u_key, "down": self._d_key, "right": self._r_key, "left": self._l_key}
        Snake.__init__(self, self.name, self._color, 1 / self._speed, start_point, self._block, keys,
                       self.disqualification_violation_boundaries)

    def get_all_positions(self):
        return self._length

    @property
    def speed(self):
        return int(1 / self._speed)

    @speed.setter
    def speed(self, new_speed):
        if not (10 < new_speed < 150):
            raise ValueError(f"speed need to be between 10-100 got {new_speed}")
        self._speed = 1 / new_speed

    def __gt__(self, other) -> bool:
        return self.score > other

    def __ge__(self, other) -> bool:
        return self.score >= other

    def __lt__(self, other) -> bool:
        return self.score < other

    def __le__(self, other) -> bool:
        return self.score <= other

    def __ne__(self, other) -> bool:
        return self.score != other

    def __eq__(self, other) -> bool:
        return self.score == other


class Button:
    def __init__(self, screen, pos_and_size: Tuple[int, int, int, int] or List[int, int, int, int], func_in_pressed,
                 background: Union[str, bytes, PathLike] or Tuple[int, int, int],
                 text: AnyStr = None, text_font: pygame.font.Font = None, text_color: Tuple[int, int, int] = None,
                 text_align_center=False, brightness_if_mouse_on=50, *func_parameters):
        """
        :param text: The text on the button
        :param pos_and_size: The position and the size of the
        button in format - (x, y, width, height). can be tuple or list.
        :param func_in_pressed: func that calling when the the button pressed
        :param background: Color or image path - tuple or list of three integers or string with path
        """
        self.x = pos_and_size[0]
        self.y = pos_and_size[1]
        self.width = pos_and_size[2]
        self.height = pos_and_size[3]

        self._screen = screen
        self.text = text
        self._font = text_font
        self.text_color = text_color
        self.text_align_center = text_align_center
        self.background = background

        self._brightness = brightness_if_mouse_on
        self._func = func_in_pressed
        self._func_parameters = func_parameters

    def check_button(self):
        if_mouse_click = pygame.mouse.get_pressed(3)[0]
        click_pos = pygame.mouse.get_pos()
        if if_mouse_click and self.x <= click_pos[0] <= self.x + self.width and self.y <= click_pos[1] <= self.y + 
                self.height:
            return self._func(*self._func_parameters)

    def display(self):
        mouse = pygame.mouse.get_pos()
        if isinstance(self.background, list) or isinstance(self.background, tuple):
            if self.x <= mouse[0] <= self.x + self.width and self.y <= mouse[1] <= self.y + self.height:
                color_light = [255 if i + self._brightness >= 255 else i + self._brightness for i in self.background]
                pygame.draw.rect(self._screen, color_light, [self.x, self.y, self.width, self.height])
            else:
                pygame.draw.rect(self._screen, self.background, [self.x, self.y, self.width, self.height])

        elif isinstance(self.background, str):
            image = pygame.image.load(self.background)
            image = pygame.transform.scale(image, (self.width, self.height))

            bright_image = image.copy()
            bright_image.fill((self._brightness, self._brightness, self._brightness),
                              special_flags=pygame.BLEND_RGB_ADD)

            #
            if self.x <= mouse[0] <= self.x + self.width and self.y <= mouse[1] <= self.y + self.height:
                self._screen.blit(bright_image, (self.x, self.y))
            else:
                self._screen.blit(image, (self.x, self.y))

        else:
            raise ValueError("background have to be image path or RBG color.")

        if self.text is not None and self._font is not None and self.text_color is not None:
            text = self._font.render(self.text, True, self.text_color)
            if self.text_align_center:
                text_x = self.x + self.width // 2 - self._font.size(self.text)[0] // 2
            else:
                text_x = self.x
            self._screen.blit(text, (text_x, self.y))


class Display:
    @staticmethod
    def update_display():
        pygame.event.pump()
        pygame.display.update()
        time.sleep(0.009)


class SnakesArrayManager:
    def __init__(self, snakes_lst):
        self._snakes = snakes_lst

    def update_snakes_pos(self, screen):
        for snake in self._snakes:
            snake.update_pos(screen)

    def update_snakes_status_if_encountered_margins(self, screen: pygame.Surface):
        width, height = screen.get_width(), screen.get_height()
        for snake in self._snakes:
            snake.update_status_if_encountered_margins((width, height))

    def update_snakes_status_if_encountered_by_themselves(self):
        for snake in self._snakes:
            snake.update_status_if_encountered_himself()

    def update_snakes_status_if_encountered_others(self):
        for ind, snake in enumerate(self._snakes):
            for other in self._snakes[:ind] + self._snakes[ind + 1:]:
                if snake.get_head_pos() in other.get_all_positions():
                    snake.disqualified = True

    def display_snakes_score_in_rows(self, screen, font: pygame.font.Font, first_line_pos_start: Tuple[int, int],
                                     line_height: int):
        x_row = first_line_pos_start[0]
        y_row = first_line_pos_start[1]
        for snake in self._snakes:
            snake.dis_score(font, (x_row, y_row), screen)
            y_row += line_height + 5

    def __str__(self):
        return f"SnakesArrayManager({self._snakes})"

    def __getitem__(self, item):
        return self._snakes[item]


class SnakeGame(Display, SnakesArrayManager):
    def __init__(self, screen: pygame.Surface, snakes: List[Snake]):
        super().__init__(snakes)
        self._screen = screen
        self._foods_lst = []

    def add_food(self, block: int):
        x = random.randrange(0, self._screen.get_width(), block)
        y = random.randrange(0, self._screen.get_height(), block)
        self._foods_lst.append((x, y))

    def display_foods(self, color: Tuple[int, int, int], block):
        for x_food, y_food in self._foods_lst:
            if x_food >= self._screen.get_width() or y_food >= self._screen.get_height():
                self._foods_lst.remove((x_food, y_food, block))

            pygame.draw.rect(self._screen, color, [x_food, y_food, block, block])

    def update_snakes_score_by_food(self):
        for snake in self._snakes:
            snake_pos = snake.get_head_pos()
            if snake_pos in self._foods_lst:
                self._foods_lst.remove(snake_pos)
                snake.score += 1

    def game_over_display(self, game_over_mass_text: str, game_over_font: pygame.font.Font,
                          game_over_mass_color: Tuple[int, int, int],
                          score_table_font: pygame.font.Font, mass_align_center: bool = True):
        width, height = self._screen.get_width(), self._screen.get_height()

        game_over_mass_size = game_over_font.size(game_over_mass_text)
        if mass_align_center:
            game_over_mass_x = width // 2 - game_over_mass_size[0] // 2
        else:
            game_over_mass_x = 0
        game_over_mass = game_over_font.render(game_over_mass_text, True, game_over_mass_color)
        self._screen.blit(game_over_mass, (game_over_mass_x, height // 4))

        score_row_size = score_table_font.size(Snake.drafting_score_mess.format(name="      ", score="    "))
        if mass_align_center:
            score_table_x = width // 2 - score_row_size[0] // 2
        else:
            score_table_x = 0

        self.display_snakes_score_in_rows(self._screen, score_table_font,
                                          (score_table_x, height // 4 + game_over_mass_size[1] + 10), score_row_size[1])

    def restart(self):
        for snake in self._snakes:
            snake.reset()
        self.__init__(self._screen, *self._snakes)

    @property
    def game_over(self):
        snakes_lost = [snake.disqualified for snake in self._snakes]
        if False not in snakes_lost:
            return True
        else:
            return False

Если у вас есть другие комментарии к коду (я уверен, что они есть), напишите также. Действительно спасибо за помощь.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *