From 699efaff0a51a2c745bdf1ef4fc1004114121380 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r Date: Thu, 4 Jan 2024 12:01:56 +0100 Subject: [PATCH 1/6] Added Circle.collideswith() Co-authored-by: Emc2356 <63981925+emc2356@users.noreply.github.com> Co-authored-by: NovialRiptide <35881688+novialriptide@users.noreply.github.com> Co-authored-by: ScriptLineStudios Co-authored-by: Avaxar <44055981+avaxar@users.noreply.github.com> --- buildconfig/stubs/pygame/geometry.pyi | 4 +++ docs/reST/ref/geometry.rst | 20 +++++++++++ src_c/circle.c | 40 +++++++++++++++++++++ src_c/doc/geometry_doc.h | 1 + test/geometry_test.py | 52 +++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index ec0abf0687..11cb30987e 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -8,6 +8,8 @@ from typing import ( ) from ._common import Coordinate, RectValue +from .rect import Rect, FRect +from .math import Vector2 _CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]] @@ -17,6 +19,7 @@ class _HasCirclettribute(Protocol): circle: Union[_CanBeCircle, Callable[[], _CanBeCircle]] _CircleValue = Union[_CanBeCircle, _HasCirclettribute] +_CanBeCollided = Union[Circle, Rect, FRect, Coordinate, Vector2] class Circle: @property @@ -89,6 +92,7 @@ class Circle: def colliderect(self, x: float, y: float, w: float, h: float) -> bool: ... @overload def colliderect(self, topleft: Coordinate, size: Coordinate) -> bool: ... + def collideswith(self, other: _CanBeCollided) -> bool: ... @overload def update(self, circle: _CircleValue) -> None: ... @overload diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index f67aaed355..ebdd250be9 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -230,6 +230,26 @@ .. ## Circle.colliderect ## + .. method:: collideswith + + | :sl:`check if a shape or point collides with the circle` + | :sg:`collideswith(Circle) -> bool` + | :sg:`collideswith(Rect) -> bool` + | :sg:`collideswith(FRect) -> bool` + | :sg:`collideswith((x, y)) -> bool` + | :sg:`contains(Vector2) -> bool` + + The `collideswith` method checks if a shape or point overlaps with a `Circle` object. + It takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point. + It returns `True` if there's an overlap, and `False` otherwise. + + .. note:: + The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`). + You can't pass a tuple or list of coordinates representing the shape (except for a point), + because the shape type can't be determined from the coordinates alone. + + .. ## Circle.collideswith ## + .. method:: update | :sl:`updates the circle position and radius` diff --git a/src_c/circle.c b/src_c/circle.c index f393cab318..5c2a70af8f 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -388,6 +388,44 @@ pg_circle_colliderect(pgCircleObject *self, PyObject *const *args, return PyBool_FromLong(pgCollision_RectCircle(x, y, w, h, &self->circle)); } +static PyObject * +pg_circle_collideswith(pgCircleObject *self, PyObject *arg) +{ + int result = 0; + pgCircleBase *scirc = &self->circle; + if (pgCircle_Check(arg)) { + result = pgCollision_CircleCircle(&pgCircle_AsCircle(arg), scirc); + } + else if (pgRect_Check(arg)) { + SDL_Rect *argrect = &pgRect_AsRect(arg); + result = pgCollision_RectCircle((double)argrect->x, (double)argrect->y, + (double)argrect->w, (double)argrect->h, + scirc); + } + else if (pgFRect_Check(arg)) { + SDL_FRect *argrect = &pgFRect_AsRect(arg); + result = pgCollision_RectCircle((double)argrect->x, (double)argrect->y, + (double)argrect->w, (double)argrect->h, + scirc); + } + else if (PySequence_Check(arg)) { + double x, y; + if (!pg_TwoDoublesFromObj(arg, &x, &y)) { + return RAISE( + PyExc_TypeError, + "Invalid point argument, must be a sequence of 2 numbers"); + } + result = pgCollision_CirclePoint(scirc, x, y); + } + else { + return RAISE(PyExc_TypeError, + "Invalid shape argument, must be a CircleType, RectType, " + "LineType, PolygonType or a sequence of 2 numbers"); + } + + return PyBool_FromLong(result); +} + static struct PyMethodDef pg_circle_methods[] = { {"collidepoint", (PyCFunction)pg_circle_collidepoint, METH_FASTCALL, DOC_CIRCLE_COLLIDEPOINT}, @@ -400,6 +438,8 @@ static struct PyMethodDef pg_circle_methods[] = { DOC_CIRCLE_COLLIDERECT}, {"update", (PyCFunction)pg_circle_update, METH_FASTCALL, DOC_CIRCLE_UPDATE}, + {"collideswith", (PyCFunction)pg_circle_collideswith, METH_O, + DOC_CIRCLE_COLLIDESWITH}, {"__copy__", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY}, {"copy", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY}, {NULL, NULL, 0, NULL}}; diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 00edb67b17..8e734d9b25 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -14,5 +14,6 @@ #define DOC_CIRCLE_MOVE "move((x, y)) -> Circle\nmove(x, y) -> Circle\nmove(Vector2) -> Circle\nmoves the circle by a given amount" #define DOC_CIRCLE_MOVEIP "move_ip((x, y)) -> None\nmove_ip(x, y) -> None\nmove_ip(Vector2) -> None\nmoves the circle by a given amount, in place" #define DOC_CIRCLE_COLLIDERECT "colliderect(Rect) -> bool\ncolliderect((x, y, width, height)) -> bool\ncolliderect(x, y, width, height) -> bool\ncolliderect((x, y), (width, height)) -> bool\nchecks if a rectangle intersects the circle" +#define DOC_CIRCLE_COLLIDESWITH "collideswith(Circle) -> bool\ncollideswith(Rect) -> bool\ncollideswith(FRect) -> bool\ncollideswith((x, y)) -> bool\ncontains(Vector2) -> bool\ncheck if a shape or point collides with the circle" #define DOC_CIRCLE_UPDATE "update((x, y), radius) -> None\nupdate(x, y, radius) -> None\nupdates the circle position and radius" #define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle" diff --git a/test/geometry_test.py b/test/geometry_test.py index 95d17b7c74..a5a3ebf582 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -616,6 +616,58 @@ def test_colliderect(self): self.assertTrue(c3.colliderect(0.4, 0.0, 1, 1), msgt) self.assertTrue(c3.colliderect((0.4, 0.0), (1, 1)), msgt) + def test_collideswith_argtype(self): + """tests if the function correctly handles incorrect types as parameters""" + + invalid_types = (None, [], "1", (1,), Vector3(1, 1, 1), 1) + + c = Circle(10, 10, 4) + + for value in invalid_types: + with self.assertRaises(TypeError): + c.collideswith(value) + + def test_collideswith_argnum(self): + c = Circle(10, 10, 4) + args = [tuple(range(x)) for x in range(2, 4)] + + # no params + with self.assertRaises(TypeError): + c.collideswith() + + # too many params + for arg in args: + with self.assertRaises(TypeError): + c.collideswith(*arg) + + def test_collideswith(self): + """Ensures the collideswith function correctly registers collisions with circles, lines, rects and points""" + c = Circle(0, 0, 5) + + # circle + c2 = Circle(0, 10, 15) + c3 = Circle(100, 100, 1) + self.assertTrue(c.collideswith(c2)) + self.assertFalse(c.collideswith(c3)) + + # rect + r = Rect(0, 0, 10, 10) + r2 = Rect(50, 0, 10, 10) + self.assertTrue(c.collideswith(r)) + self.assertFalse(c.collideswith(r2)) + + # rect + r = FRect(0, 0, 10, 10) + r2 = FRect(50, 0, 10, 10) + self.assertTrue(c.collideswith(r)) + self.assertFalse(c.collideswith(r2)) + + # point + p = (0, 0) + p2 = (50, 0) + self.assertTrue(c.collideswith(p)) + self.assertFalse(c.collideswith(p2)) + def test_update(self): """Ensures that updating the circle position and dimension correctly updates position and dimension""" From ebe38db13a5828312e2cbb3e74d617bdf7ebfa1c Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 28 Jan 2024 12:52:47 +0100 Subject: [PATCH 2/6] fixed docs --- docs/reST/ref/geometry.rst | 9 ++++----- src_c/doc/geometry_doc.h | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 614cb1268b..0d7fd161f3 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -237,11 +237,10 @@ .. method:: collideswith | :sl:`check if a shape or point collides with the circle` - | :sg:`collideswith(Circle) -> bool` - | :sg:`collideswith(Rect) -> bool` - | :sg:`collideswith(FRect) -> bool` - | :sg:`collideswith((x, y)) -> bool` - | :sg:`contains(Vector2) -> bool` + | :sg:`collideswith(circle, /) -> bool` + | :sg:`collideswith(rect, /) -> bool` + | :sg:`collideswith((x, y), /) -> bool` + | :sg:`collideswith(vector2, /) -> bool` The `collideswith` method checks if a shape or point overlaps with a `Circle` object. It takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point. diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 28507f726f..8db31b73c1 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -14,6 +14,6 @@ #define DOC_CIRCLE_MOVE "move((x, y), /) -> Circle\nmove(x, y, /) -> Circle\nmove(vector2, /) -> Circle\nmoves the circle by a given amount" #define DOC_CIRCLE_MOVEIP "move_ip((x, y), /) -> None\nmove_ip(x, y, /) -> None\nmove_ip(vector2, /) -> None\nmoves the circle by a given amount, in place" #define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\nchecks if a rectangle intersects the circle" -#define DOC_CIRCLE_COLLIDESWITH "collideswith(Circle) -> bool\ncollideswith(Rect) -> bool\ncollideswith(FRect) -> bool\ncollideswith((x, y)) -> bool\ncontains(Vector2) -> bool\ncheck if a shape or point collides with the circle" +#define DOC_CIRCLE_COLLIDESWITH "collideswith(circle, /) -> bool\ncollideswith(rect, /) -> bool\ncollideswith((x, y), /) -> bool\ncollideswith(vector2, /) -> bool\ncheck if a shape or point collides with the circle" #define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdates the circle position and radius" #define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle" From ade28da1794297a1198a70ebde5f7ac9c97fb1b4 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:54:50 +0100 Subject: [PATCH 3/6] added missing versionadded tags --- docs/reST/ref/geometry.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 0d7fd161f3..bfb6b009a8 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -251,6 +251,8 @@ You can't pass a tuple or list of coordinates representing the shape (except for a point), because the shape type can't be determined from the coordinates alone. + .. versionadded:: 2.5.0 + .. ## Circle.collideswith ## .. method:: update From 1e56aa8e684dbe9b53fa34dfd6c70c2fb2fdb6c3 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:16:48 +0100 Subject: [PATCH 4/6] fixed test indent and error msgs --- src_c/circle.c | 9 ++++----- test/geometry_test.py | 5 ++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src_c/circle.c b/src_c/circle.c index 6d71ad303b..9b57a9ec9e 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -411,16 +411,15 @@ pg_circle_collideswith(pgCircleObject *self, PyObject *arg) else if (PySequence_Check(arg)) { double x, y; if (!pg_TwoDoublesFromObj(arg, &x, &y)) { - return RAISE( - PyExc_TypeError, - "Invalid point argument, must be a sequence of 2 numbers"); + return RAISE(PyExc_TypeError, + "Invalid point argument, must be a valid Coordinate"); } result = pgCollision_CirclePoint(scirc, x, y); } else { return RAISE(PyExc_TypeError, - "Invalid shape argument, must be a CircleType, RectType, " - "LineType, PolygonType or a sequence of 2 numbers"); + "Invalid shape argument, must be a Circle, Rect / Frect " + "or a Coordinate"); } return PyBool_FromLong(result); diff --git a/test/geometry_test.py b/test/geometry_test.py index c9c290c79e..d3031fe504 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -616,9 +616,8 @@ def test_colliderect(self): self.assertTrue(c3.colliderect(0.4, 0.0, 1, 1), msgt) self.assertTrue(c3.colliderect((0.4, 0.0), (1, 1)), msgt) - def test_collideswith_argtype(self): - """tests if the function correctly handles incorrect types as parameters""" - + def test_collideswith_argtype(self): + """tests if the function correctly handles incorrect types as parameters""" invalid_types = (None, [], "1", (1,), Vector3(1, 1, 1), 1) c = Circle(10, 10, 4) From 669c481774b9391b274f3e5fa12dbeef59525b12 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:19:02 +0100 Subject: [PATCH 5/6] capitalize R in FRect --- src_c/circle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_c/circle.c b/src_c/circle.c index 9b57a9ec9e..7846fda044 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -418,7 +418,7 @@ pg_circle_collideswith(pgCircleObject *self, PyObject *arg) } else { return RAISE(PyExc_TypeError, - "Invalid shape argument, must be a Circle, Rect / Frect " + "Invalid shape argument, must be a Circle, Rect / FRect " "or a Coordinate"); } From 096b0bf93a0855d79a6dc701439946034f64772b Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sun, 25 Feb 2024 20:46:14 +0100 Subject: [PATCH 6/6] addressed review --- src_c/circle.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src_c/circle.c b/src_c/circle.c index 7846fda044..597cf295a4 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -411,15 +411,16 @@ pg_circle_collideswith(pgCircleObject *self, PyObject *arg) else if (PySequence_Check(arg)) { double x, y; if (!pg_TwoDoublesFromObj(arg, &x, &y)) { - return RAISE(PyExc_TypeError, - "Invalid point argument, must be a valid Coordinate"); + return RAISE( + PyExc_TypeError, + "Invalid point argument, must be a sequence of two numbers"); } result = pgCollision_CirclePoint(scirc, x, y); } else { return RAISE(PyExc_TypeError, - "Invalid shape argument, must be a Circle, Rect / FRect " - "or a Coordinate"); + "Invalid shape argument, must be a Circle, Rect / FRect, " + "Line, Polygon or a sequence of two numbers"); } return PyBool_FromLong(result);