diff --git a/mod_pygame/moveimg.py b/mod_pygame/moveimg.py index c7be425..be8cb52 100644 --- a/mod_pygame/moveimg.py +++ b/mod_pygame/moveimg.py @@ -14,6 +14,7 @@ import tempfile import urllib.request from abc import ABC, abstractmethod from contextlib import contextmanager +from random import choice, randrange from typing import NamedTuple, Optional import PIL @@ -148,6 +149,75 @@ def choose_plural(amount, declensions): return f"{amount} {declensions[i]}" +def maze_gen(row=4, col=4): + """генератор карты + взял с коментария + https://stepik.org/lesson/502494/step/3?discussion=6527620&unit=494196 + """ + row = max(2 * row + 1, 3) + col = max(2 * col + 1, 3) + maze = [[2] * col] + maze.extend([[2] + [1] * (col - 2) + [2] for _ in range(row - 2)]) + maze.append(maze[0]) + + curr = (randrange(1, len(maze) - 1, 2), randrange(1, len(maze[0]) - 1, 2)) + path = [curr] + maze[curr[0]][curr[1]] = 0 + + while path: + nexts = [ + (r1, c1, r2, c2) + for r, c in zip((1, 0, -1, 0), (0, 1, 0, -1)) + if ( + (r1 := curr[0] + r) is None + or (c1 := curr[1] + c) is None + or (r2 := curr[0] + 2 * r) is None + or (c2 := curr[1] + 2 * c) is None + or 1 == maze[r1][c1] == maze[r2][c2] + ) + ] + if len(nexts): + r1, c1, r2, c2 = choice(nexts) + maze[r1][c1] = maze[r2][c2] = 0 + path.append((r2, c2)) + else: + curr = path.pop() + + upd = { + ("22", "20"): (None, "00"), + ("02", "22"): ("00", None), + ("11101", "00101", "11111", "10100", "10111"): ( + None, + None, + None, + "10001", + None, + ), + ("10111", "10100", "11111", "00101", "11101"): ( + None, + "10001", + None, + None, + None, + ), + } + for key in upd: + for i in range(len(maze) - len(key) + 1): + for j in range(len(maze[0]) - len(key[0]) + 1): + if all( + maze[i + k][j : j + len(v)] == list(map(int, v)) + for k, v in enumerate(key) + ): + for k, v in filter(lambda x: x[1], enumerate(upd[key])): + maze[i + k][j : j + len(v)] = list(map(int, v)) + + return maze + + +def get_maze_sz(maze: list[list[int]]) -> Coords: + return Coords(len(maze[0]), len(maze)) + + class GameObject(ABC): """обобщение игрового элемента""" @@ -162,7 +232,7 @@ class GameObject(ABC): assets: dict | None = None, ): self.parent = parent - self._coords = coords + self.rect = pygame.Rect(coords, coords) self.assets = assets or (parent.assets if parent else None) self._surface = None @@ -170,20 +240,24 @@ class GameObject(ABC): def surface(self) -> pygame.Surface | None: return self._surface or (self.parent.surface if self.parent else None) + @property + def scene(self): + return self.parent.scene if self.parent else self + def get_coords(self) -> Coords: - return self._coords + return Coords(*self.rect.topleft) def set_coords(self, coords: Coords): if self.parent: if self.parent.surface: if not ( coords.x >= 0 - and coords.x < self.parent.surface.get_width() + and coords.x + self.rect.width <= self.parent.surface.get_width() and coords.y >= 0 - and coords.y < self.parent.surface.get_height() + and coords.y + self.rect.height < self.parent.surface.get_height() ): return - self._coords = coords + self.rect.topleft = coords @abstractmethod def draw(self): @@ -204,23 +278,46 @@ class Hero(GameObject): assets: dict | None = None, ): super().__init__(coords, parent, assets) - screen_sz = Coords(*parent.surface.get_size()) - ghost_sz = screen_sz // 10 - resize_img(self.assets, "ghost.png", ghost_sz) - self._surface = pygame.image.load(self.assets["ghost.png"]) self.rect = self.surface.get_rect() - self.coords = coords - - def set_coords(self, coords: Coords): - super().set_coords(coords) - self.rect.topleft = self.coords + self.rect.topleft = coords def draw(self): self.parent.surface.blit(self.surface, self.rect) + def _check_collision(self, coords): + """Проверка пересечения со стенами""" + new_rect = self.rect.copy() + new_rect.topleft = coords + return self.scene.walls.check_collision(new_rect) + + def _reduce_step(self, coords): + """Уменьшение шага движения, с целью подойти вплотную к стене""" + delta = coords - self.coords + dx, dy = 0, 0 + if abs(delta.x) > 1: + dx = 1 * (delta.x < 0 or -1) + if abs(delta.y) > 1: + dy = 1 * (delta.y < 0 or -1) + return coords + Coords(dx, dy) + + def set_coords(self, coords: Coords): + # проверка колизии + has_collision = self._check_collision(coords) + if not has_collision: + return super().set_coords(coords) + + # уменьшение шага + while has_collision and coords != self.coords: + coords_new = self._reduce_step(coords) + if coords_new == coords: + return # не могу уменьшить шаг + coords = coords_new + has_collision = self._check_collision(coords) + super().set_coords(coords) + def handle_event(self, event: pygame.event.Event): - delta = 10 + delta = 30 if event.type == pygame.KEYDOWN: match event.key: case pygame.K_UP | pygame.K_w: @@ -233,25 +330,106 @@ class Hero(GameObject): self.coords += Coords(1, 0) * delta +class WallBlock(GameObject): + """объект елемента стены""" + + def __init__( + self, + coords: Coords, + parent: GameObject, + assets: dict | None = None, + ): + super().__init__(coords, parent, assets) + self._surface = pygame.image.load(self.assets["brick.png"]) + self.rect = self.surface.get_rect() + self.rect.topleft = coords + + def draw(self): + self.parent.surface.blit(self.surface, self.rect) + + def handle_event(self, event: pygame.event.Event): + ... + + +class Walls(GameObject): + """объект стен""" + + def __init__( + self, + parent: GameObject, + maze: list[list[int]], + box_sz: Coords, + assets: dict | None = None, + ): + super().__init__(Coords.zero(), parent, assets) + self.box_sz = box_sz + self.blocks = [ + WallBlock(Coords(j, i).transform(box_sz), self, self.assets) + for i, row in enumerate(maze) + for j, item in enumerate(row) + if item > 0 + ] + + def draw(self): + for block in self.blocks: + block.draw() + + def check_collision(self, rect: pygame.Rect) -> bool: + for block in self.blocks: + if block.rect.colliderect(rect): + return True + return False + + def handle_event(self, event: pygame.event.Event): + for block in self.blocks: + block.handle_event(event) + + class Scene(GameObject): """основной игровой объект""" # кнопки для выхода из игры exit_keys = (pygame.K_ESCAPE, pygame.K_q) - def __init__(self, assets: dict, sz: Coords): + def __init__(self, assets: dict, screen_sz: Coords, maze_sz: Coords): super().__init__(Coords.zero(), None, assets) - self._surface = pygame.display.set_mode(sz) - self.hero = Hero(Coords(100, 100), self) - resize_img(assets, "bg1k.png", sz) + self.maze = maze_gen(*maze_sz) + maze_sz = get_maze_sz(self.maze) + + box_sz = screen_sz // get_maze_sz(self.maze) + self.box_sz = box_sz + resize_img(self.assets, "brick.png", box_sz) + + hero_sz = Coords(*map(int, box_sz * 0.8)) + resize_img(self.assets, "ghost.png", hero_sz) + + hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y + + self._surface = pygame.display.set_mode(screen_sz) + self.rect = self._surface.get_rect() + self.hero = Hero(Coords(0, hero_y_offset), self) + resize_img(assets, "bg1k.png", screen_sz) self.background = pygame.image.load(self.assets["bg1k.png"]) self.done = False + self.maze = maze_gen(6, 6) + self.walls = Walls(self, self.maze, box_sz) + print(self.get_exit_rect()) + print(self.rect) + + def get_exit_rect(self) -> pygame.Rect: + maze_sz = get_maze_sz(self.maze) + coords = (maze_sz - Coords(1, 2)) * self.box_sz + rect = pygame.Rect(coords, coords) + rect.width, rect.height = self.box_sz + return rect def draw(self): if self.done: return self.surface.blit(self.background, self.coords) + pygame.draw.rect(self._surface, pygame.Color("#42c53d25"), self.get_exit_rect()) self.hero.draw() + self.walls.draw() def handle_event(self, event: pygame.event.Event): if self.done: @@ -276,9 +454,10 @@ class Scene(GameObject): def game(assets): pygame.init() screen_sz = Coords(1000, 1000) + maze_sz = Coords(6, 6) pygame.display.set_caption("Движение рисунка на Pygame") - scene = Scene(assets, screen_sz) + scene = Scene(assets, screen_sz, maze_sz) scene.event_loop() pygame.quit() @@ -288,6 +467,7 @@ def main(): assets = [ "bg1k.png", "ghost.png", + "brick.png", ] with get_assets(assets) as assets: game(assets)