Skip to content

Commit

Permalink
Add Circle collideswith() (#2661)
Browse files Browse the repository at this point in the history
* Added Circle.collideswith()

Co-authored-by: Emc2356 <[email protected]>
Co-authored-by: NovialRiptide <[email protected]>
Co-authored-by: ScriptLineStudios <[email protected]>
Co-authored-by: Avaxar <[email protected]>

* fixed docs

* added missing versionadded tags

* fixed test indent and error msgs

* capitalize R in FRect

* addressed review

---------

Co-authored-by: Emc2356 <[email protected]>
Co-authored-by: NovialRiptide <[email protected]>
Co-authored-by: ScriptLineStudios <[email protected]>
Co-authored-by: Avaxar <[email protected]>
  • Loading branch information
5 people authored Feb 25, 2024
1 parent acb6155 commit 7699c03
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
4 changes: 4 additions & 0 deletions buildconfig/stubs/pygame/geometry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ from typing import (

from pygame import Rect, FRect
from ._common import Coordinate, RectValue
from .rect import Rect, FRect
from .math import Vector2

_CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]]

Expand All @@ -18,6 +20,7 @@ class _HasCirclettribute(Protocol):
circle: Union[_CanBeCircle, Callable[[], _CanBeCircle]]

_CircleValue = Union[_CanBeCircle, _HasCirclettribute]
_CanBeCollided = Union[Circle, Rect, FRect, Coordinate, Vector2]

class Circle:
@property
Expand Down Expand Up @@ -90,6 +93,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
Expand Down
21 changes: 21 additions & 0 deletions docs/reST/ref/geometry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,27 @@

.. ## 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((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.
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.

.. versionadded:: 2.5.0

.. ## Circle.collideswith ##
.. method:: update

| :sl:`updates the circle position and radius`
Expand Down
40 changes: 40 additions & 0 deletions src_c/circle.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,44 @@ pg_circle_rotate_ip(pgCircleObject *self, PyObject *const *args,
Py_RETURN_NONE;
}

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 two numbers");
}
result = pgCollision_CirclePoint(scirc, x, y);
}
else {
return RAISE(PyExc_TypeError,
"Invalid shape argument, must be a Circle, Rect / FRect, "
"Line, Polygon or a sequence of two numbers");
}

return PyBool_FromLong(result);
}

static PyObject *
pg_circle_as_rect(pgCircleObject *self, PyObject *_null)
{
Expand Down Expand Up @@ -515,6 +553,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},
{"as_rect", (PyCFunction)pg_circle_as_rect, METH_NOARGS,
DOC_CIRCLE_ASRECT},
{"as_frect", (PyCFunction)pg_circle_as_frect, METH_NOARGS,
Expand Down
1 change: 1 addition & 0 deletions src_c/doc/geometry_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#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((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_ROTATE "rotate(angle, rotation_point=Circle.center, /) -> Circle\nrotate(angle, /) -> Circle\nrotates the circle"
#define DOC_CIRCLE_ROTATEIP "rotate_ip(angle, rotation_point=Circle.center, /) -> None\nrotate_ip(angle, /) -> None\nrotates the circle in place"
Expand Down
51 changes: 51 additions & 0 deletions test/geometry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,57 @@ 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"""
Expand Down

0 comments on commit 7699c03

Please sign in to comment.