diff --git a/pygame-wasm/phantomcastle/main.py b/pygame-wasm/phantomcastle/main.py index 1ea8966..d94547b 100644 --- a/pygame-wasm/phantomcastle/main.py +++ b/pygame-wasm/phantomcastle/main.py @@ -65,6 +65,21 @@ class Coords(NamedTuple): def transform(self, ref: "Coords"): return self * ref + def dir_norm(self): + """нормализация вектора, но только для получения направления + x, y могут быть только -1, 0, 1 + может быть только направление по горизонтали либо по вертикали + либо без направления + """ + src = self + if self.x and self.y: + src = ( + self.__class__(self.x, 0) + if abs(self.x) > abs(self.y) + else self.__class__(0, self.y) + ) + return self.__class__(*((n > 0) - (n < 0) for n in src)) + @classmethod def zero(cls): return cls(0, 0) @@ -125,7 +140,10 @@ def maze_gen(row=4, col=4): for pattern, replacement in upd.items(): for i in range(len(maze) - len(pattern) + 1): for j in range(len(maze[0]) - len(pattern[0]) + 1): - if all(maze[i + k][j : j + len(v)] == list(map(int, v)) for k, v in enumerate(pattern)): + if all( + maze[i + k][j : j + len(v)] == list(map(int, v)) + for k, v in enumerate(pattern) + ): for k, v in filter(lambda x: x[1], enumerate(replacement)): maze[i + k][j : j + len(v)] = list(map(int, v)) @@ -153,6 +171,18 @@ class Direction(Enum): case Direction.DOWN: return Coords(0, 1) + @staticmethod + def from_coords(coords: Coords) -> Optional["Direction"]: + match coords.dir_norm(): + case Coords(-1, 0): + return Direction.LEFT + case Coords(1, 0): + return Direction.RIGHT + case Coords(0, -1): + return Direction.UP + case Coords(0, 1): + return Direction.DOWN + class SurfaceWithRect(NamedTuple): surface: pygame.Surface @@ -165,7 +195,9 @@ class SurfaceWithRect(NamedTuple): class DrawableGameObject(ABC): """обобщение игрового элемента""" - coords = property(lambda self: self.get_coords(), lambda self, c: self.set_coords(c)) + coords = property( + lambda self: self.get_coords(), lambda self, c: self.set_coords(c) + ) def __init__( self, @@ -221,13 +253,17 @@ class DrawableGameObject(ABC): pass -class KeysHandler(ABC): +class EventHandler(ABC): @abstractmethod def handle_keys(self, keys_pressed): pass + @abstractmethod + def handle_event(self): + pass -class Hero(DrawableGameObject, KeysHandler): + +class Hero(DrawableGameObject, EventHandler): """объект главного героя""" def __init__( @@ -246,6 +282,7 @@ class Hero(DrawableGameObject, KeysHandler): self.looking_right = False self._speed = 1 self.direction = Direction.RIGHT + self.mouse_active = False # картинка изначально влево, а надо бы начинать со взгляда вправо self.flip() @@ -280,7 +317,9 @@ class Hero(DrawableGameObject, KeysHandler): # проверка колизии has_collision = self._check_collision(coords) if not has_collision: - return super().set_coords(coords) + super().set_coords(coords) + self.scene.coins.collect(self) + return # уменьшение шага while has_collision and coords != self.coords: @@ -290,6 +329,7 @@ class Hero(DrawableGameObject, KeysHandler): coords = coords_new has_collision = self._check_collision(coords) super().set_coords(coords) + self.scene.coins.collect(self) def flip(self): self.looking_right = not self.looking_right @@ -310,7 +350,30 @@ class Hero(DrawableGameObject, KeysHandler): def move(self, direction: Direction, step: int = 1): self.update_direction(direction) self.coords += direction.as_coords() * step * self.speed // 3 - self.scene.coins.collect(self) + + def handle_mouse_event(self, event: pygame.event.Event): + if event.type not in ( + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP, + pygame.MOUSEMOTION, + ): + return + match event.type: + case pygame.MOUSEBUTTONDOWN: + self.mouse_active = self.rect.collidepoint(event.pos) + case pygame.MOUSEBUTTONUP: + self.mouse_active = False + case pygame.MOUSEMOTION if self.mouse_active: + rel = Coords(*event.rel) + direction = Direction.from_coords(rel) + if direction: + self.update_direction(direction) + self.coords += rel + + def handle_event(self, event: pygame.event.Event): + if not self.active: + return + self.handle_mouse_event(event) def handle_keys(self, keys_pressed): if not self.active: @@ -351,6 +414,7 @@ class WallBlock(DrawableGameObject): # уменьшаем размер монетки sf = Coords(1, 1) self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf) + self._mask = pygame.mask.Mask(self.rect.size, fill=True) def draw(self): self.parent.surface.blit(self.surface, self.rect) @@ -438,7 +502,9 @@ class Coins(DrawableGameObject): continue coin_points = sample(free_points, min(count, len(free_points))) - self.coins = [Coin(point.transform(box_sz), self, self.assets) for point in coin_points] + self.coins = [ + Coin(point.transform(box_sz), self, self.assets) for point in coin_points + ] self.collected_coins = [] # Надпись, если все монетки собраны @@ -487,7 +553,7 @@ class Coins(DrawableGameObject): self.add_to_collected(coin) -class EndLevelMenu(DrawableGameObject, KeysHandler): +class EndLevelMenu(DrawableGameObject, EventHandler): def __init__(self, scene: DrawableGameObject): super().__init__(Coords.zero(), scene, scene.assets) self._surface, self.rect = self._create_end_game_label() @@ -557,21 +623,37 @@ class EndLevelMenu(DrawableGameObject, KeysHandler): self.stats_label = self._create_stats_label() self.stats_label.draw_to(self.parent.surface) + def request_new_level(self): + self.scene.want_new_level = True + self.scene.done = True + def handle_keys(self, keys_pressed): if not self.active: return if keys_pressed[pygame.K_n]: - self.parent.want_new_level = True - self.parent.done = True + self.request_new_level() + + def handle_mouse_event(self, event: pygame.event.Event): + if event.type == pygame.MOUSEBUTTONDOWN and self.keys_hint.rect.collidepoint( + event.pos + ): + self.request_new_level() + + def handle_event(self, event: pygame.event.Event): + if not self.active: + return + self.handle_mouse_event(event) -class Scene(DrawableGameObject, KeysHandler): +class Scene(DrawableGameObject, EventHandler): """основной игровой объект""" # кнопки для выхода из игры exit_keys = (pygame.K_ESCAPE, pygame.K_q) - def __init__(self, assets: dict, screen_sz: Coords, maze_sz: Coords, coins_count: int): + def __init__( + self, assets: dict, screen_sz: Coords, maze_sz: Coords, coins_count: int + ): super().__init__(Coords.zero(), None, assets) self.maze = maze_gen(*maze_sz) maze_sz = get_maze_sz(self.maze) @@ -630,7 +712,9 @@ class Scene(DrawableGameObject, KeysHandler): self.walls.draw() self.coins.draw() - def scale_box(self, surface: pygame.Surface, rect: pygame.Rect, scale_factor: Coords): + def scale_box( + self, surface: pygame.Surface, rect: pygame.Rect, scale_factor: Coords + ): rect.size = self.box_sz rect.scale_by_ip(*scale_factor) surface = pygame.transform.scale(surface, rect.size) @@ -644,10 +728,18 @@ class Scene(DrawableGameObject, KeysHandler): self.check_level_completed() self.end.handle_keys(keys_pressed) + def handle_event(self, event: pygame.event.Event): + if self.done: + return + self.hero.handle_event(event) + self.check_level_completed() + self.end.handle_event(event) + async def event_loop(self): clock = pygame.time.Clock() while not self.done: - pygame.event.pump() + for event in pygame.event.get(): + self.handle_event(event) self.handle_keys(pygame.key.get_pressed()) self.draw() pygame.display.flip()