This commit is contained in:
Dmitry Belyaev 2024-03-30 16:17:42 +03:00
parent efa19e3398
commit 5a4bc5f225

View File

@ -14,6 +14,7 @@ import tempfile
import urllib.request import urllib.request
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from contextlib import contextmanager from contextlib import contextmanager
from random import choice, randrange
from typing import NamedTuple, Optional from typing import NamedTuple, Optional
import PIL import PIL
@ -148,6 +149,75 @@ def choose_plural(amount, declensions):
return f"{amount} {declensions[i]}" 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): class GameObject(ABC):
"""обобщение игрового элемента""" """обобщение игрового элемента"""
@ -162,7 +232,7 @@ class GameObject(ABC):
assets: dict | None = None, assets: dict | None = None,
): ):
self.parent = parent self.parent = parent
self._coords = coords self.rect = pygame.Rect(coords, coords)
self.assets = assets or (parent.assets if parent else None) self.assets = assets or (parent.assets if parent else None)
self._surface = None self._surface = None
@ -170,20 +240,24 @@ class GameObject(ABC):
def surface(self) -> pygame.Surface | None: def surface(self) -> pygame.Surface | None:
return self._surface or (self.parent.surface if self.parent else 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: def get_coords(self) -> Coords:
return self._coords return Coords(*self.rect.topleft)
def set_coords(self, coords: Coords): def set_coords(self, coords: Coords):
if self.parent: if self.parent:
if self.parent.surface: if self.parent.surface:
if not ( if not (
coords.x >= 0 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 >= 0
and coords.y < self.parent.surface.get_height() and coords.y + self.rect.height < self.parent.surface.get_height()
): ):
return return
self._coords = coords self.rect.topleft = coords
@abstractmethod @abstractmethod
def draw(self): def draw(self):
@ -204,23 +278,46 @@ class Hero(GameObject):
assets: dict | None = None, assets: dict | None = None,
): ):
super().__init__(coords, parent, assets) 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._surface = pygame.image.load(self.assets["ghost.png"])
self.rect = self.surface.get_rect() self.rect = self.surface.get_rect()
self.coords = coords self.rect.topleft = coords
def set_coords(self, coords: Coords):
super().set_coords(coords)
self.rect.topleft = self.coords
def draw(self): def draw(self):
self.parent.surface.blit(self.surface, self.rect) 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): def handle_event(self, event: pygame.event.Event):
delta = 10 delta = 30
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
match event.key: match event.key:
case pygame.K_UP | pygame.K_w: case pygame.K_UP | pygame.K_w:
@ -233,25 +330,106 @@ class Hero(GameObject):
self.coords += Coords(1, 0) * delta 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): class Scene(GameObject):
"""основной игровой объект""" """основной игровой объект"""
# кнопки для выхода из игры # кнопки для выхода из игры
exit_keys = (pygame.K_ESCAPE, pygame.K_q) 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) super().__init__(Coords.zero(), None, assets)
self._surface = pygame.display.set_mode(sz) self.maze = maze_gen(*maze_sz)
self.hero = Hero(Coords(100, 100), self) maze_sz = get_maze_sz(self.maze)
resize_img(assets, "bg1k.png", sz)
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.background = pygame.image.load(self.assets["bg1k.png"])
self.done = False 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): def draw(self):
if self.done: if self.done:
return return
self.surface.blit(self.background, self.coords) self.surface.blit(self.background, self.coords)
pygame.draw.rect(self._surface, pygame.Color("#42c53d25"), self.get_exit_rect())
self.hero.draw() self.hero.draw()
self.walls.draw()
def handle_event(self, event: pygame.event.Event): def handle_event(self, event: pygame.event.Event):
if self.done: if self.done:
@ -276,9 +454,10 @@ class Scene(GameObject):
def game(assets): def game(assets):
pygame.init() pygame.init()
screen_sz = Coords(1000, 1000) screen_sz = Coords(1000, 1000)
maze_sz = Coords(6, 6)
pygame.display.set_caption("Движение рисунка на Pygame") pygame.display.set_caption("Движение рисунка на Pygame")
scene = Scene(assets, screen_sz) scene = Scene(assets, screen_sz, maze_sz)
scene.event_loop() scene.event_loop()
pygame.quit() pygame.quit()
@ -288,6 +467,7 @@ def main():
assets = [ assets = [
"bg1k.png", "bg1k.png",
"ghost.png", "ghost.png",
"brick.png",
] ]
with get_assets(assets) as assets: with get_assets(assets) as assets:
game(assets) game(assets)