From b8ecdd8f2335e0b53e3d5aa78458f12d951b825f Mon Sep 17 00:00:00 2001
From: Dmitry <b4tm4n@mail.ru>
Date: Thu, 4 Apr 2024 13:30:04 +0300
Subject: [PATCH] phantomcastle use overlap

---
 mod_pygame/phantom_castle.py      | 33 +++++++++++++++++++++++--------
 pygame-wasm/phantomcastle/main.py | 33 +++++++++++++++++++++++--------
 2 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/mod_pygame/phantom_castle.py b/mod_pygame/phantom_castle.py
index 5b16b7e..4b91c6e 100644
--- a/mod_pygame/phantom_castle.py
+++ b/mod_pygame/phantom_castle.py
@@ -262,11 +262,29 @@ class DrawableGameObject(ABC):
         self.rect = pygame.Rect(coords, coords)
         self.assets = assets or (parent.assets if parent else None)
         self._surface = None
+        self._mask = None
+
+    def __str__(self):
+        return f"{self.__class__.__name__}({self.coords})"
 
     @property
     def surface(self) -> pygame.Surface | None:
         return self._surface or (self.parent.surface if self.parent else None)
 
+    @property
+    def mask(self) -> pygame.Mask | None:
+        if not self._mask:
+            self._mask = pygame.mask.from_surface(self.surface.convert_alpha())
+        return self._mask
+
+    def overlap(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
+        if not self.rect.colliderect(rect):
+            return False
+
+        offset = Coords(*self.rect.topleft) - Coords(*rect.topleft)
+        overlap = mask.overlap(self.mask, offset)
+        return overlap is not None
+
     @property
     def scene(self):
         return self.parent.scene if self.parent else self
@@ -321,10 +339,9 @@ class Hero(DrawableGameObject, EventHandler):
 
     def _check_collision(self, coords):
         """Проверка пересечения со стенами"""
-        new_rect = self.surface.get_bounding_rect()
+        new_rect = self.rect.copy()
         new_rect.topleft = coords
-        new_rect.scale_by_ip(0.99, 0.99)
-        return self.scene.walls.check_collision(new_rect)
+        return self.scene.walls.check_collision(new_rect, self.mask)
 
     @property
     def speed(self):
@@ -378,7 +395,7 @@ class Hero(DrawableGameObject, EventHandler):
     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.rect)
+        self.scene.coins.collect(self)
 
     def handle_event(self, event: pygame.event.Event):
         if not self.active:
@@ -449,9 +466,9 @@ class Walls(DrawableGameObject):
         for block in self.blocks:
             block.draw()
 
-    def check_collision(self, rect: pygame.Rect) -> bool:
+    def check_collision(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
         for block in self.blocks:
-            if block.rect.colliderect(rect):
+            if block.overlap(rect, mask):
                 return True
         return False
 
@@ -552,8 +569,8 @@ class Coins(DrawableGameObject):
         coin.coords = last_pos
         self.collected_coins.append(coin)
 
-    def collect(self, rect: pygame.Rect):
-        mined = [*filter(lambda coin: coin.bounding_rect.colliderect(rect), self.coins)]
+    def collect(self, actor: DrawableGameObject):
+        mined = [*filter(lambda coin: coin.overlap(actor.rect, actor.mask), self.coins)]
         for coin in mined:
             self.coins.remove(coin)
             self.add_to_collected(coin)
diff --git a/pygame-wasm/phantomcastle/main.py b/pygame-wasm/phantomcastle/main.py
index b9bc98e..1ea8966 100644
--- a/pygame-wasm/phantomcastle/main.py
+++ b/pygame-wasm/phantomcastle/main.py
@@ -177,11 +177,29 @@ class DrawableGameObject(ABC):
         self.rect = pygame.Rect(coords, coords)
         self.assets = assets or (parent.assets if parent else None)
         self._surface = None
+        self._mask = None
+
+    def __str__(self):
+        return f"{self.__class__.__name__}({self.coords})"
 
     @property
     def surface(self) -> pygame.Surface | None:
         return self._surface or (self.parent.surface if self.parent else None)
 
+    @property
+    def mask(self) -> pygame.Mask | None:
+        if not self._mask:
+            self._mask = pygame.mask.from_surface(self.surface.convert_alpha())
+        return self._mask
+
+    def overlap(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
+        if not self.rect.colliderect(rect):
+            return False
+
+        offset = Coords(*self.rect.topleft) - Coords(*rect.topleft)
+        overlap = mask.overlap(self.mask, offset)
+        return overlap is not None
+
     @property
     def scene(self):
         return self.parent.scene if self.parent else self
@@ -236,10 +254,9 @@ class Hero(DrawableGameObject, KeysHandler):
 
     def _check_collision(self, coords):
         """Проверка пересечения со стенами"""
-        new_rect = self.surface.get_bounding_rect()
+        new_rect = self.rect.copy()
         new_rect.topleft = coords
-        new_rect.scale_by_ip(0.99, 0.99)
-        return self.scene.walls.check_collision(new_rect)
+        return self.scene.walls.check_collision(new_rect, self.mask)
 
     @property
     def speed(self):
@@ -293,7 +310,7 @@ 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.rect)
+        self.scene.coins.collect(self)
 
     def handle_keys(self, keys_pressed):
         if not self.active:
@@ -362,9 +379,9 @@ class Walls(DrawableGameObject):
         for block in self.blocks:
             block.draw()
 
-    def check_collision(self, rect: pygame.Rect) -> bool:
+    def check_collision(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
         for block in self.blocks:
-            if block.rect.colliderect(rect):
+            if block.overlap(rect, mask):
                 return True
         return False
 
@@ -463,8 +480,8 @@ class Coins(DrawableGameObject):
         coin.coords = last_pos
         self.collected_coins.append(coin)
 
-    def collect(self, rect: pygame.Rect):
-        mined = [*filter(lambda coin: coin.bounding_rect.colliderect(rect), self.coins)]
+    def collect(self, actor: DrawableGameObject):
+        mined = [*filter(lambda coin: coin.overlap(actor.rect, actor.mask), self.coins)]
         for coin in mined:
             self.coins.remove(coin)
             self.add_to_collected(coin)