102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
from typing import Iterator, Literal
|
|
|
|
|
|
@dataclass(slots=True, frozen=True, order=True)
|
|
class Position:
|
|
""" This class represents a position in 2D integer space """
|
|
x: int
|
|
y: int
|
|
|
|
@classmethod
|
|
def splat(cls, value: int) -> Position:
|
|
""" Creates a Position with two equal values """
|
|
return Position(value, value)
|
|
|
|
def __str__(self) -> str:
|
|
return f"({self.x}, {self.y})"
|
|
|
|
def __getitem__(self, index: Literal[0, 1]) -> int:
|
|
""" Gets the first or second component of this position """
|
|
match index:
|
|
case 0: return self.x
|
|
case 1: return self.y
|
|
case _: raise IndexError()
|
|
|
|
def __iter__(self) -> Iterator[int]:
|
|
""" Iterates over all components of this psition """
|
|
yield self.x
|
|
yield self.y
|
|
|
|
def set_x(self, x: int) -> Position:
|
|
""" Creates a copy of this position with x set to the new value """
|
|
return Position(x, self.y)
|
|
|
|
def set_y(self, y: int) -> Position:
|
|
""" Creates a copy of this position with y set to the new value """
|
|
return Position(self.x, y)
|
|
|
|
def __neg__(self) -> Position:
|
|
""" Creates a copy of this position with all values negated """
|
|
return Position(-self.x, -self.y)
|
|
|
|
def __add__(self, other: Position) -> Position:
|
|
""" Adds the components of these two positions """
|
|
return Position(self.x + other.x, self.y + other.y)
|
|
|
|
def __sub__(self, other: Position) -> Position:
|
|
""" Subtracts the components of these two positions """
|
|
return Position(self.x - other.x, self.y - other.y)
|
|
|
|
def __mul__(self, factor: int) -> Position:
|
|
""" Multiplies all components of this position by the given factor """
|
|
return Position(self.x * factor, self.y * factor)
|
|
|
|
def right(self) -> Position:
|
|
""" Returns the neighboring position to the right """
|
|
return self + UNIT_X
|
|
|
|
def up(self) -> Position:
|
|
""" Returns the neighboring position above """
|
|
return self + UNIT_NEG_Y
|
|
|
|
def left(self) -> Position:
|
|
""" Returns the neighboring position to the left """
|
|
return self + UNIT_NEG_X
|
|
|
|
def down(self) -> Position:
|
|
""" Returns the neighboring position below """
|
|
return self + UNIT_Y
|
|
|
|
def unit_neighbors(self) -> Iterator[Position]:
|
|
""" Returns an iterator of all direct neighbors """
|
|
yield self.right()
|
|
yield self.up()
|
|
yield self.left()
|
|
yield self.down()
|
|
|
|
def is_within(self, top_left: Position, bottom_right: Position) -> bool:
|
|
"""
|
|
Checks if this point is within the rectangle spanned by the given positions.
|
|
bottom_right is considered to be the first point outside the spanned rectangle.
|
|
Behavior is undefined if top_left and bottom_right are not in the correct order.
|
|
"""
|
|
return top_left.x <= self.x < bottom_right.x and top_left.y <= self.y < bottom_right.y
|
|
|
|
def taxicab_distance(self, other: Position | None = None) -> int:
|
|
"""
|
|
If other is given returns the taxicab distance from this point to other.
|
|
Otherwise returns the taxicab distance of this position to the Origin.
|
|
"""
|
|
if other is None:
|
|
return abs(self.x) + abs(self.y)
|
|
else:
|
|
return abs(self.x - other.x) + abs(self.y - other.y)
|
|
|
|
|
|
ORIGIN = Position.splat(0)
|
|
UNIT_X = Position(1, 0)
|
|
UNIT_Y = Position(0, 1)
|
|
UNIT_NEG_X = Position(-1, 0)
|
|
UNIT_NEG_Y = Position(0, -1)
|