Initial commit
This commit is contained in:
19
geometry/base.py
Normal file
19
geometry/base.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from caseconverter import snakecase
|
||||
|
||||
class GeometryObject:
|
||||
"""A base class for geometry objects.
|
||||
This base class provides some consistent behavior for all geometry objects.
|
||||
|
||||
Note: This class uses some advanced techniques which you aren't expected to
|
||||
understand. If you're interested, by all means keep reading, and ask a teacher
|
||||
if you have questions.
|
||||
"""
|
||||
|
||||
def __new__(cls, value):
|
||||
"""Overrides default constructor to support construction from other objects.
|
||||
When value is an instance of CustomClass, checks cls for a `from_custom_class`
|
||||
method which will be called to produce a new instance.
|
||||
"""
|
||||
method_name = value.__class__.__name__
|
||||
|
||||
|
34
geometry/drawing.py
Normal file
34
geometry/drawing.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from turtle import *
|
||||
from superturtle.movement import fly
|
||||
|
||||
def centered_circle(radius):
|
||||
"""Draws a circle, centered on the turtle's current position.
|
||||
The built-in circle function annoyingly draws a circle whose center
|
||||
is one radius left of the turtle.
|
||||
"""
|
||||
right(90)
|
||||
penup()
|
||||
forward(radius)
|
||||
pendown()
|
||||
left(90)
|
||||
circle(radius)
|
||||
left(90)
|
||||
penup()
|
||||
forward(radius)
|
||||
pendown()
|
||||
right(90)
|
||||
|
||||
def write_label(point, label, align=None, font=None):
|
||||
fly(point.x, point.y)
|
||||
write(label, align=align, font=font)
|
||||
|
||||
def line_end_arrow():
|
||||
"Draws an arrow"
|
||||
left(20)
|
||||
back(20)
|
||||
forward(20)
|
||||
right(40)
|
||||
back(20)
|
||||
forward(20)
|
||||
left(20)
|
||||
|
73
geometry/point.py
Normal file
73
geometry/point.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from geometry.validation import is_coordinates, expect_type
|
||||
from geometry.drawing import centered_circle, write_label
|
||||
from superturtle.movement import fly
|
||||
from turtle import begin_fill, end_fill, fillcolor
|
||||
|
||||
class Point:
|
||||
"""A Point is a location in 2D space, identified by its
|
||||
x and y coordinates. A Point can be initialized in multiple ways:
|
||||
|
||||
- With no value: The Point will be the origin (0, 0).
|
||||
- With an ordered pair of numbers: The Point will have those coordinates.
|
||||
- With a Vector: The Point will have the Vector's x and y coordinates.
|
||||
"""
|
||||
|
||||
def __init__(self, value=None):
|
||||
from geometry.vector import Vector
|
||||
if value is None:
|
||||
self.x, self.y = (0, 0)
|
||||
elif is_coordinates(value):
|
||||
self.x, self.y = value
|
||||
elif isinstance(value, Vector):
|
||||
self.x, self.y = value.x, value.y
|
||||
else:
|
||||
raise TypeError(f"Can't create a Point from {value}.")
|
||||
|
||||
def draw(self, label=None):
|
||||
fly(self.x, self.y)
|
||||
fillcolor("black")
|
||||
begin_fill()
|
||||
centered_circle(2)
|
||||
end_fill()
|
||||
if label:
|
||||
write_label(self, label)
|
||||
|
||||
def __str__(self):
|
||||
return f"({self.x}, {self.y})"
|
||||
|
||||
def __repr__(self):
|
||||
return f"Point(({self.x}, {self.y}))"
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Implements equality checking with a == b.
|
||||
Two Points are equal if their coordinates are the same.
|
||||
A Point can only be equal to another Point.
|
||||
"""
|
||||
try:
|
||||
expect_type(other, Point)
|
||||
return (self.x, self.y) == (other.x, other.y)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def __add__(self, other):
|
||||
"""Implements addition with a + b. Other can be:
|
||||
- a Vector: interpreted as moving the point in the direction of the Vector,
|
||||
returning a new Point.
|
||||
"""
|
||||
from geometry.vector import Vector
|
||||
expect_type(other, Vector)
|
||||
return Point((self.x + other.x, self.y + other.y))
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Implements subtraction with a - b. Other can be:
|
||||
- a Point: interpreted as "what's needed to get from here to there,
|
||||
returning a Vector.
|
||||
- a Vector:interpreted as moving the point in the (negative) direction
|
||||
of the vector and returns a Point.
|
||||
"""
|
||||
from geometry.vector import Vector
|
||||
expect_type(other, (Point, Vector))
|
||||
if isinstance(other, Point):
|
||||
return Vector((self.x - other.x, self.y - other.y))
|
||||
elif isinstance(other, Vector):
|
||||
return Point((self.x - other.x, self.y - other.y))
|
31
geometry/validation.py
Normal file
31
geometry/validation.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
def is_numeric(value):
|
||||
"Checks whether value is an integer or a float (decimal number)."
|
||||
return isinstance(value, int) or isinstance(value, float)
|
||||
|
||||
def is_coordinates(value):
|
||||
"Checks whether value is a tuple of two numeric values."
|
||||
try:
|
||||
x, y = value
|
||||
return is_numeric(x) and is_numeric(y)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
def has_method(obj, method_name):
|
||||
"Checks whether the object has a method called `method_name`."
|
||||
attr = getattr(obj, method_name, None)
|
||||
return callable(attr)
|
||||
|
||||
def expect_type(obj, expected_type):
|
||||
"""Checks whether obj is of the expected type.
|
||||
If multiple types are provided, checks whether obj is one of the types.
|
||||
"""
|
||||
try:
|
||||
if not any(isinstance(obj, t) for t in expected_type):
|
||||
raise TypeError(f"Expected {obj} to be one of the following types: {expected_type}")
|
||||
except TypeError:
|
||||
if not isinstance(obj, expected_type):
|
||||
raise TypeError(f"Expected {obj} to be a {expected_type}")
|
141
geometry/vector.py
Normal file
141
geometry/vector.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from geometry.point import Point
|
||||
from geometry.validation import is_coordinates, expect_type
|
||||
from geometry.drawing import line_end_arrow
|
||||
from math import sqrt, atan
|
||||
from superturtle.movement import fly
|
||||
from turtle import setheading, forward
|
||||
|
||||
class Vector:
|
||||
"""A Vector represents movement in 2D space. Like a Point, a
|
||||
Vector is defined by x and y coordinates. However, Points and Vectors
|
||||
are different. Here are a few things you can do with Vectors which it doesn't
|
||||
make sense to do with Points:
|
||||
- Multiply them by a number (multiplying a vector by 2 means twice the movement)
|
||||
- Negate them (-v means moving in the opposite direction of v)
|
||||
- Find their length
|
||||
|
||||
A Vector can be initialized in multiple ways:
|
||||
|
||||
- With no value: Represents no motion at all: (0, 0).
|
||||
- With an ordered pair of numbers: The Vector will have those coordinates.
|
||||
- With a Point: The Vector will have the Point's x and y coordinates.
|
||||
"""
|
||||
def __init__(self, value=None):
|
||||
if value is None:
|
||||
self.x, self.y = (0, 0)
|
||||
elif is_coordinates(value):
|
||||
self.x, self.y = value
|
||||
elif isinstance(value, Point):
|
||||
self.x, self.y = value.x, value.y
|
||||
else:
|
||||
raise TypeError(f"Can't create a Vector from {value}.")
|
||||
|
||||
def draw(self, origin=None):
|
||||
"Draws the vector."
|
||||
origin = origin or Point()
|
||||
expect_type(origin, Point)
|
||||
fly(origin.x, origin.y)
|
||||
θ = atan(self.y / self.x)
|
||||
setheading(θ)
|
||||
forward(self.mag())
|
||||
line_end_arrow()
|
||||
|
||||
def __str__(self):
|
||||
return f"〈{self.x}, {self.y}〉"
|
||||
|
||||
def __repr__(self):
|
||||
return f"Vector(({self.x}, {self.y}))"
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Implements equality checking with a == b.
|
||||
Two Vectors are equal if their coordinates are the same.
|
||||
A Vector can only be equal to another Vector.
|
||||
"""
|
||||
try:
|
||||
expect_type(other, Vector)
|
||||
return (self.x, self.y) == (other.x, other.y)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def __neg__(self):
|
||||
"""Implements negation with -a.
|
||||
"""
|
||||
return Vector((-self.x, -self.y))
|
||||
|
||||
def __add__(self, other):
|
||||
"""Implements addition with a + b. Other can be:
|
||||
- a Vector: interpreted as combining the movement of self and other,
|
||||
returning a new Vector.
|
||||
- a Point: interpreted as moving the point in the direction of the Vector,
|
||||
returning a new Point.
|
||||
"""
|
||||
expect_type(other, (Point, Vector))
|
||||
if isinstance(other, Vector):
|
||||
return Vector((self.x + other.x, self.y + other.y))
|
||||
elif isinstance(other, Point):
|
||||
return Point((self.x + other.x, self.y + other.y))
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Implements subtraction with a - b. Other must be a Vector,
|
||||
interpreted as subtraction the movement of other from self and
|
||||
returning a new Vector.
|
||||
"""
|
||||
expect_type(other, Vector)
|
||||
return Vector((self.x - other.x, self.y - other.y))
|
||||
|
||||
def __mul__(self, other):
|
||||
"""Implements multiplication with a * 2, interpreted as scaling the
|
||||
vector by other. Other must be numeric.
|
||||
"""
|
||||
expect_type(other, (int, float))
|
||||
return Vector((self.x * other, self.y * other))
|
||||
|
||||
def __rmul__(self, other):
|
||||
"""Implements multiplication with 2 * a.
|
||||
"""
|
||||
return self * other
|
||||
|
||||
def __truediv__(self, other):
|
||||
"""Implements division with a / b, interpreted as dividing the
|
||||
vector by other. Other must be numeric.
|
||||
"""
|
||||
expect_type(other, (int, float))
|
||||
if other == 0:
|
||||
raise ZeroDivisionError("Can't divide a Vector by zero.")
|
||||
return Vector((self.x / other, self.y / other))
|
||||
|
||||
def __bool__(self):
|
||||
"""Implements truthiness--whether an instance of Vector is treated as
|
||||
True or False. Any Vector other than the zero vector is True.
|
||||
"""
|
||||
return self.mag() != 0
|
||||
|
||||
def mag(self):
|
||||
"""Returns the magnitude (length) of the vector, using the Pythagorean formula.
|
||||
"""
|
||||
return sqrt(self.x * self.x + self.y * self.y)
|
||||
|
||||
def scale(self, mag):
|
||||
"""Returns a Vector pointing in the same direction with the given magnitude.
|
||||
"""
|
||||
expect_type(mag, (int, float))
|
||||
if self.mag() > 0:
|
||||
return self * (mag / self.mag())
|
||||
else:
|
||||
raise ValueError("Can't scale the zero vector.")
|
||||
|
||||
def normalize(self):
|
||||
"""Returns a Vector pointing in the same direction with a magnitude of 1.
|
||||
"""
|
||||
return self.scale(1)
|
||||
|
||||
def dot(self, other):
|
||||
"""Computes the dot product of this vector with another.
|
||||
"""
|
||||
expect_type(other, Vector)
|
||||
return self.x * other.x + self.y * other.y
|
||||
|
||||
def project(self, other):
|
||||
"""Projects this vector onto another.
|
||||
"""
|
||||
return other.scale(self.dot(other.normalize()))
|
Reference in New Issue
Block a user