Skip to content

Commit 7105d34

Browse files
authored
implemented geometry.regular_polygon (#73)
* implemented `Polygon.normal_polygon` * added more tests for `Polygon.normal_polygon` * formatted code and more tests * handled some reviews * more reviews... * added `geometry.regular_polygon` * moved regular_polygon implementation in geometry.c * removed declaration of regular_polygon * handled some reviews
1 parent 19e06c9 commit 7105d34

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed

geometry.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,7 @@ class Polygon:
185185
def __init__(self, polygon: Polygon) -> None: ...
186186
def __copy__(self) -> "Polygon": ...
187187
copy = __copy__
188+
189+
def regular_polygon(
190+
sides: int, center: Coordinate, radius: float, angle: float = 0
191+
) -> Polygon: ...

src_c/geometry.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,73 @@
88

99
#define PYGAMEAPI_GEOMETRY_NUMSLOTS 21
1010

11-
static PyMethodDef _pg_module_methods[] = {{NULL, NULL, 0, NULL}};
11+
static PyObject *
12+
geometry_regular_polygon(PyObject *_null, PyObject *const *args,
13+
Py_ssize_t nargs)
14+
{
15+
int sides;
16+
double radius;
17+
double angle = 0;
18+
double Cx, Cy;
19+
20+
if (nargs < 3 || nargs > 4) {
21+
return RAISE(PyExc_TypeError,
22+
"invalid number of arguments, expected 3 or 4 arguments");
23+
}
24+
sides = PyLong_AsLong(args[0]);
25+
if (PyErr_Occurred()) {
26+
return NULL;
27+
}
28+
29+
if (sides < 3) {
30+
if (sides < 0) {
31+
return RAISE(PyExc_ValueError,
32+
"the sides can not be a negative number");
33+
}
34+
return RAISE(PyExc_ValueError, "polygons need at least 3 sides");
35+
}
36+
37+
if (!pg_TwoDoublesFromObj(args[1], &Cx, &Cy)) {
38+
return RAISE(PyExc_TypeError,
39+
"the second parameter must be a sequence of 2 numbers");
40+
}
41+
42+
if (!pg_DoubleFromObj(args[2], &radius)) {
43+
return RAISE(PyExc_TypeError, "the third parameter must be a number");
44+
}
45+
if (nargs == 4) {
46+
if (!pg_DoubleFromObj(args[3], &angle)) {
47+
return RAISE(PyExc_TypeError,
48+
"the forth parameter must be a number");
49+
}
50+
angle *= PI / 180.0;
51+
}
52+
53+
double *vertices = PyMem_New(double, sides * 2);
54+
if (!vertices) {
55+
return RAISE(PyExc_MemoryError,
56+
"cannot allocate memory for the polygon vertices");
57+
}
58+
59+
int loop;
60+
double fac = TAU / sides;
61+
for (loop = 0; loop < sides; loop++) {
62+
double ang = angle + fac * loop;
63+
vertices[loop * 2] = Cx + radius * cos(ang);
64+
vertices[loop * 2 + 1] = Cy + radius * sin(ang);
65+
}
66+
67+
PyObject *ret = pgPolygon_New2(vertices, sides);
68+
PyMem_Free(vertices);
69+
70+
return ret;
71+
}
72+
73+
74+
static PyMethodDef _pg_module_methods[] = {
75+
{"regular_polygon", (PyCFunction)geometry_regular_polygon, METH_FASTCALL,
76+
NULL},
77+
{NULL, NULL, 0, NULL}};
1278

1379
MODINIT_DEFINE(geometry)
1480
{

test/test_polygon.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
from pygame import Vector2, Vector3
44

5+
import geometry
56
from geometry import Polygon
67

8+
import math
9+
710
p1 = (12.0, 12.0)
811
p2 = (32.0, 43.0)
912
p3 = (22.0, 4.0)
@@ -124,6 +127,68 @@ def test_copy_invalid_args(self):
124127
with self.assertRaises(TypeError):
125128
po.copy(*value)
126129

130+
def test_static_normal_polygon(self):
131+
center = (150.5, 100.1)
132+
radius = 50.2
133+
sides = 10
134+
angle = 20.6
135+
136+
polygon_pg = geometry.regular_polygon(sides, center, radius, angle)
137+
vertices_pg = polygon_pg.vertices
138+
139+
vertices = []
140+
141+
for i in range(sides):
142+
vertices.append(
143+
(
144+
center[0]
145+
+ radius * math.cos(math.radians(angle) + math.pi * 2 * i / sides),
146+
center[1]
147+
+ radius * math.sin(math.radians(angle) + math.pi * 2 * i / sides),
148+
)
149+
)
150+
151+
self.assertEqual(vertices_pg, vertices)
152+
153+
invalid_types = [
154+
None,
155+
[],
156+
"1",
157+
"123",
158+
(1,),
159+
[1, 2, 3],
160+
[p1, p2, p3, 32],
161+
[p1, p2, "(1, 1)"],
162+
]
163+
164+
for invalid_type in invalid_types + [(1, 2)]:
165+
with self.assertRaises(TypeError):
166+
geometry.regular_polygon(invalid_type, (1, 2.2), 5.5, 1)
167+
168+
for invalid_type in invalid_types:
169+
with self.assertRaises(TypeError):
170+
geometry.regular_polygon(5, invalid_type, 5.5, 1)
171+
172+
for invalid_type in invalid_types + [(1, 2)]:
173+
with self.assertRaises(TypeError):
174+
geometry.regular_polygon(5, (1, 2.2), invalid_type, 1)
175+
176+
for invalid_type in invalid_types + [(1, 2)]:
177+
with self.assertRaises(TypeError):
178+
geometry.regular_polygon(5, (1, 2.2), 5.5, invalid_type)
179+
180+
with self.assertRaises(TypeError):
181+
geometry.regular_polygon(1, (1, 2.2), 5.5, 1, 5)
182+
183+
with self.assertRaises(TypeError):
184+
geometry.regular_polygon()
185+
186+
with self.assertRaises(ValueError):
187+
geometry.regular_polygon(-1, center, radius, angle)
188+
189+
with self.assertRaises(ValueError):
190+
geometry.regular_polygon(2, center, radius, angle)
191+
127192
def test_copy_return_type(self):
128193
"""Checks whether the copy method returns a polygon."""
129194
po = Polygon([p1, p2, p3, p4])

0 commit comments

Comments
 (0)