Skip to content

Commit

Permalink
Add Circle top, left, right, bottom attributes (#237)
Browse files Browse the repository at this point in the history
* add top, left, right, bottom attributes

* format
  • Loading branch information
itzpr3d4t0r authored Sep 28, 2024
1 parent f984c3c commit c10dda5
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 0 deletions.
33 changes: 33 additions & 0 deletions docs/circle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,39 @@ Circle Attributes
It's calculated using the `circumference=2*pi*r` formula. It can be reassigned.
If reassigned the circle radius will be changed to produce a circle with matching
circumference. The circle will not be moved from its original position.

.. attribute:: top
| :sl:`top coordinate of the circle`
| :sg:`top -> (float, float)`
It's a tuple containing the `x` and `y` coordinates that represent the top
of the circle. It can be reassigned. If reassigned, the circle will be moved
to the new position. The radius will not be affected.

.. attribute:: bottom
| :sl:`bottom coordinate of the circle`
| :sg:`bottom -> (float, float)`
It's a tuple containing the `x` and `y` coordinates that represent the bottom
of the circle. It can be reassigned. If reassigned, the circle will be moved
to the new position. The radius will not be affected.

.. attribute:: left
| :sl:`left coordinate of the circle`
| :sg:`left -> (float, float)`
It's a tuple containing the `x` and `y` coordinates that represent the left
of the circle. It can be reassigned. If reassigned, the circle will be moved
to the new position. The radius will not be affected.

.. attribute:: right
| :sl:`right coordinate of the circle`
| :sg:`right -> (float, float)`
It's a tuple containing the `x` and `y` coordinates that represent the right
of the circle. It can be reassigned. If reassigned, the circle will be moved
to the new position. The radius will not be affected.

Circle Methods
------
The `Circle` functions which modify the position or size return a new copy of the
Expand Down
8 changes: 8 additions & 0 deletions docs/geometry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ performing transformations and checking for collisions with other objects.

circumference: The circumference of the circle.

top: The top point of the circle.

bottom: The bottom point of the circle.

left: The left point of the circle.

right: The right point of the circle.

**Here is the full list of methods:**
::
move: Moves the circle by the given amount.
Expand Down
4 changes: 4 additions & 0 deletions geometry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ class Circle:
area: float
circumference: float
center: Tuple[float, float]
top: Tuple[float, float]
left: Tuple[float, float]
right: Tuple[float, float]
bottom: Tuple[float, float]
__safe_for_unpickling__: Literal[True]
__hash__: None # type: ignore

Expand Down
106 changes: 106 additions & 0 deletions src_c/circle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,106 @@ pg_circle_setdiameter(pgCircleObject *self, PyObject *value, void *closure)
return 0;
}

static PyObject *
pg_circle_gettop(pgCircleObject *self, void *closure)
{
return pg_TupleFromDoublePair(self->circle.x,
self->circle.y - self->circle.r);
}

static int
pg_circle_settop(pgCircleObject *self, PyObject *value, void *closure)
{
double x, y;

DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);

if (!pg_TwoDoublesFromObj(value, &x, &y)) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers");
return -1;
}

self->circle.y = y + self->circle.r;
self->circle.x = x;

return 0;
}

static PyObject *
pg_circle_getleft(pgCircleObject *self, void *closure)
{
return pg_TupleFromDoublePair(self->circle.x - self->circle.r,
self->circle.y);
}

static int
pg_circle_setleft(pgCircleObject *self, PyObject *value, void *closure)
{
double x, y;

DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);

if (!pg_TwoDoublesFromObj(value, &x, &y)) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers");
return -1;
}

self->circle.x = x + self->circle.r;
self->circle.y = y;

return 0;
}

static PyObject *
pg_circle_getbottom(pgCircleObject *self, void *closure)
{
return pg_TupleFromDoublePair(self->circle.x,
self->circle.y + self->circle.r);
}

static int
pg_circle_setbottom(pgCircleObject *self, PyObject *value, void *closure)
{
double x, y;

DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);

if (!pg_TwoDoublesFromObj(value, &x, &y)) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers");
return -1;
}

self->circle.y = y - self->circle.r;
self->circle.x = x;

return 0;
}

static PyObject *
pg_circle_getright(pgCircleObject *self, void *closure)
{
return pg_TupleFromDoublePair(self->circle.x + self->circle.r,
self->circle.y);
}

static int
pg_circle_setright(pgCircleObject *self, PyObject *value, void *closure)
{
double x, y;

DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);

if (!pg_TwoDoublesFromObj(value, &x, &y)) {
PyErr_SetString(PyExc_TypeError, "Expected a sequence of 2 numbers");
return -1;
}

self->circle.x = x - self->circle.r;
self->circle.y = y;

return 0;
}

static PyObject *
pg_circle_getsafepickle(pgCircleObject *self, void *closure)
{
Expand Down Expand Up @@ -1082,6 +1182,12 @@ static PyGetSetDef pg_circle_getsets[] = {
{"area", (getter)pg_circle_getarea, (setter)pg_circle_setarea, NULL, NULL},
{"circumference", (getter)pg_circle_getcircumference,
(setter)pg_circle_setcircumference, NULL, NULL},
{"top", (getter)pg_circle_gettop, (setter)pg_circle_settop, NULL, NULL},
{"left", (getter)pg_circle_getleft, (setter)pg_circle_setleft, NULL, NULL},
{"bottom", (getter)pg_circle_getbottom, (setter)pg_circle_setbottom, NULL,
NULL},
{"right", (getter)pg_circle_getright, (setter)pg_circle_setright, NULL,
NULL},
{"__safe_for_unpickling__", (getter)pg_circle_getsafepickle, NULL, NULL,
NULL},
{NULL, 0, NULL, NULL, NULL} /* Sentinel */
Expand Down
204 changes: 204 additions & 0 deletions test/test_circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,210 @@ def test_center_del(self):
with self.assertRaises(AttributeError):
del c.center

def test_top(self):
"""Ensures changing the top attribute moves the circle and does not change the circle's radius."""
expected_radius = 5.0

for pos in [
(1, 0),
(0, 0),
(-1, 0),
(0, -1),
(1, 1),
(-1, -1),
(-1, 1),
(1, -1),
]:
c = Circle((0, 0), expected_radius)

c.top = pos

self.assertEqual(pos[0], c.x)
self.assertEqual(pos[1], c.y - expected_radius)

self.assertEqual(expected_radius, c.r)

def test_top_update(self):
"""Ensures changing the x or y value of the circle correctly updates the top."""
expected_x = 10.3
expected_y = 2.12
expected_radius = 5.0
c = Circle(1, 1, expected_radius)

c.x = expected_x
self.assertEqual(c.top, (expected_x, c.y - expected_radius))

c.y = expected_y
self.assertEqual(c.top, (c.x, expected_y - expected_radius))

def test_top_invalid_value(self):
"""Ensures the top attribute handles invalid values correctly."""
c = Circle(0, 0, 1)

for value in (None, [], "1", (1,), [1, 2, 3], True, False):
with self.assertRaises(TypeError):
c.top = value

def test_top_del(self):
"""Ensures the top attribute can't be deleted."""
c = Circle(0, 0, 1)

with self.assertRaises(AttributeError):
del c.top

def test_left(self):
"""Ensures changing the left attribute moves the circle and does not change the circle's radius."""
expected_radius = 5.0

for pos in [
(1, 0),
(0, 0),
(-1, 0),
(0, -1),
(1, 1),
(-1, -1),
(-1, 1),
(1, -1),
]:
c = Circle((0, 0), expected_radius)

c.left = pos

self.assertEqual(pos[0], c.x - expected_radius)
self.assertEqual(pos[1], c.y)

self.assertEqual(expected_radius, c.r)

def test_left_update(self):
"""Ensures changing the x or y value of the circle correctly updates the left."""
expected_x = 10.3
expected_y = 2.12
expected_radius = 5.0
c = Circle(1, 1, expected_radius)

c.x = expected_x
self.assertEqual(c.left, (expected_x - expected_radius, c.y))

c.y = expected_y
self.assertEqual(c.left, (c.x - expected_radius, expected_y))

def test_left_invalid_value(self):
"""Ensures the left attribute handles invalid values correctly."""
c = Circle(0, 0, 1)

for value in (None, [], "1", (1,), [1, 2, 3], True, False):
with self.assertRaises(TypeError):
c.left = value

def test_left_del(self):
"""Ensures the left attribute can't be deleted."""
c = Circle(0, 0, 1)

with self.assertRaises(AttributeError):
del c.left

def test_right(self):
"""Ensures changing the right attribute moves the circle and does not change the circle's radius."""
expected_radius = 5.0

for pos in [
(1, 0),
(0, 0),
(-1, 0),
(0, -1),
(1, 1),
(-1, -1),
(-1, 1),
(1, -1),
]:
c = Circle((0, 0), expected_radius)

c.right = pos

self.assertEqual(pos[0], c.x + expected_radius)
self.assertEqual(pos[1], c.y)

self.assertEqual(expected_radius, c.r)

def test_right_update(self):
"""Ensures changing the x or y value of the circle correctly updates the right."""
expected_x = 10.3
expected_y = 2.12
expected_radius = 5.0
c = Circle(1, 1, expected_radius)

c.x = expected_x
self.assertEqual(c.right, (expected_x + expected_radius, c.y))

c.y = expected_y
self.assertEqual(c.right, (c.x + expected_radius, expected_y))

def test_right_invalid_value(self):
"""Ensures the right attribute handles invalid values correctly."""
c = Circle(0, 0, 1)

for value in (None, [], "1", (1,), [1, 2, 3], True, False):
with self.assertRaises(TypeError):
c.right = value

def test_right_del(self):
"""Ensures the right attribute can't be deleted."""
c = Circle(0, 0, 1)

with self.assertRaises(AttributeError):
del c.right

def test_bottom(self):
"""Ensures changing the bottom attribute moves the circle and does not change the circle's radius."""
expected_radius = 5.0

for pos in [
(1, 0),
(0, 0),
(-1, 0),
(0, -1),
(1, 1),
(-1, -1),
(-1, 1),
(1, -1),
]:
c = Circle((0, 0), expected_radius)

c.bottom = pos

self.assertEqual(pos[0], c.x)
self.assertEqual(pos[1], c.y + expected_radius)

self.assertEqual(expected_radius, c.r)

def test_bottom_update(self):
"""Ensures changing the x or y value of the circle correctly updates the bottom."""
expected_x = 10.3
expected_y = 2.12
expected_radius = 5.0
c = Circle(1, 1, expected_radius)

c.x = expected_x
self.assertEqual(c.bottom, (expected_x, c.y + expected_radius))

c.y = expected_y
self.assertEqual(c.bottom, (c.x, expected_y + expected_radius))

def test_bottom_invalid_value(self):
"""Ensures the bottom attribute handles invalid values correctly."""
c = Circle(0, 0, 1)

for value in (None, [], "1", (1,), [1, 2, 3], True, False):
with self.assertRaises(TypeError):
c.bottom = value

def test_bottom_del(self):
"""Ensures the bottom attribute can't be deleted."""
c = Circle(0, 0, 1)

with self.assertRaises(AttributeError):
del c.bottom

def test_area(self):
"""Ensures the area is calculated correctly."""
c = Circle(0, 0, 1)
Expand Down

0 comments on commit c10dda5

Please sign in to comment.