Initial commit of files.

This commit is contained in:
Chris Davoren 2023-09-27 09:07:09 +10:00
commit e41e360ed8
4 changed files with 269 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

3
toyrobot/__init__.py Normal file
View File

@ -0,0 +1,3 @@
# __init__.py
from .robot import Robot

243
toyrobot/robot.py Normal file
View File

@ -0,0 +1,243 @@
class Robot:
"""
Class representing a single "Toy Robot". Robots instances have knowledge of their
position, direction, and movement limits.
Attributes:
DEFAULT_MAX_X (int): The default maximum allowable horizontal coordinate
(inclusive).
DEFAULT_MAX_Y (int): The default maximum allowable vertical coordinate
(inclusive).
DIRECTIONS (dict): A dictionary (str: int) of direction names (e.g. NORTH, EAST
etc) and their numerical encoding. Only the keys are intended for use externally as a list of valid directions.
"""
DEFAULT_MAX_X = 5
DEFAULT_MAX_Y = 5
# Directions ordered such that adding 1 corresponds to RIGHT turn
DIRECTIONS = {
"NORTH": 0,
"EAST": 1,
"SOUTH": 2,
"WEST": 3,
}
# Private internals
# Key corresponds to numerical direction defined in Robot.directions
# Value is an offset vector in with x and y keys (positive x is NORTH, positive y is
# EAST)
_MOVEMENT_VECTORS = {
0: {"x": 0, "y": 1},
1: {"x": 1, "y": 0},
2: {"x": 0, "y": -1},
3: {"x": -1, "y": 0},
}
def __init__(self, max_x: int = DEFAULT_MAX_X, max_y: int = DEFAULT_MAX_Y):
"""
Creates a new Robot instance with the specified position limits (optional).
Implicitly, the minimum limit on coordinates is 0 both horizontally and
vertically.
See the class attributes `DEFAULT_MAX_X` and `DEFAULT_MAX_Y` for the respective
default numerical values.
Args:
max_x (int): The maximum allowable horizontal coordinate (inclusive,
positive is EAST).
max_y (int): The maximum allowable vertical coordinate (inclusive,
positive is NORTH).
Raises:
ValueError: If max_x or max_y are less than 0.
"""
if max_x < 0 or max_y < 0:
raise ValueError("Cannot specify negative limits")
self._max_x = max_x
self._max_y = max_y
self._position_x = None
self._position_y = None
self._direction = None
def set_limits(self, max_x: int, max_y: int):
"""
Sets the positional limits for this Robot. See __init__() for details.
Args:
max_x (int): New maximum allowable horizontal coordinate.
max_y (int): New maximum allowable vertical coordinate.
Raises:
ValueError: If max_x or max_y are less than 0.
"""
if max_x < 0 or max_y < 0:
raise ValueError("Cannot specify negative limits")
self._max_x = max_x
self._max_y = max_y
def get_limits(self) -> (int, int):
"""
Returns the current maximum coordinates valid for this instance.
Returns:
(int, int): A tuple of the maximum coordinates in the order (maximum_x,
maximum y).
"""
return (self._max_x, self._max_y)
def valid_position(self, x: int, y: int) -> bool:
"""
Calculates whether the given coordinates are valid for the limits set on this
Robot instance.
This function is used by the `move()` function to verify that a move action
would be successful.
Args:
x: Proposed horizontal coordinate.
y: Proposed vertical coordinate.
Returns:
bool: True if given coordinates are within limits, otherwise False.
"""
return x >= 0 and y >= 0 and x <= self._max_x and y <= self._max_y
def is_initialized(self):
"""
Returns whether this Robot instance has been initialized (i.e. whether a valid
`place()` command has been executed).
This function is used by the `move()` function to verify that the instance has
been correctly placed before movement.
Returns:
bool: True if correctly initialized, otherwise false.
"""
return (
self._position_x is not None
and self._position_y is not None
and self._direction is not None
)
def move(self):
"""
Moves the robot one space in the direction it is currently facing, provided that
said movement would be within limits.
Will do nothing if this Robot instance has not been initialized successfully
with `place()` or the specified movement would be out of bounds.
"""
if not self.is_initialized():
return
vector = Robot._MOVEMENT_VECTORS[self._direction]
new_position_x = self._position_x + vector["x"]
new_position_y = self._position_y + vector["y"]
if not Robot.valid_position(new_position_x, new_position_y):
return
self._position_x = new_position_x
self._position_y = new_position_y
def place(self, position_x: int, position_y: int, direction_name: str):
"""
Places the Robot instance at the specified coordinates with the specified
direction.
Will do nothing if the given coordinates are out of bounds, or the direction is
not one of the keys in `Robot.DIRECTIONS`.
See `__init__()` for detailed coordinate information.
Args:
position_x (int): Horizontal coordinate for placement.
position_y (int): Vertical coordinate for placement.
direction_name (str): Direction of placement; must be one of the string keys
in `Robot.DIRECTIONS`.
"""
direction_name = direction_name.upper()
if direction_name not in Robot.DIRECTIONS.keys() or not self.valid_position(
position_x, position_y
):
return
self._position_x = position_x
self._position_y = position_y
self._direction = Robot.DIRECTIONS[direction_name]
def get_position(self) -> (int, int):
"""
Returns the current position of this Robot instance.
Returns:
(int, int): A tuple with this instance's current coordinates in the order
(position_x, position_y), or (None, None) if this instance has not yet
been initialized.
"""
return (
(self._position_x, self._position_y)
if self.is_initialized()
else (None, None)
)
def get_direction(self) -> str:
"""
Returns the direction in which this instance is currently facing in string form.
Returns:
str: The current direction of this instance; will be one of the keys of
`Robot.DIRECTION`. Will return None if this instance has not yet been
initialized.
"""
return (
{v: k for k, v in Robot.DIRECTIONS.items()}[self._direction]
if self.is_initialized()
else None
)
def rotate_left(self):
"""
Rotates this Robot instance's direction to the LEFT. For example, if currently
facing NORTH, the new direction will be WEST.
Does nothing if this instance has not yet been correctly placed (initialized).
"""
if not self.is_initialized():
return
self._direction = (self._direction - 1) % 4
def rotate_right(self):
"""
Rotates this Robot instance's direction to the RIGHT. For example, if currently
facing NORTH, the new direction will be EAST.
Does nothing if this instance has not yet been correctly placed (initialized).
"""
if self.is_initialized():
return
self._direction = (self._direction + 1) % 4
def __str__(self) -> str:
"""
Returns a string representation of the instance including position and
direction.
Returns:
str: This instance's string representation.
"""
if not self.is_initialized():
return "Uninitialized"
return "X: {}, Y: {}, direction: {}".format(
self._position_x,
self._position_y,
{v: k for k, v in Robot.DIRECTIONS.items()}[self._direction],
)

22
trmain.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# import sys
# import argparse
import toyrobot
def main():
current_bot = toyrobot.Robot(10, 10)
print(current_bot)
current_bot.place(6, 6, "NORTH")
print(current_bot)
current_bot.place(2, 2, "NORTH")
print(current_bot)
current_bot.place(6, 6, "NORTH")
print(current_bot)
print("Not yet implemented")
if __name__ == "__main__":
main()