2022-04-30 21:59:33 -07:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
from dataclasses import dataclass
|
2022-05-05 08:37:48 -07:00
|
|
|
from typing import Any, Iterator, overload
|
2022-05-01 00:08:10 -07:00
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
@dataclass(frozen=True)
|
2022-04-30 21:59:33 -07:00
|
|
|
class Point:
|
2022-05-01 17:32:48 -07:00
|
|
|
x: int = 0
|
|
|
|
y: int = 0
|
2022-04-30 21:59:33 -07:00
|
|
|
|
2022-05-01 09:26:20 -07:00
|
|
|
@overload
|
2022-05-01 09:51:22 -07:00
|
|
|
def __add__(self, other: 'Vector') -> 'Point':
|
2022-05-01 09:26:20 -07:00
|
|
|
...
|
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
def __add__(self, other: Any) -> 'Point':
|
2022-05-01 09:26:20 -07:00
|
|
|
if not isinstance(other, Vector):
|
|
|
|
raise TypeError('Only Vector can be added to a Point')
|
|
|
|
return Point(self.x + other.dx, self.y + other.dy)
|
|
|
|
|
2022-05-01 17:43:13 -07:00
|
|
|
def __iter__(self):
|
|
|
|
yield self.x
|
|
|
|
yield self.y
|
|
|
|
|
2022-04-30 21:59:33 -07:00
|
|
|
def __str__(self):
|
|
|
|
return f'(x:{self.x}, y:{self.y})'
|
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
@dataclass(frozen=True)
|
2022-04-30 21:59:33 -07:00
|
|
|
class Vector:
|
2022-05-01 17:32:48 -07:00
|
|
|
dx: int = 0
|
|
|
|
dy: int = 0
|
2022-04-30 21:59:33 -07:00
|
|
|
|
2022-05-01 17:43:13 -07:00
|
|
|
def __iter__(self):
|
|
|
|
yield self.dx
|
|
|
|
yield self.dy
|
|
|
|
|
2022-04-30 21:59:33 -07:00
|
|
|
def __str__(self):
|
|
|
|
return f'(δx:{self.x}, δy:{self.y})'
|
|
|
|
|
2022-05-03 18:21:24 -07:00
|
|
|
class Direction:
|
|
|
|
North = Vector(0, -1)
|
|
|
|
NorthEast = Vector(1, -1)
|
|
|
|
East = Vector(1, 0)
|
|
|
|
SouthEast = Vector(1, 1)
|
|
|
|
South = Vector(0, 1)
|
|
|
|
SouthWest = Vector(-1, 1)
|
|
|
|
West = Vector(-1, 0)
|
|
|
|
NorthWest = Vector(-1, -1)
|
|
|
|
|
2022-05-05 08:37:48 -07:00
|
|
|
@classmethod
|
|
|
|
def all(cls) -> Iterator['Direction']:
|
|
|
|
yield Direction.North
|
|
|
|
yield Direction.NorthEast
|
|
|
|
yield Direction.East
|
|
|
|
yield Direction.SouthEast
|
|
|
|
yield Direction.South
|
|
|
|
yield Direction.SouthWest
|
|
|
|
yield Direction.West
|
|
|
|
yield Direction.NorthWest
|
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
@dataclass(frozen=True)
|
2022-04-30 21:59:33 -07:00
|
|
|
class Size:
|
2022-05-01 17:32:48 -07:00
|
|
|
width: int = 0
|
|
|
|
height: int = 0
|
2022-05-01 00:08:10 -07:00
|
|
|
|
2022-05-01 17:43:13 -07:00
|
|
|
def __iter__(self):
|
|
|
|
yield self.width
|
|
|
|
yield self.height
|
|
|
|
|
2022-04-30 21:59:33 -07:00
|
|
|
def __str__(self):
|
|
|
|
return f'(w:{self.width}, h:{self.height})'
|
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
@dataclass(frozen=True)
|
2022-04-30 21:59:33 -07:00
|
|
|
class Rect:
|
2022-05-01 17:32:48 -07:00
|
|
|
origin: Point
|
|
|
|
size: Size
|
2022-04-30 21:59:33 -07:00
|
|
|
|
2022-04-30 23:29:24 -07:00
|
|
|
@property
|
|
|
|
def min_x(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''Minimum x-value that is still within the bounds of this rectangle. This is the origin's x-value.'''
|
2022-04-30 23:29:24 -07:00
|
|
|
return self.origin.x
|
|
|
|
|
|
|
|
@property
|
|
|
|
def min_y(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''Minimum y-value that is still within the bounds of this rectangle. This is the origin's y-value.'''
|
2022-04-30 23:29:24 -07:00
|
|
|
return self.origin.y
|
|
|
|
|
2022-05-01 00:08:10 -07:00
|
|
|
@property
|
|
|
|
def mid_x(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''The x-value of the center point of this rectangle.'''
|
2022-05-01 00:08:10 -07:00
|
|
|
return int(self.origin.x + self.size.width / 2)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def mid_y(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''The y-value of the center point of this rectangle.'''
|
2022-05-01 00:08:10 -07:00
|
|
|
return int(self.origin.y + self.size.height / 2)
|
|
|
|
|
2022-04-30 21:59:33 -07:00
|
|
|
@property
|
|
|
|
def max_x(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''Maximum x-value that is still within the bounds of this rectangle.'''
|
2022-04-30 23:29:24 -07:00
|
|
|
return self.origin.x + self.size.width - 1
|
2022-04-30 21:59:33 -07:00
|
|
|
|
|
|
|
@property
|
|
|
|
def max_y(self) -> int:
|
2022-05-01 09:26:20 -07:00
|
|
|
'''Maximum y-value that is still within the bounds of this rectangle.'''
|
2022-04-30 23:29:24 -07:00
|
|
|
return self.origin.y + self.size.height - 1
|
2022-04-30 21:59:33 -07:00
|
|
|
|
2022-05-01 00:08:10 -07:00
|
|
|
@property
|
|
|
|
def midpoint(self) -> Point:
|
|
|
|
return Point(self.mid_x, self.mid_y)
|
|
|
|
|
2022-05-03 19:02:39 -07:00
|
|
|
def inset_rect(self, top: int = 0, right: int = 0, bottom: int = 0, left: int = 0) -> 'Rect':
|
|
|
|
'''
|
|
|
|
Return a new Rect inset from this rect by the specified values. Arguments are listed in clockwise order around
|
|
|
|
the permeter. This method doesn't do any validation or transformation of the returned Rect to make sure it's
|
|
|
|
valid.
|
|
|
|
'''
|
|
|
|
return Rect(Point(self.origin.x + left, self.origin.y + top),
|
|
|
|
Size(self.size.width - right - left, self.size.height - top - bottom))
|
|
|
|
|
2022-05-01 17:43:13 -07:00
|
|
|
def __iter__(self):
|
|
|
|
yield tuple(self.origin)
|
|
|
|
yield tuple(self.size)
|
2022-04-30 21:59:33 -07:00
|
|
|
|
2022-05-01 17:32:48 -07:00
|
|
|
def __str__(self):
|
|
|
|
return f'[{self.origin}, {self.size}]'
|