Эх сурвалжийг харах

touch function added to character.

mdo 1 жил өмнө
parent
commit
11a6f644c7

+ 1 - 1
data/02.yaml

@@ -12,7 +12,7 @@ characters:
       increase: e
       decrease: q
       jump: SPACE
-  jett:
+  secondary:
     speed: 5
     width: 150
     position: [600, 200]

+ 0 - 0
images/characters/jett.png → images/characters/secondary.png


+ 90 - 40
jogai/character.py

@@ -4,6 +4,7 @@ from typing import Type
 
 import pygame
 
+import jogai
 from jogai import _events, logger, settings, utils
 from jogai._events import _get_keys
 from jogai._globals import _window
@@ -118,9 +119,9 @@ class Character(pygame.sprite.Sprite):
             relative: True for a incremental movement, False for an absolute movement
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.move(5, 3)
-            >>> ch.move()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.move(5, 3)
+            >>> protagonist.move()
         """
         if x is None:
             x = self.speed
@@ -151,8 +152,8 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.move_to(0, 0)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.move_to(0, 0)
         """
         self.move(x, y, key, relative=False)
 
@@ -165,9 +166,9 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.backward(3)
-            >>> ch.backward()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.backward(3)
+            >>> protagonist.backward()
         """
         if amount is None:
             amount = self.speed
@@ -182,9 +183,9 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.down(3)
-            >>> ch.down()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.down(3)
+            >>> protagonist.down()
         """
         if amount is None:
             amount = self.speed
@@ -201,9 +202,9 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.forward(3)
-            >>> ch.forward()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.forward(3)
+            >>> protagonist.forward()
         """
         if amount is None:
             amount = self.speed
@@ -218,9 +219,9 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.up(3)
-            >>> ch.up()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.up(3)
+            >>> protagonist.up()
         """
         if amount is None:
             amount = self.speed
@@ -258,8 +259,8 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.decrease(0.1)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.decrease(0.1)
         """
         if isinstance(decrement, int):
             decrement = float(decrement)
@@ -278,8 +279,8 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.increase(0.1)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.increase(0.1)
         """
         if isinstance(increment, int):
             increment = float(increment)
@@ -301,8 +302,8 @@ class Character(pygame.sprite.Sprite):
             scale_factor: factor to scale
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.scale_to(2)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.scale_to(2)
         """
         if not isinstance(scale_factor, float) and not isinstance(
             scale_factor, int
@@ -335,8 +336,8 @@ class Character(pygame.sprite.Sprite):
             height: the character height in pixels
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.set_height(100)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.set_height(100)
 
         """
         if not isinstance(height, int):
@@ -360,8 +361,8 @@ class Character(pygame.sprite.Sprite):
             predefined_keys: The predefined keys with the associated action.
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.set_keys({'forward': 'd'})
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.set_keys({'forward': 'd'})
         """
         if not isinstance(predefined_keys, dict):
             raise TypeError(
@@ -382,8 +383,8 @@ class Character(pygame.sprite.Sprite):
             key: Optional key to perform the action
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.set_position((0, 0))
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.set_position((0, 0))
         """
         if not isinstance(position, tuple) and not isinstance(position, list):
             raise TypeError(_('The position must be a tuple or list (x, y).'))
@@ -401,8 +402,8 @@ class Character(pygame.sprite.Sprite):
             speed: The character speed
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.set_speed(10)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.set_speed(10)
         """
         if isinstance(speed, int):
             self.speed = speed
@@ -415,8 +416,8 @@ class Character(pygame.sprite.Sprite):
             width: the character width in pixels
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.set_width(100)
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.set_width(100)
         """
         if not isinstance(width, int):
             raise TypeError(_('Width must be a not negative integer.'))
@@ -430,10 +431,59 @@ class Character(pygame.sprite.Sprite):
         )
         self.rect = self.image.get_rect()
 
-    def load(self, filename: str) -> bool:
+    def touch(
+        self,
+        other_characters: Type['Character'] | Type['jogai.group.Group'] | list,
+        key: str = '',
+    ) -> bool:
+        """
+        Detect if one character is touching another(s).
+
+        Parameters:
+            other_characters: character or list of characters to check
+
+        Returns:
+            True, if it is touching; False, otherwise.
+
+        Examples:
+            >>> protagonist = Character('protagonist.png')
+            >>> secondary = Character('secondary.png')
+            >>> protagonist.touch(secondary)
+            True
+        """
+
+        if isinstance(other_characters, Character):
+            other_characters = [other_characters]
+
+        if not isinstance(other_characters, list) and not isinstance(
+            other_characters, jogai.group.Group
+        ):
+            raise TypeError(
+                _(
+                    'The instances to be touched must be a character or a list of characters.'
+                )
+            )
+
+        if isinstance(other_characters, list) and not all(
+            map(lambda x: isinstance(x, Character), other_characters)
+        ):
+            raise ValueError(
+                _('All instances to be touched must be characters.')
+            )
+
+        if not isinstance(key, str):
+            raise TypeError(_('Key pressed must be a string.'))
+
+        for other_character in other_characters:
+            if not key or _get_keys()[utils.eval_key(key)]:
+                if self.rect.colliderect(other_character.rect):
+                    return True
+        return False
+
+    def load(self, filename: str):
         """
-        >>> ch = Character('protagonist.png')
-        >>> result = ch.load('protagonist.png')
+        >>> protagonist = Character('protagonist.png')
+        >>> protagonist.load('secondary.png')
         """
         self._original_image, self._original_rect = utils._load_image(
             settings.CHARACTERS_DIR, filename
@@ -450,8 +500,8 @@ class Character(pygame.sprite.Sprite):
         Create a copy of this character keeping size and position.
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> other = ch.copy()
+            >>> protagonist = Character('protagonist.png')
+            >>> other = protagonist.copy()
         """
         new_character = Character(self.image_filename)
         new_character.set_height(self.height)
@@ -464,8 +514,8 @@ class Character(pygame.sprite.Sprite):
         Paint the character in the main surface (window).
 
         Examples:
-            >>> ch = Character('protagonist.png')
-            >>> ch.paint()
+            >>> protagonist = Character('protagonist.png')
+            >>> protagonist.paint()
         """
         if self.image:
             _window.blit(self.image, self.rect)

+ 10 - 1
jogai/default_data.yaml

@@ -1,4 +1,4 @@
-scene: scene.png
+scene: test_large.png
 characters:
   protagonist:
     speed: 10
@@ -9,3 +9,12 @@ characters:
       backward: LEFT
       up: UP
       down: DOWN
+  secondary:
+    speed: 5
+    width: 150
+    position: [400, 200]
+    keys:
+      forward: RIGHT
+      backward: LEFT
+      up: UP
+      down: DOWN

+ 8 - 8
jogai/group.py

@@ -24,8 +24,8 @@ class RandomGroup(Group):
     Group of randomly distributed characters.
 
     Examples:
-        >>> ch = Character('protagonist.png')
-        >>> rg = RandomGroup(ch, 5, (100, 500), (0, 300))
+        >>> protagonist = Character('protagonist.png')
+        >>> rg = RandomGroup(protagonist, 5, (100, 500), (0, 300))
     """
 
     def __init__(
@@ -83,8 +83,8 @@ class RectangleGroup(Group):
     Group of characters forming a rectangle.
 
     Examples:
-        >>> ch = Character('protagonist.png')
-        >>> rg = RectangleGroup(ch, 5, 3)
+        >>> protagonist = Character('protagonist.png')
+        >>> rg = RectangleGroup(protagonist, 5, 3)
     """
 
     def __init__(self, model: Type['Character'], base: int, height: int):
@@ -127,8 +127,8 @@ class SquareGroup(RectangleGroup):
     Group of characters forming a square.
 
     Examples:
-        >>> ch = Character('protagonist.png')
-        >>> sg = SquareGroup(ch, 3)
+        >>> protagonist = Character('protagonist.png')
+        >>> sg = SquareGroup(protagonist, 3)
     """
 
     def __init__(self, model: Type['Character'], side: int):
@@ -148,8 +148,8 @@ class StairsGroup(Group):
     Group of characters forming stairs.
 
     Examples:
-        >>> ch = Character('protagonist.png')
-        >>> sg = StairsGroup(ch, 3)
+        >>> protagonist = Character('protagonist.png')
+        >>> sg = StairsGroup(protagonist, 3)
     """
 
     def __init__(

+ 6 - 1
jogai/videogame.py

@@ -115,7 +115,12 @@ def add_group(new_group: 'group.SquareGroup'):
     Add a new group to the videogame.
 
     Parameters:
-        new_group: the character to add
+        new_group: the character to be added
+
+    Examples:
+        >>> p = character.Character('protagonist.png')
+        >>> rectangle = group.RectangleGroup(p, 4, 2)
+        >>> add_group(rectangle) # doctest: +SKIP
     """
     if not isinstance(new_group, group.Group):
         raise TypeError(_('Only Group instances can be added.'))

+ 3 - 1
scripts/basics.py

@@ -6,8 +6,10 @@ def test_example01():
         ...
 
     prepare('02.yaml')
-    sq = SquareGroup(brick, 23)
+    sq = SquareGroup(brick, 3)
     add_group(sq)
 
     while is_running():
+        if protagonist.touch(sq):
+            secondary.move_to(0, 0)
         update()

+ 1 - 1
tests/conftest.py

@@ -3,7 +3,7 @@ from pytest import fixture
 from jogai import videogame as vg
 
 
-@fixture(scope='module')
+@fixture()
 def prepare_videogame():
     vg.init()
     vg.prepare()

+ 62 - 4
tests/test_character.py

@@ -6,6 +6,15 @@ from jogai import videogame as vg
 from jogai.translations import gettext as _
 
 
+def test_load_data_with_none_argument(prepare_videogame):
+    protagonist._load_data(None)
+
+
+def test_load_data_with_wrong_type(prepare_videogame):
+    with pytest.raises(TypeError):
+        protagonist._load_data('foo')
+
+
 def test_height_is_a_non_negative_integer(prepare_videogame):
     assert protagonist.height > -1
 
@@ -24,7 +33,7 @@ def test_y_coordinate_is_an_integer(prepare_videogame):
 
 def test_wrong_character_name():
     with pytest.raises(TypeError):
-        ch = character.Character('protagonist.png', 1)
+        protagonist = character.Character('protagonist.png', name=1)
 
 
 def test_repr_character(prepare_videogame):
@@ -33,7 +42,9 @@ def test_repr_character(prepare_videogame):
 
 def test_wrong_predefined_keys_type():
     with pytest.raises(TypeError):
-        ch = character.Character('protagonist.png', predefined_keys=[])
+        protagonist = character.Character(
+            'protagonist.png', predefined_keys=[]
+        )
 
 
 def test_move_with_wrong_coordinates(prepare_videogame):
@@ -109,9 +120,40 @@ def test_set_width_with_a_negative_amount(prepare_videogame):
         protagonist.set_width(-1)
 
 
-def test_update_rect_position_with_wrong_type(prepare_videogame):
+def test_touch_with_wrong_characters_type(prepare_videogame):
+    with pytest.raises(TypeError):
+        protagonist.touch('foo')
+
+
+def test_touch_with_list_not_all_characters(prepare_videogame):
+    with pytest.raises(ValueError):
+        protagonist.touch([secondary, 'foo'])
+
+
+def test_touch_with_wrong_key_type(prepare_videogame):
+    with pytest.raises(TypeError):
+        protagonist.touch(secondary, -1)
+
+
+def test_touch_without_sprites_colliding(prepare_videogame):
+    protagonist.move_to(0, 0)
+    secondary.move_to(5000, 500)
+    vg.update()
+    assert protagonist.touch(secondary) == False
+
+
+def test_jump_with_force_as_integer(prepare_videogame):
+    protagonist.jump(5)
+
+
+def test_jump_with_wrong_type(prepare_videogame):
     with pytest.raises(TypeError):
-        protagonist._update_rect_position(vgscene, 'foo')
+        protagonist.jump('foo')
+
+
+def test_jump_with_negative_force(prepare_videogame):
+    with pytest.raises(ValueError):
+        protagonist.jump(-5)
 
 
 def test_jump_counter_when_updating_jump(prepare_videogame):
@@ -126,3 +168,19 @@ def test_jump_counter_when_jump_finishes():
     while protagonist.is_jumping:
         protagonist._update_jump()
     assert protagonist._jump_counter == settings.DEFAULT_JUMP_FORCE
+
+
+def test_update_rect_position_with_wrong_traking_type(prepare_videogame):
+    with pytest.raises(TypeError):
+        protagonist._update_rect_position(vgscene, tracking='foo')
+
+
+def test_update_rect_position_with_no_scene(prepare_videogame):
+    protagonist._update_rect_position(None)
+
+
+def test_update_rect_position_when_character_moved(prepare_videogame):
+    protagonist.move_to(1000, 0)
+    protagonist._update_rect_position(vgscene, tracking=True)
+    protagonist.move_to(3000, 0)
+    protagonist._update_rect_position(vgscene, tracking=True)

+ 9 - 0
tests/test_videogame.py

@@ -5,6 +5,10 @@ from pytest import fixture, mark
 from jogai import videogame as vg
 
 
+def test_load_empty_scene():
+    vg._load_scene({})
+
+
 @mark.xfail
 @mark.parametrize(
     ('key, down'),
@@ -16,6 +20,11 @@ def test_simulate_key_press(key, down):
     vg.quit()
 
 
+def test_add_group_with_wrong_type():
+    with pytest.raises(TypeError):
+        vg.add_group('foo')
+
+
 def test_listen_quit_event():
     quit_event = pygame.event.Event(
         pygame.QUIT,