Source code for universe.map.position

import enum
import math
from functools import cache
from typing import TYPE_CHECKING, Tuple

if TYPE_CHECKING:
    from .boundary import Boundary


[docs] class Direction(enum.Enum): """Enum class for directions.""" NORTH = 0 EAST = 90 SOUTH = 180 WEST = 270
[docs] @staticmethod def from_angle(angle: int) -> "Direction": """Convert an angle to a direction. :param angle: The angle. :type angle: int :return: The direction. :rtype: Direction """ return Direction((angle + 45) % 360 // 90)
[docs] def to_angle(self) -> int: """Convert the direction to an angle. :return: The angle. :rtype: int """ return self.value
[docs] def to_arrow(self) -> str: """Convert the direction to an arrow. :return: The arrow. :rtype: str """ return { Direction.NORTH: "↑", Direction.EAST: "→", Direction.SOUTH: "↓", Direction.WEST: "←", }[self]
[docs] class Position: def __init__(self, x: int, y: int, direction: Direction = Direction.NORTH): """Initialize a position. :param x: The x-coordinate of the position. :type x: int :param y: The y-coordinate of the position. :type y: int :param direction: The direction of the position, defaults to Direction.NORTH. :type direction: Direction """ self.x = x self.y = y self.direction = direction def __add__(self, other: "Position") -> "Position": """Add two positions.""" return Position(self.x + other.x, self.y + other.y) def __sub__(self, other: "Position") -> "Position": """Subtract two positions.""" return Position(self.x - other.x, self.y - other.y) def __eq__(self, other: "Position") -> bool: """Check if two positions are equal.""" return self.x == other.x and self.y == other.y def __ne__(self, other: "Position") -> bool: """Check if two positions are not equal.""" return not self.__eq__(other) def __hash__(self) -> int: """Get the hash of the position.""" return hash((self.x, self.y)) def __str__(self) -> str: """Get a string representation of the position.""" return f"({self.x}, {self.y})" def __repr__(self) -> str: """Get a formal string representation of the position.""" return f"Position({self.x}, {self.y})" def __iter__(self) -> Tuple[int, int]: """Iterate over the position.""" yield from (self.x, self.y) def __getitem__(self, index: int) -> int: """Get an item from the position.""" return (self.x, self.y)[index]
[docs] def manhattan_distance(self, other: "Position") -> int: """Calculate the Manhattan distance to another position.""" return abs(self.x - other.x) + abs(self.y - other.y)
[docs] def euclidean_distance(self, other: "Position") -> float: """Calculate the Euclidean distance to another position.""" return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
[docs] def chebyshev_distance(self, other: "Position") -> int: """Calculate the Chebyshev distance to another position.""" return max(abs(self.x - other.x), abs(self.y - other.y))
[docs] def get_neighbors(self, distance: int = 1) -> list["Position"]: """Get the neighbors of the position. :param distance: The distance to get the neighbors from, defaults to 1. :type distance: int :return: The neighbors of the position. :rtype: list[Position] """ return [ Position(self.x + dx, self.y + dy) for dx in range(-distance, distance + 1) for dy in range(-distance, distance + 1) if dx != 0 or dy != 0 ]
[docs] @cache def calculate_new_position( self, boundary: "Boundary", direction: Direction, distance: int = 1 ) -> "Position": """Calculate a new position based on a direction and distance. :param boundary: The boundary of the universe. :type boundary: Boundary :param direction: The direction to move. :type direction: Direction :param distance: The distance to move, defaults to 1. :type distance: int :return: The new position. :rtype: Position """ if direction == Direction.NORTH: return Position(self.x, min(self.y + distance, boundary.position_2.y)) elif direction == Direction.EAST: return Position(min(self.x + distance, boundary.position_2.x), self.y) elif direction == Direction.SOUTH: return Position(self.x, max(self.y - distance, boundary.position_1.y)) elif direction == Direction.WEST: return Position(max(self.x - distance, boundary.position_1.x), self.y) else: raise ValueError(f"Invalid direction: {direction}")
[docs] def move( self, boundary: "Boundary", direction: Direction = None, distance: int = 1, new_position: "Position" = None, ): """Move the position. :param boundary: The boundary of the universe. :type boundary: Boundary :param direction: The direction to move, defaults to None. :type direction: Direction :param distance: The distance to move, defaults to 1. :type distance: int :param new_position: The new position to move to, defaults to None. :type new_position: Position """ if new_position: if boundary.contains(new_position): self.x, self.y = new_position else: raise ValueError(f"Out of boundary: {new_position}") return new_position = self.calculate_new_position(boundary, direction, distance) self.direction = direction if boundary.contains(new_position): self.x, self.y = new_position else: raise ValueError(f"Out of boundary: {new_position}")
[docs] def can_move( self, boundary: "Boundary", direction: Direction, distance: int = 1 ) -> bool: """Check if the position can move in a direction. :param boundary: The boundary of the universe. :type boundary: Boundary :param direction: The direction to move. :type direction: Direction :param distance: The distance to move, defaults to 1. :type distance: int :return: True if the position can move, False otherwise. :rtype: bool """ return boundary.contains( self.calculate_new_position(boundary, direction, distance) )
[docs] def to_dict(self) -> dict: """Convert the position to a dictionary. :return: The dictionary representation of the position. :rtype: dict """ return { "x": self.x, "y": self.y, "direction": self.direction.to_angle(), }