Skip to content

Commit 4d2e563

Browse files
itzpr3d4t0rEmc2356andrewhong04ScriptLineStudiosavaxar
committed
Implemented Circle.contains().
Co-authored-by: Emc2356 <[email protected]> Co-authored-by: NovialRiptide <[email protected]> Co-authored-by: ScriptLineStudios <[email protected]> Co-authored-by: Avaxar <[email protected]>
1 parent a68f741 commit 4d2e563

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

buildconfig/stubs/pygame/geometry.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class Circle:
9494
@overload
9595
def colliderect(self, topleft: Coordinate, size: Coordinate, /) -> bool: ...
9696
def collideswith(self, other: _CanBeCollided, /) -> bool: ...
97+
def contains(self, shape: _CanBeCollided) -> bool: ...
9798
@overload
9899
def update(self, circle: _CircleValue, /) -> None: ...
99100
@overload

docs/reST/ref/geometry.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,29 @@
283283

284284
.. ## Circle.collideswith ##
285285
286+
.. method:: contains
287+
288+
| :sl:`test if a shape or point is inside the circle`
289+
| :sg:`contains(circle, /) -> bool`
290+
| :sg:`contains(rect, /) -> bool`
291+
| :sg:`contains((x, y), /) -> bool`
292+
| :sg:`contains(vector2, /) -> bool`
293+
294+
The `contains` method tests whether a given shape or point is completely contained
295+
within a `Circle` object. The function takes a single argument which can be a `Circle`,
296+
`Rect`, `FRect`, or a point.
297+
It returns `True` if the shape or point is completely contained, and `False` otherwise.
298+
299+
300+
.. note::
301+
The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`).
302+
You can't pass a tuple or list of coordinates representing the shape (except for a point),
303+
because the shape type can't be determined from the coordinates alone.
304+
305+
.. versionadded:: 2.5.0
306+
307+
.. ## Circle.contains ##
308+
286309
.. method:: update
287310

288311
| :sl:`updates the circle position and radius`

src_c/circle.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,65 @@ pg_circle_as_frect(pgCircleObject *self, PyObject *_null)
371371
return pgFRect_New4((float)x, (float)y, (float)diameter, (float)diameter);
372372
}
373373

374+
static PyObject *
375+
pg_circle_contains(pgCircleObject *self, PyObject *arg)
376+
{
377+
int result = 0;
378+
pgCircleBase *scirc = &self->circle;
379+
double x, y;
380+
381+
if (pgCircle_Check(arg)) {
382+
pgCircleBase *temp = &pgCircle_AsCircle(arg);
383+
if (temp == scirc) {
384+
/*a circle is always contained within itself*/
385+
Py_RETURN_TRUE;
386+
}
387+
double dx, dy, dr;
388+
389+
dx = temp->x - scirc->x;
390+
dy = temp->y - scirc->y;
391+
dr = temp->r - scirc->r;
392+
393+
result = (dx * dx + dy * dy) <= (dr * dr);
394+
}
395+
else if (pgRect_Check(arg)) {
396+
SDL_Rect *temp = &pgRect_AsRect(arg);
397+
398+
if (pgCollision_CirclePoint(scirc, (double)temp->x, (double)temp->y) &&
399+
pgCollision_CirclePoint(scirc, (double)(temp->x + temp->w),
400+
(double)temp->y) &&
401+
pgCollision_CirclePoint(scirc, (double)temp->x,
402+
(double)(temp->y + temp->h)) &&
403+
pgCollision_CirclePoint(scirc, (double)(temp->x + temp->w),
404+
(double)(temp->y + temp->h))) {
405+
result = 1;
406+
}
407+
}
408+
else if (pgFRect_Check(arg)) {
409+
SDL_FRect *temp = &pgFRect_AsRect(arg);
410+
411+
if (pgCollision_CirclePoint(scirc, (double)temp->x, (double)temp->y) &&
412+
pgCollision_CirclePoint(scirc, (double)(temp->x + temp->w),
413+
(double)temp->y) &&
414+
pgCollision_CirclePoint(scirc, (double)temp->x,
415+
(double)(temp->y + temp->h)) &&
416+
pgCollision_CirclePoint(scirc, (double)(temp->x + temp->w),
417+
(double)(temp->y + temp->h))) {
418+
result = 1;
419+
}
420+
}
421+
else if (pg_TwoDoublesFromObj(arg, &x, &y)) {
422+
result = pgCollision_CirclePoint(scirc, x, y);
423+
}
424+
else {
425+
return RAISE(PyExc_TypeError,
426+
"Invalid shape argument, must be a Circle, Rect / Frect "
427+
"or a coordinate");
428+
}
429+
430+
return PyBool_FromLong(result);
431+
}
432+
374433
static struct PyMethodDef pg_circle_methods[] = {
375434
{"collidepoint", (PyCFunction)pg_circle_collidepoint, METH_FASTCALL,
376435
DOC_CIRCLE_COLLIDEPOINT},
@@ -395,6 +454,7 @@ static struct PyMethodDef pg_circle_methods[] = {
395454
DOC_CIRCLE_ROTATE},
396455
{"rotate_ip", (PyCFunction)pg_circle_rotate_ip, METH_FASTCALL,
397456
DOC_CIRCLE_ROTATEIP},
457+
{"contains", (PyCFunction)pg_circle_contains, METH_O, DOC_CIRCLE_CONTAINS},
398458
{NULL, NULL, 0, NULL}};
399459

400460
#define GETTER_SETTER(name) \

src_c/doc/geometry_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#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"
1616
#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"
1717
#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"
18+
#define DOC_CIRCLE_CONTAINS "contains(circle, /) -> bool\ncontains(rect, /) -> bool\ncontains((x, y), /) -> bool\ncontains(vector2, /) -> bool\ntest if a shape or point is inside the circle"
1819
#define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdates the circle position and radius"
1920
#define DOC_CIRCLE_ROTATE "rotate(angle, rotation_point=Circle.center, /) -> Circle\nrotate(angle, /) -> Circle\nrotates the circle"
2021
#define DOC_CIRCLE_ROTATEIP "rotate_ip(angle, rotation_point=Circle.center, /) -> None\nrotate_ip(angle, /) -> None\nrotates the circle in place"

test/geometry_test.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,108 @@ def assert_approx_equal(circle1, circle2, eps=1e-12):
11491149
c.rotate_ip(angle, center)
11501150
assert_approx_equal(c, rotate_circle(c, angle, center))
11511151

1152+
def test_contains_argtype(self):
1153+
"""Tests if the function correctly handles incorrect types as parameters"""
1154+
1155+
invalid_types = (None, [], "1", (1,), 1, (1, 2, 3))
1156+
1157+
c = Circle(10, 10, 4)
1158+
1159+
for value in invalid_types:
1160+
with self.assertRaises(TypeError):
1161+
c.contains(value)
1162+
1163+
def test_contains_argnum(self):
1164+
"""Tests if the function correctly handles incorrect number of parameters"""
1165+
c = Circle(10, 10, 4)
1166+
1167+
invalid_args = [(Circle(10, 10, 4), Circle(10, 10, 4))]
1168+
1169+
with self.assertRaises(TypeError):
1170+
c.contains()
1171+
1172+
for arg in invalid_args:
1173+
with self.assertRaises(TypeError):
1174+
c.contains(*arg)
1175+
1176+
def test_contains_return_type(self):
1177+
"""Tests if the function returns the correct type"""
1178+
c = Circle(10, 10, 4)
1179+
1180+
self.assertIsInstance(c.contains(Circle(10, 10, 4)), bool)
1181+
1182+
def test_contains_circle(self):
1183+
"""Ensures that the contains method correctly determines if a circle is
1184+
contained within the circle"""
1185+
c = Circle(10, 10, 4)
1186+
c2 = Circle(10, 10, 2)
1187+
c3 = Circle(100, 100, 5)
1188+
c4 = Circle(16, 10, 7)
1189+
1190+
# self
1191+
self.assertTrue(c.contains(c))
1192+
1193+
# contained circle
1194+
self.assertTrue(c.contains(c2))
1195+
1196+
# not contained circle
1197+
self.assertFalse(c.contains(c3))
1198+
1199+
# intersecting circle
1200+
self.assertFalse(c.contains(c4))
1201+
1202+
def test_contains_point(self):
1203+
"""Ensures that the contains method correctly determines if a point is
1204+
contained within the circle"""
1205+
c = Circle(10, 10, 4)
1206+
p1 = (10, 10)
1207+
p2 = (10, 15)
1208+
p3 = (100, 100)
1209+
1210+
p1v = Vector2(10, 10)
1211+
p2v = Vector2(10, 15)
1212+
p3v = Vector2(100, 100)
1213+
1214+
# contained point
1215+
self.assertTrue(c.contains(p1))
1216+
1217+
# not contained point
1218+
self.assertFalse(c.contains(p2))
1219+
self.assertFalse(c.contains(p3))
1220+
1221+
# contained point
1222+
self.assertTrue(c.contains(p1v))
1223+
1224+
# not contained point
1225+
self.assertFalse(c.contains(p2v))
1226+
self.assertFalse(c.contains(p3v))
1227+
1228+
def test_contains_rect_frect(self):
1229+
"""Ensures that the contains method correctly determines if a rect is
1230+
contained within the circle"""
1231+
c = Circle(0, 0, 10)
1232+
r1 = Rect(0, 0, 3, 3)
1233+
r2 = Rect(10, 10, 10, 10)
1234+
r3 = Rect(10, 10, 5, 5)
1235+
1236+
fr1 = FRect(0, 0, 3, 3)
1237+
fr2 = FRect(10, 10, 10, 10)
1238+
fr3 = FRect(10, 10, 5, 5)
1239+
1240+
# contained rect
1241+
self.assertTrue(c.contains(r1))
1242+
1243+
# not contained rect
1244+
self.assertFalse(c.contains(r2))
1245+
self.assertFalse(c.contains(r3))
1246+
1247+
# contained rect
1248+
self.assertTrue(c.contains(fr1))
1249+
1250+
# not contained rect
1251+
self.assertFalse(c.contains(fr2))
1252+
self.assertFalse(c.contains(fr3))
1253+
11521254

11531255
if __name__ == "__main__":
11541256
unittest.main()

0 commit comments

Comments
 (0)