diff --git a/projects/002-polygon-area-calculator/main.py b/projects/002-polygon-area-calculator/main.py deleted file mode 100644 index d4354fa..0000000 --- a/projects/002-polygon-area-calculator/main.py +++ /dev/null @@ -1,20 +0,0 @@ -# This entrypoint file to be used in development. Start by reading README.md -import shape_calculator -from unittest import main - - -rect = shape_calculator.Rectangle(5, 10) -print(rect.get_area()) -rect.set_width(3) -print(rect.get_perimeter()) -print(rect) - -sq = shape_calculator.Square(9) -print(sq.get_area()) -sq.set_side(4) -print(sq.get_diagonal()) -print(sq) - - -# Run unit tests automatically -main(module='test_module', exit=False) \ No newline at end of file diff --git a/projects/002-polygon-area-calculator/python/main.py b/projects/002-polygon-area-calculator/python/main.py index e69de29..a505b2d 100644 --- a/projects/002-polygon-area-calculator/python/main.py +++ b/projects/002-polygon-area-calculator/python/main.py @@ -0,0 +1,5 @@ +from poligon_calculator.controller import Controller + +if __name__ == "__main__": + controller = Controller() + controller.start() \ No newline at end of file diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/__init__.py b/projects/002-polygon-area-calculator/python/poligon_calculator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/controller.py b/projects/002-polygon-area-calculator/python/poligon_calculator/controller.py new file mode 100644 index 0000000..886d18d --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/controller.py @@ -0,0 +1,149 @@ +from poligon_calculator.input import Input +from poligon_calculator.output import Output +from poligon_calculator.shape_calculator import ShapeCalculator + + +class Controller: + """Main controller class responsible for managing user interactions + and delegating shape-related operations to the ShapeCalculator. + """ + + def __init__(self): + """Initialize the Controller instance and create a ShapeCalculator object.""" + self.shape_calculator = ShapeCalculator() + + def start(self) -> None: + """Start the program by displaying the intro menu and handling user choices.""" + Output.welcome() + choice = Input.get_intro_menu_selection() + + match choice: + case 1: + self.handle_rect() + case 2: + self.handle_square() + case 3: + self.handle_square_and_rect() + case _: + print("You didn't enter a valid choice") + return + self.second_menu() + + def handle_rect(self) -> None: + """Handle the creation of a rectangle by collecting user input and storing it.""" + + width, height = Input.create_rectangle() + self.shape_calculator.create_rectangle(width, height) + print("Rectangle create successfully") + + def handle_square(self) -> None: + """Handle the creation of a square by collecting user input and storing it.""" + + side = Input.create_square() + self.shape_calculator.create_square(side) + print("Square create successfully") + + def handle_square_and_rect(self) -> None: + """Handle the creation of both a rectangle and a square.""" + + self.handle_rect() + self.handle_square() + + def second_menu(self) -> None: + """Display the secondary menu and handle subsequent user interactions.""" + + while True: + choice = Input.menu_after_shape() + + match choice: + case 1: + self.show_specs() + case 2: + self.calculate_square_in_rect() + + case 3: + self.modify_shapes() + case 4: + print("Thank you for using this program") + break + case _: + print("You didn't enter a valid choice") + + def show_specs(self) -> None: + """Display the specifications (area, perimeter, etc.) of the chosen shape.""" + + shape_choice = Input.get_shape() + + if shape_choice == 1: + if self.shape_calculator.rectangle: + Output.print_shape_specs(self.shape_calculator.rectangle) + else: + print("You didn't enter a valid shape") + elif shape_choice == 2: + if self.shape_calculator.square: + Output.print_shape_specs(self.shape_calculator.square) + else: + print("You didn't enter a valid shape") + + def calculate_square_in_rect(self) -> None: + """Calculate how many squares fit inside the rectangle. + + This method ensures that both shapes exist. If not, it prompts + the user to create them first. It then calls the calculator + to determine how many squares can fit within the rectangle. + """ + try: + if not self.shape_calculator.rectangle: + choice = Input.if_not_rectangle() + if choice == "y": + self.handle_rect() + else: + return + + if not self.shape_calculator.square: + choice = Input.if_not_square() + if choice == "y": + self.handle_square() + else: + return + + amount = self.shape_calculator.square_in_rectangle() + if amount is not None: + Output.print_how_square_in_rect(amount) + except Exception as e: + print(f"Error calculating square in rectangle: {e}") + + def modify_shapes(self) -> None: + """Allow the user to modify the measures of existing shapes. + + Raises: + Exception: If an unexpected error occurs during modification. + """ + try: + if not self.shape_calculator.rectangle and not self.shape_calculator.square: + print("No shape has been created.") + return + + result = Input.modify_measures() + + if result is None: + return + + if result[0] == "rectangle": + if self.shape_calculator.rectangle: + _, width, height = result + self.shape_calculator.set_width(width) + self.shape_calculator.set_height(height) + print("Rectangle modify successfully") + else: + print("You didn't enter a valid shape") + + elif result[0] == "square": + if self.shape_calculator.square: + _, side = result + self.shape_calculator.set_side(side) + print("Square modify successfully") + else: + print("You didn't enter a valid shape") + except Exception as e: + print(f"Error modifying shape: {e}") diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/input.py b/projects/002-polygon-area-calculator/python/poligon_calculator/input.py new file mode 100644 index 0000000..62bf3e5 --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/input.py @@ -0,0 +1,144 @@ +class Input: + """Class responsible for handling all user input operations.""" + + def create_rectangle() -> tuple[int, int]: + """Prompt the user to input the dimensions of a rectangle. + + Returns: + tuple[int, int]: A tuple containing the width and height of the rectangle. + """ + while True: + width: int = Input.validate_positive_number("Insert width: ") + height: int = Input.validate_positive_number("Insert height: ") + if width != height: + return width, height + else: + print("Width and height must be greater than or equal to two.") + + def create_square() -> int: + """Prompt the user to input the side length of a square. + + Returns: + int: The side length of the square. + """ + width: int = Input.validate_positive_number("Insert side length: ") + return width + + def get_intro_menu_selection() -> int | None: + """Display the main menu and get the user's choice of shape. + + Returns: + int | None: The user's menu selection (1, 2, or 3), or None if invalid input is entered. + """ + print("What shape do you want to work on?") + print("1. Rectangle") + print("2. Square") + print("3. Rectangle / Square\n") + try: + while (choice := (int(input("Enter your choice: ")))) not in (1, 2, 3): + print("Invalid input. Try again.") + return choice + except ValueError: + print("Please enter a number.") + + def validate_positive_number(prompt: str) -> int: + """Validate that the user input is a positive integer. + + Args: + prompt (str): The text prompt displayed to the user. + + Returns: + int: A positive integer input by the user. + """ + while True: + try: + number = int(input(prompt)) + if number > 0: + return number + + print("Please enter a positive number") + except ValueError: + print("Please enter a positive number") + + def if_not_square() -> str: + """Ask the user if they want to create a square when one does not exist. + + Returns: + str: 'y' if the user wants to create a square, 'n' otherwise. + """ + + print("For use this function, you must create a square shape.") + while (choose := (input("Do you want create it?(y/n) "))) not in ("y", "n"): + print("Please enter y or n") + return choose + + def if_not_rectangle() -> str: + """Ask the user if they want to create a rectangle when one does not exist. + + Returns: + str: 'y' if the user wants to create a rectangle, 'n' otherwise. + """ + print("For use this function, you must create a rectangle shape.") + while (choose := (input("Do you want create it?(y/n) "))) not in ("y", "n"): + print("Please enter y or n") + return choose + + def get_shape() -> int: + """Ask the user which shape they want to view. + + Returns: + int | None: 1 for rectangle, 2 for square, or None if invalid input is entered. + """ + print("Which shape do you want to view?") + print("1. Rectangle") + print("2. Square") + try: + while (choice := (int(input("Choose shape: ")))) not in (1, 2): + print("Invalid input. Try again.") + return choice + except ValueError: + print("Please enter a number.") + + def menu_after_shape() -> int: + """Display the post-creation menu and get the user's choice. + + Returns: + int | None: The user's menu selection (1–4), or None if invalid input is entered. + """ + print("1. Calculate shape specs (Area, Perimeter, Diagonal)") + print("2. Calculate amount square inside rectangle") + print("3. Modify measures") + print("4. Exit") + try: + while (choice := (int(input("Enter your choice: ")))) not in (1, 2, 3, 4): + print("Invalid input. Try again.") + return choice + except ValueError: + print("Please enter a number.") + + def modify_measures() -> tuple[str, int, int] | tuple[str, int] | None: + """Allow the user to modify the dimensions of a shape. + + Returns: + tuple[str, int, int] | tuple[str, int] | None: + A tuple containing the shape type and its new dimensions, + or None if invalid input is entered. + - ("rectangle", width, height) + - ("square", side) + """ + print("\nWhich shape do you want to modify?") + print("1. Rectangle") + print("2. Square") + + try: + while (choice := int(input("Choose shape: "))) not in (1, 2): + print("Please enter 1 or 2") + if choice == 1: + width = Input.validate_positive_number("Enter new width: ") + height = Input.validate_positive_number("Enter new height: ") + return "rectangle", width, height + elif choice == 2: + side = Input.validate_positive_number("Enter new side: ") + return "square", side + except ValueError: + print("Please enter a valid number") diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/output.py b/projects/002-polygon-area-calculator/python/poligon_calculator/output.py new file mode 100644 index 0000000..328273c --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/output.py @@ -0,0 +1,29 @@ +class Output: + """Class responsible for displaying information to the user.""" + + def welcome() -> None: + """Display a welcome message at the start of the program.""" + print("WELCOME TO THE SHAPE CALCULATOR\n") + + def print_shape_specs(shape) -> None: + """Print the specifications of a given shape, including dimensions, + area, perimeter, diagonal, and a visual representation. + + Args: + shape: The shape object containing geometric data. + """ + specs = f"{shape}:\n" + specs += f"Width: {shape.width} Height: {shape.height}\n" + specs += f"Area: {shape.get_area()}\n" + specs += f"Perimeter: {shape.get_perimeter()}\n" + specs += f"Diagonal: {shape.get_diagonal():.2f}\n" + specs += f"Pic:\n{shape.get_picture()}" + print(specs) + + def print_how_square_in_rect(amount: int) -> None: + """Print the number of squares that can fit inside a rectangle. + + Args: + amount (int): The number of squares that fit within the rectangle. + """ + print(f"In rectangle area can stay {amount} square units") diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/rectangle.py b/projects/002-polygon-area-calculator/python/poligon_calculator/rectangle.py new file mode 100644 index 0000000..8406cfa --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/rectangle.py @@ -0,0 +1,93 @@ +class Rectangle: + """Class representing a rectangle shape with width and height. + + Provides methods for computing geometric properties such as area, + perimeter, diagonal length, and ASCII visualization. + """ + + def __init__(self, width: int, height: int) -> None: + """Initialize a Rectangle instance. + + Args: + width (int): The width of the rectangle. + height (int): The height of the rectangle. + """ + self.width: int = width + self.height: int = height + + def set_width(self, width: int) -> None: + """Set a new width for the rectangle. + + Args: + width (int): The new width value. + """ + self.width = width + + def set_height(self, height: int) -> None: + """Set a new height for the rectangle. + + Args: + height (int): The new height value. + """ + self.height = height + + def get_area(self) -> int: + """Calculate the area of the rectangle. + + Returns: + int: The area of the rectangle. + """ + return self.width * self.height + + def get_perimeter(self) -> int: + """Calculate the perimeter of the rectangle. + + Returns: + int: The perimeter of the rectangle. + """ + return 2 * self.width + 2 * self.height + + def get_diagonal(self) -> float: + """Calculate the diagonal length of the rectangle. + + Returns: + float: The diagonal length, calculated using the Pythagorean theorem. + """ + return (self.width**2 + self.height**2) ** 0.5 + + def get_picture(self) -> str: + """Return a string representation (ASCII art) of the rectangle. + + If the rectangle is too large (width or height > 50), + a warning message is returned instead. + + Returns: + str: A multi-line string made of '*' representing the rectangle, + or a warning message if too large. + """ + if self.width > 50 or self.height > 50: + return "Too big for picture." + design = "" + for i in range(self.height): + design += ("*" * self.width) + "\n" + return design + + def get_amount_inside(self, shape) -> int: + """Determine how many times another shape fits inside this rectangle. + + Args: + shape (Rectangle): Another shape (typically a Square) + to fit inside this one. + + Returns: + int: The total number of times the given shape can fit inside. + """ + return (self.width // shape.width) * (self.height // shape.height) + + def __str__(self) -> str: + """Return a human-readable string representation of the rectangle. + + Returns: + str: A formatted description of the rectangle. + """ + return f"Rectangle (width={self.width}, height={self.height}" diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/shape_calculator.py b/projects/002-polygon-area-calculator/python/poligon_calculator/shape_calculator.py new file mode 100644 index 0000000..861c4b8 --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/shape_calculator.py @@ -0,0 +1,74 @@ +from typing import Optional + +from poligon_calculator.rectangle import Rectangle +from poligon_calculator.square import Square + + +class ShapeCalculator: + """Class responsible for managing and performing operations on geometric shapes. + + This class acts as a bridge between user input and geometric computations. + It supports creating, modifying, and calculating relationships between + rectangles and squares. + """ + + def __init__(self) -> None: + """Initialize the ShapeCalculator with no shapes created.""" + self.rectangle = None + self.square = None + + def create_rectangle(self, width: int, height: int) -> None: + """Create and store a new Rectangle object. + + Args: + width (int): The width of the rectangle. + height (int): The height of the rectangle. + """ + self.rectangle = Rectangle(width, height) + + def create_square(self, side: int) -> None: + """Create and store a new Square object. + + Args: + side (int): The side length of the square. + """ + self.square = Square(side) + + def set_height(self, height: int) -> None: + """Set the height of the current rectangle, if one exists. + + Args: + height (int): The new height value. + """ + if self.rectangle: + self.rectangle.set_height(height) + + def set_width(self, width: int) -> None: + """Set the width of the current rectangle, if one exists. + + Args: + width (int): The new width value. + """ + if self.rectangle: + self.rectangle.set_width(width) + + def set_side(self, side: int) -> None: + """Set the side length of the current square, if one exists. + + Args: + side (int): The new side length. + """ + if self.square: + self.square.set_side(side) + + def square_in_rectangle(self) -> Optional[int]: + """Calculate how many squares can fit inside the current rectangle. + + Returns: + Optional[int]: The number of squares that fit inside the rectangle, + or None if either shape has not been created yet. + """ + if self.rectangle is not None and self.square is not None: + amount: int = self.rectangle.get_amount_inside(self.square) + return amount + return None diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/square.py b/projects/002-polygon-area-calculator/python/poligon_calculator/square.py new file mode 100644 index 0000000..d158f60 --- /dev/null +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/square.py @@ -0,0 +1,38 @@ +from poligon_calculator.rectangle import Rectangle + + +class Square(Rectangle): + """Class representing a square, a special type of rectangle + where all sides have equal length. + """ + + def __init__(self, side: int) -> None: + """Initialize a Square instance. + + Args: + side (int): The length of each side of the square. + """ + super().__init__(height=side, width=side) + + self.width = side + self.height = side + self.side = side + + def set_side(self, side: int) -> None: + """Set a new side length for the square. + + Updates both width and height to maintain equality. + + Args: + side (int): The new side length of the square. + """ + self.height = side + self.width = side + + def __str__(self) -> str: + """Return a human-readable string representation of the square. + + Returns: + str: A formatted description of the square. + """ + return f"Square(side={self.width}" diff --git a/projects/002-polygon-area-calculator/python/poligon_calculator/tests/__init__.py b/projects/002-polygon-area-calculator/python/poligon_calculator/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/002-polygon-area-calculator/test_module.py b/projects/002-polygon-area-calculator/python/poligon_calculator/tests/test_module.py similarity index 52% rename from projects/002-polygon-area-calculator/test_module.py rename to projects/002-polygon-area-calculator/python/poligon_calculator/tests/test_module.py index d263a4b..dbcfdf9 100644 --- a/projects/002-polygon-area-calculator/test_module.py +++ b/projects/002-polygon-area-calculator/python/poligon_calculator/tests/test_module.py @@ -1,4 +1,5 @@ import unittest + import shape_calculator @@ -10,52 +11,77 @@ def setUp(self): def test_subclass(self): actual = issubclass(shape_calculator.Square, shape_calculator.Rectangle) expected = True - self.assertEqual(actual, expected, 'Expected Square class to be a subclass of the Rectangle class.') + self.assertEqual( + actual, + expected, + "Expected Square class_config to be a subclass of the Rectangle class_config.", + ) def test_distinct_classes(self): actual = shape_calculator.Square is not shape_calculator.Rectangle expected = True - self.assertEqual(actual, expected, 'Expected Square class to be a distinct class from the Rectangle class.') + self.assertEqual( + actual, + expected, + "Expected Square class_config to be a distinct class_config from the Rectangle class_config.", + ) def test_square_is_square_and_rectangle(self): - actual = isinstance(self.sq, shape_calculator.Square) and isinstance(self.sq, shape_calculator.Rectangle) + actual = isinstance(self.sq, shape_calculator.Square) and isinstance( + self.sq, shape_calculator.Rectangle + ) expected = True - self.assertEqual(actual, expected, 'Expected square object to be an instance of the Square class and the Rectangle class.') + self.assertEqual( + actual, + expected, + "Expected square object to be an instance of the Square class_config and the Rectangle class_config.", + ) def test_rectangle_string(self): actual = str(self.rect) expected = "Rectangle(width=3, height=6)" - self.assertEqual(actual, expected, 'Expected string representation of rectangle to be "Rectangle(width=3, height=6)"') + self.assertEqual( + actual, + expected, + 'Expected string representation of rectangle to be "Rectangle(width=3, height=6)"', + ) def test_square_string(self): actual = str(self.sq) expected = "Square(side=5)" - self.assertEqual(actual, expected, 'Expected string representation of square to be "Square(side=5)"') + self.assertEqual( + actual, + expected, + 'Expected string representation of square to be "Square(side=5)"', + ) def test_area(self): actual = self.rect.get_area() expected = 18 - self.assertEqual(actual, expected, 'Expected area of rectangle to be 18') + self.assertEqual(actual, expected, "Expected area of rectangle to be 18") actual = self.sq.get_area() expected = 25 - self.assertEqual(actual, expected, 'Expected area of square to be 25') - + self.assertEqual(actual, expected, "Expected area of square to be 25") def test_perimeter(self): actual = self.rect.get_perimeter() expected = 18 - self.assertEqual(actual, expected, 'Expected perimeter of rectangle to be 18') + self.assertEqual(actual, expected, "Expected perimeter of rectangle to be 18") actual = self.sq.get_perimeter() expected = 20 - self.assertEqual(actual, expected, 'Expected perimeter of square to be 20') + self.assertEqual(actual, expected, "Expected perimeter of square to be 20") def test_diagonal(self): actual = self.rect.get_diagonal() expected = 6.708203932499369 - self.assertEqual(actual, expected, 'Expected diagonal of rectangle to be 6.708203932499369') + self.assertEqual( + actual, expected, "Expected diagonal of rectangle to be 6.708203932499369" + ) actual = self.sq.get_diagonal() expected = 7.0710678118654755 - self.assertEqual(actual, expected, 'Expected diagonal of square to be 7.0710678118654755') + self.assertEqual( + actual, expected, "Expected diagonal of square to be 7.0710678118654755" + ) def test_set_attributes(self): self.rect.set_width(7) @@ -63,27 +89,41 @@ def test_set_attributes(self): self.sq.set_side(2) actual = str(self.rect) expected = "Rectangle(width=7, height=8)" - self.assertEqual(actual, expected, 'Expected string representation of rectangle after setting new values to be "Rectangle(width=7, height=8)"') + self.assertEqual( + actual, + expected, + 'Expected string representation of rectangle after setting new values to be "Rectangle(width=7, height=8)"', + ) actual = str(self.sq) expected = "Square(side=2)" - self.assertEqual(actual, expected, 'Expected string representation of square after setting new values to be "Square(side=2)"') + self.assertEqual( + actual, + expected, + 'Expected string representation of square after setting new values to be "Square(side=2)"', + ) self.sq.set_width(4) actual = str(self.sq) expected = "Square(side=4)" - self.assertEqual(actual, expected, 'Expected string representation of square after setting width to be "Square(side=4)"') + self.assertEqual( + actual, + expected, + 'Expected string representation of square after setting width to be "Square(side=4)"', + ) def test_rectangle_picture(self): self.rect.set_width(7) self.rect.set_height(3) actual = self.rect.get_picture() expected = "*******\n*******\n*******\n" - self.assertEqual(actual, expected, 'Expected rectangle picture to be different.') + self.assertEqual( + actual, expected, "Expected rectangle picture to be different." + ) def test_square_picture(self): self.sq.set_side(2) actual = self.sq.get_picture() expected = "**\n**\n" - self.assertEqual(actual, expected, 'Expected square picture to be different.') + self.assertEqual(actual, expected, "Expected square picture to be different.") def test_big_picture(self): self.rect.set_width(51) @@ -97,19 +137,20 @@ def test_get_amount_inside(self): self.rect.set_width(15) actual = self.rect.get_amount_inside(self.sq) expected = 6 - self.assertEqual(actual, expected, 'Expected `get_amount_inside` to return 6.') + self.assertEqual(actual, expected, "Expected `get_amount_inside` to return 6.") def test_get_amount_inside_two_rectangles(self): rect2 = shape_calculator.Rectangle(4, 8) actual = rect2.get_amount_inside(self.rect) expected = 1 - self.assertEqual(actual, expected, 'Expected `get_amount_inside` to return 1.') + self.assertEqual(actual, expected, "Expected `get_amount_inside` to return 1.") def test_get_amount_inside_none(self): rect2 = shape_calculator.Rectangle(2, 3) actual = rect2.get_amount_inside(self.rect) expected = 0 - self.assertEqual(actual, expected, 'Expected `get_amount_inside` to return 0.') - + self.assertEqual(actual, expected, "Expected `get_amount_inside` to return 0.") + + if __name__ == "__main__": unittest.main() diff --git a/projects/002-polygon-area-calculator/shape_calculator.py b/projects/002-polygon-area-calculator/shape_calculator.py deleted file mode 100644 index e210be6..0000000 --- a/projects/002-polygon-area-calculator/shape_calculator.py +++ /dev/null @@ -1,6 +0,0 @@ -class Rectangle: - - - - -class Square: diff --git a/projects/__init__.py b/projects/__init__.py new file mode 100644 index 0000000..e69de29