diff --git a/src/BeeBot.py b/src/BeeBot.py index db5bb55..ffb0d51 100644 --- a/src/BeeBot.py +++ b/src/BeeBot.py @@ -98,54 +98,54 @@ def move_backward(self): # The origin is in the top left corner if self.heading == Heading.SOUTH: for _ in range(0, self.step): - self.screen_location.y = self.screen_location.y - 1 + self.screen_location = self.screen_location - (0, 1) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.y = self.logical_position.y - 1 + self.logical_position = self.logical_position - (0, 1) elif self.heading == Heading.WEST: for _ in range(0, self.step): - self.screen_location.x = self.screen_location.x + 1 + self.screen_location = self.screen_location + (1, 0) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.x = self.logical_position.x + 1 + self.logical_position = self.logical_position + (1, 0) elif self.heading == Heading.NORTH: for _ in range(0, self.step): - self.screen_location.y = self.screen_location.y + 1 + self.screen_location = self.screen_location + (0, 1) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.y = self.logical_position.y + 1 + self.logical_position = self.logical_position + (0, 1) elif self.heading == Heading.EAST: for _ in range(0, self.step): - self.screen_location.x = self.screen_location.x - 1 + self.screen_location = self.screen_location - (1, 0) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.x = self.logical_position.x - 1 + self.logical_position = self.logical_position - (1, 0) def move_forward(self): """Move the BeeBot forward.""" # The origin is in the top left corner if self.heading == Heading.NORTH: for _ in range(0, self.step): - self.screen_location.y = self.screen_location.y - 1 + self.screen_location = self.screen_location - (0, 1) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.y = self.logical_position.y - 1 + self.logical_position = self.logical_position - (0, 1) elif self.heading == Heading.EAST: for _ in range(0, self.step): - self.screen_location.x = self.screen_location.x + 1 + self.screen_location = self.screen_location + (1, 0) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.x = self.logical_position.x + 1 + self.logical_position = self.logical_position + (1, 0) elif self.heading == Heading.SOUTH: for _ in range(0, self.step): - self.screen_location.y = self.screen_location.y + 1 + self.screen_location = self.screen_location + (0, 1) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.y = self.logical_position.y + 1 + self.logical_position = self.logical_position + (0, 1) elif self.heading == Heading.WEST: for _ in range(0, self.step): - self.screen_location.x = self.screen_location.x - 1 + self.screen_location = self.screen_location - (1, 0) sleep(0.01) # sleep prevents the BeeBot moving too quickly - self.logical_position.x = self.logical_position.x - 1 + self.logical_position = self.logical_position - (1, 0) def move_right(self): """Turn the BeeBot right.""" diff --git a/src/Board.py b/src/Board.py index 76a2a52..98eb5c6 100644 --- a/src/Board.py +++ b/src/Board.py @@ -73,18 +73,22 @@ def display(self, screen): # Draw lines over Board background image if self.border_colour is not None: for iter_width in range(0, self.board_width + 1, self.step): - pygame.draw.line(screen, - self.border_colour, - (iter_width, 0), - (iter_width, self.board_height), - 5) + + line_start = Point(iter_width, 0) + line_end = Point(iter_width, self.board_height) + + # Draw a line from line_start to line_end. + pygame.draw.line(screen, self.border_colour, + line_start, line_end, 5) for iter_height in range(0, self.board_height + 1, self.step): - pygame.draw.line(screen, - self.border_colour, - (0, iter_height), - (self.board_width, iter_height), - 5) + + line_start = Point(0, iter_height) + line_end = Point(self.board_width, iter_height) + + # Draw a line from line_start to line_end. + pygame.draw.line(screen, self.border_colour, + line_start, line_end, 5) def is_equal_to(self, other_component): """Compare this Board for equality with other_component.""" diff --git a/src/CommandLog.py b/src/CommandLog.py index bb86a61..c313171 100644 --- a/src/CommandLog.py +++ b/src/CommandLog.py @@ -38,6 +38,13 @@ def _convert_memory_to_icons(self, beebot_memory): # The location to draw the first Icon in the CommandLog. location = self.screen_location + if self.horizontal: + # Make the next Icon appear right of the current one. + increment = (self.icon_size[0], 0) + else: + # Make the next Icon appear below the current one. + increment = (0, self.icon_size[1]) + for index, entry in enumerate(beebot_memory): # Convert Events into Icon Arrows. text = self._event_type_to_text(entry.type) @@ -52,12 +59,8 @@ def _convert_memory_to_icons(self, beebot_memory): # that would otherwise override each other. self.add(tmp_log, key=index) - if self.horizontal: - # Make the next Icon appear right of the current one. - location = (location[0] + self.icon_size[0], location[1]) - else: - # Make the next Icon appear below the current one. - location = (location[0], location[1] + self.icon_size[1]) + # Put the next Icon in the right place + location = location + increment @classmethod def _event_type_to_text(cls, event_type): diff --git a/src/Component.py b/src/Component.py index e4ee27d..7783359 100644 --- a/src/Component.py +++ b/src/Component.py @@ -20,7 +20,7 @@ def __init__(self, self.sprite = sprite # The position of the Component in terms of squares on the screen - self.logical_position = start_logical_position + self.logical_position = start_logical_position.copy() # The position of the Goal in terms pixels self.screen_location = start_logical_position.scale(step) @@ -31,8 +31,7 @@ def __init__(self, def display(self, screen): """Draw the Component object on screen, if it has a sprite.""" if self.sprite is not None: - screen.blit(self.sprite, (self.screen_location.x, - self.screen_location.y)) + screen.blit(self.sprite, self.screen_location) def is_equal_to(self, other_component): """Compare this Component for equality with other_component.""" diff --git a/src/GameWindow.py b/src/GameWindow.py index d5c709e..0374a23 100644 --- a/src/GameWindow.py +++ b/src/GameWindow.py @@ -16,6 +16,7 @@ from src.ButtonGroup import ButtonGroup from src.CommandLog import CommandLog from src.CustomEvent import CustomEvent +from src.Point import Point from src.Scenario import Scenario from src import __version__ @@ -128,7 +129,7 @@ def run(self): # Display the logo (if any) if self.logo is not None: self.screen.blit(self.logo, - (self.width + 69, self.height - 85)) + Point(self.width + 69, self.height - 85)) # Display any Buttons self.buttons.display(self.screen) @@ -229,7 +230,7 @@ def choose_scenario(self): temp = Button(os.path.splitext(scenario_file)[0], GameWindow.BLACK, GameWindow.WHITE, - (width_counter, height_counter), + Point(width_counter, height_counter), (120, 120)) # Add temp Button to ButtonGroup @@ -321,14 +322,16 @@ def load_scenario(self): # - the CommandLog below the map. self.size = (self.width + 400, self.height + 30) # Create the empty CommandLog - self.command_log = CommandLog((0, self.height), (self.width, 30)) + self.command_log = CommandLog(Point(0, self.height), + (self.width, 30)) else: # Make space for: # - the Buttons below the map. # - the CommandLog to the right of the map. self.size = (self.width + 30, self.height + 400) # Create the empty CommandLog - self.command_log = CommandLog((self.width, 0), (30, self.height)) + self.command_log = CommandLog(Point(self.width, 0), + (30, self.height)) # Only want to do this once, so sadly can't do it in the rendering # loop without a potential race condition as @@ -345,8 +348,8 @@ def create_buttons(self, buttons_on_the_left): forward_button = Button('Forward', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 140, - float(self.height)/2 - 240), + Point(self.width + 140, + float(self.height)/2 - 240), (120, 120)) self.buttons.add(forward_button) @@ -354,8 +357,8 @@ def create_buttons(self, buttons_on_the_left): backward_button = Button('Backward', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 140, - float(self.height)/2 + 20), + Point(self.width + 140, + float(self.height)/2 + 20), (120, 120)) self.buttons.add(backward_button) @@ -363,8 +366,8 @@ def create_buttons(self, buttons_on_the_left): turn_left_button = Button('Turn Left', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 10, - float(self.height)/2 - 110), + Point(self.width + 10, + float(self.height)/2 - 110), (120, 120)) self.buttons.add(turn_left_button) @@ -372,8 +375,8 @@ def create_buttons(self, buttons_on_the_left): turn_right_button = Button('Turn Right', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 270, - float(self.height)/2 - 110), + Point(self.width + 270, + float(self.height)/2 - 110), (120, 120)) self.buttons.add(turn_right_button) @@ -381,8 +384,8 @@ def create_buttons(self, buttons_on_the_left): go_button = Button('Go', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 140, - float(self.height)/2 - 110), + Point(self.width + 140, + float(self.height)/2 - 110), (120, 120)) self.buttons.add(go_button) @@ -390,8 +393,8 @@ def create_buttons(self, buttons_on_the_left): stop_button = Button('Stop', GameWindow.WHITE, GameWindow.RED, - (self.width + 140, - float(self.height)/2 - 110), + Point(self.width + 140, + float(self.height)/2 - 110), (120, 120), False) @@ -400,8 +403,8 @@ def create_buttons(self, buttons_on_the_left): reset_button = Button('Reset', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 10, - float(self.height)/2 + 20), + Point(self.width + 10, + float(self.height)/2 + 20), (120, 120)) self.buttons.add(reset_button) @@ -409,8 +412,8 @@ def create_buttons(self, buttons_on_the_left): clear_button = Button('Clear', GameWindow.BLACK, GameWindow.WHITE, - (self.width + 270, - float(self.height)/2 + 20), + Point(self.width + 270, + float(self.height)/2 + 20), (120, 120)) self.buttons.add(clear_button) @@ -419,8 +422,8 @@ def create_buttons(self, buttons_on_the_left): forward_button = Button('Forward', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 - 60, - self.height + 10), + Point(float(self.width)/2 - 60, + self.height + 10), (120, 120)) self.buttons.add(forward_button) @@ -428,8 +431,8 @@ def create_buttons(self, buttons_on_the_left): backward_button = Button('Backward', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 - 60, - self.height + 270), + Point(float(self.width)/2 - 60, + self.height + 270), (120, 120)) self.buttons.add(backward_button) @@ -437,8 +440,8 @@ def create_buttons(self, buttons_on_the_left): turn_left_button = Button('Turn Left', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 - 190, - self.height + 140), + Point(float(self.width)/2 - 190, + self.height + 140), (120, 120)) self.buttons.add(turn_left_button) @@ -446,8 +449,8 @@ def create_buttons(self, buttons_on_the_left): turn_right_button = Button('Turn Right', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 + 70, - self.height + 140), + Point(float(self.width)/2 + 70, + self.height + 140), (120, 120)) self.buttons.add(turn_right_button) @@ -455,7 +458,8 @@ def create_buttons(self, buttons_on_the_left): go_button = Button('Go', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 - 60, self.height + 140), + Point(float(self.width)/2 - 60, + self.height + 140), (120, 120)) self.buttons.add(go_button) @@ -463,8 +467,8 @@ def create_buttons(self, buttons_on_the_left): stop_button = Button('Stop', GameWindow.WHITE, GameWindow.RED, - (float(self.width)/2 - 60, - self.height + 140), + Point(float(self.width)/2 - 60, + self.height + 140), (120, 120), False) @@ -473,8 +477,8 @@ def create_buttons(self, buttons_on_the_left): reset_button = Button('Reset', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 - 190, - self.height + 270), + Point(float(self.width)/2 - 190, + self.height + 270), (120, 120)) self.buttons.add(reset_button) @@ -482,8 +486,8 @@ def create_buttons(self, buttons_on_the_left): clear_button = Button('Clear', GameWindow.BLACK, GameWindow.WHITE, - (float(self.width)/2 + 70, - self.height + 270), + Point(float(self.width)/2 + 70, + self.height + 270), (120, 120)) self.buttons.add(clear_button) diff --git a/src/Icon.py b/src/Icon.py index fc71c52..119dda8 100644 --- a/src/Icon.py +++ b/src/Icon.py @@ -7,6 +7,7 @@ """ from enum import Enum import pygame +from src.Point import Point class Icon(): @@ -57,17 +58,21 @@ def _text_to_shape(cls, text): def _get_vertex_list(self, array, center, size=(120, 120)): """Return a translated list of vertices within the given size.""" to_return = [] - # For each vector + + # For each tuple in the tuple array for i in range(0, len(array)): - current = array[i] - # Append a vertex tuple to to_return - to_return.append( - ( - # We assume all Icon shapes are originally 120x120 - (current[0] * size[0] / 120) + center[0], - (current[1] * size[1] / 120) + center[1] - ) - ) + # Convert the tuple to a Point to make the maths easier + current = Point(array[i]) + + # We assume all Icon shapes are originally 120x120 + original_size = (120, 120) + + # Translate and scale the current Point + new_point = (current * size / original_size) + center + + # Append the translated and scaled Point to to_return + to_return.append(new_point) + return to_return def display(self, screen): @@ -98,7 +103,10 @@ def display(self, screen): class Arrow(Enum): """This class defines Enums for the arrow polygon of a Icon.""" - # Array of vectors for each arrow + # Array of tuples for each vertex in the arrow shape. + # These are deliberatly not Point objects as the describe the shape with + # reference to some origin (in this case (0,0)), rather than in terms of + # points on the screen that can be drawn. FORWARD = [(20, -20), (0, -40), (-20, -20), (-10, -20), (-10, 40), (10, 40), (10, -20)] diff --git a/src/Point.py b/src/Point.py index 7d02ae6..21013b4 100644 --- a/src/Point.py +++ b/src/Point.py @@ -1,17 +1,32 @@ """This file defines a 2D Point class.""" -class Point(): - """This class defines an individual 2D Point.""" +class Point(tuple): + """ + This class defines an individual 2D Point. - def __init__(self, x, y=None): + The point class should not be used for 2D dimensions like size, + for that a tuple should be used. + """ + + def __new__(cls, x, y=None): """Define a 2D point.""" - if y is None: # then assume we were passed a tuple - self.x = x[0] - self.y = x[1] - else: - self.x = x - self.y = y + if y is None: + # then assume we were passed a tuple + return super().__new__(cls, x) + + # Otherwise we assume we were passed two numbers. + return super().__new__(cls, (x, y)) + + @property + def x(self): + """Access the x coordinate.""" + return self[0] + + @property + def y(self): + """Access the y coordinate.""" + return self[1] def is_equal_to(self, other_point): """True if both x and y are equal in self and other_point.""" @@ -24,3 +39,31 @@ def scale(self, multipler): def copy(self): """Return a copy of this Point object.""" return Point(self.x, self.y) + + def __add__(self, other_point): + """Add two Point objects coordinate by coordinate.""" + if isinstance(other_point, tuple): + return Point(self[0] + other_point[0], self[1] + other_point[1]) + + return NotImplemented + + def __sub__(self, other_point): + """Subtract two Point objects coordinate by coordinate.""" + if isinstance(other_point, tuple): + return Point(self[0] - other_point[0], self[1] - other_point[1]) + + return NotImplemented + + def __mul__(self, other_point): + """Multiply two Point objects coordinate by coordinate.""" + if isinstance(other_point, tuple): + return Point(self[0] * other_point[0], self[1] * other_point[1]) + + return NotImplemented + + def __truediv__(self, other_point): + """Divide two Point objects coordinate by coordinate.""" + if isinstance(other_point, tuple): + return Point(self[0] / other_point[0], self[1] / other_point[1]) + + return NotImplemented diff --git a/src/Scenario.py b/src/Scenario.py index 98fc0e4..f3ba725 100644 --- a/src/Scenario.py +++ b/src/Scenario.py @@ -161,7 +161,7 @@ def get_beebot_start_position(self): def set_beebot_start_position(self, x_coord, y_coord): """Set the BeeBot's starting position.""" - self._elements['BeeBotStartPosition'] = (x_coord, y_coord) + self._elements['BeeBotStartPosition'] = Point(x_coord, y_coord) def get_logical_height(self): """Get the Board height.""" diff --git a/test/test_button.py b/test/test_button.py index 5fa703b..1786264 100644 --- a/test/test_button.py +++ b/test/test_button.py @@ -4,6 +4,7 @@ import pygame from src.Button import Button from src.GameWindow import GameWindow +from src.Point import Point class TestButton(unittest.TestCase): @@ -15,11 +16,11 @@ def setUp(self): # Create a test Button with text self.test_text_button = Button('text', GameWindow.BLACK, - GameWindow.WHITE, (0, 0), (10, 10)) + GameWindow.WHITE, Point(0, 0), (10, 10)) # Create a test Button with an Arrow self.test_icon_button = Button('Forward', GameWindow.BLACK, - GameWindow.WHITE, (0, 0), (10, 10)) + GameWindow.WHITE, Point(0, 0), (10, 10)) @classmethod def test_init(cls): diff --git a/test/test_button_group.py b/test/test_button_group.py index 0c76a67..4d1f543 100644 --- a/test/test_button_group.py +++ b/test/test_button_group.py @@ -4,6 +4,7 @@ import pygame from src.Button import Button from src.ButtonGroup import ButtonGroup +from src.Point import Point class TestButtonGroup(unittest.TestCase): @@ -19,9 +20,9 @@ def setUp(self): # Create some test Buttons self.test_button = Button("Test", (0, 0, 0), (255, 255, 255), - (0, 0), (10, 10)) + Point(0, 0), (10, 10)) self.other_button = Button("Other", (0, 0, 0), (255, 255, 255), - (20, 20), (10, 10)) + Point(20, 20), (10, 10)) # Add those test Buttons to the test ButtonGroup self.test_button_group.add(self.other_button) @@ -36,14 +37,14 @@ def test_get_named_button(self): def test_get_pressed_button(self): """Test the get_pressed_button method.""" # Simulate a mouse cick - position = (5, 5) + position = Point(5, 5) returned_button = self.test_button_group.get_pressed_button(position) self.assertIs(self.test_button, returned_button) # Simulate a mouse click elswhere, not over a Button - position = (15, 15) + position = Point(15, 15) returned_button = self.test_button_group.get_pressed_button(position) diff --git a/test/test_command_log.py b/test/test_command_log.py index 023ea3f..aab7cec 100644 --- a/test/test_command_log.py +++ b/test/test_command_log.py @@ -5,6 +5,7 @@ import pygame from src.CommandLog import CommandLog from src.CustomEvent import CustomEvent +from src.Point import Point class TestCommandLog(unittest.TestCase): @@ -15,7 +16,7 @@ def setUp(self): # Start pygame as Icons require fonts. pygame.init() - self.test_command_log = CommandLog((0, 0), (25, 5)) + self.test_command_log = CommandLog(Point(0, 0), (25, 5)) self.beebot_mem = [pygame.event.Event(CustomEvent.MOVE_BEEBOT_UP), pygame.event.Event(CustomEvent.MOVE_BEEBOT_LEFT), @@ -32,7 +33,7 @@ def test_update(self): self.assertEqual(icon.size, (5, 5)) # Check each subsequent Icon is 5 pixels # to the left of the previous. - self.assertEqual(icon.screen_location, (index * 5, 0)) + self.assertEqual(icon.screen_location, Point(index * 5, 0)) # Patch so we can tell if removal_all is called @mock.patch('src.IconGroup.IconGroup.removal_all') diff --git a/test/test_default_scenario_run.py b/test/test_default_scenario_run.py index 143a3b3..446224b 100644 --- a/test/test_default_scenario_run.py +++ b/test/test_default_scenario_run.py @@ -19,36 +19,40 @@ def setUp(self): self.test_game_window.start_rendering() # This list is used to generate 'Fake' Button press/release events - buttons = [("Forward", 950, 420), ("Backward", 950, 680), - ("Turn Left", 820, 550), ("Turn Right", 1080, 550), - ("Go", 950, 550), ("Reset", 820, 680), ("Clear", 1080, 680), - ("Stop", 950, 550)] + buttons = [("Forward", Point(950, 420)), + ("Backward", Point(950, 680)), + ("Turn Left", Point(820, 550)), + ("Turn Right", Point(1080, 550)), + ("Go", Point(950, 550)), + ("Reset", Point(820, 680)), + ("Clear", Point(1080, 680)), + ("Stop", Point(950, 550))] # These dictionaries will store the events self.button_down_events = {} self.button_up_events = {} # Use the above buttons list to generate events - for button in buttons: + for button_text, button_point in buttons: # 'Fake' Button press down_event = pygame.event.Event(pygame.MOUSEBUTTONDOWN) # Simulate a left click down_event.button = 1 # Simulate the position of the click - down_event.pos = (button[1], button[2]) + down_event.pos = (button_point.x, button_point.y) # Add this event to the event dictionary, # using the corresponding Button text as the key - self.button_down_events[button[0]] = down_event + self.button_down_events[button_text] = down_event # 'Fake' Button release up_event = pygame.event.Event(pygame.MOUSEBUTTONUP) # Simulate a left click up_event.button = 1 # Simulate the position of the click - up_event.pos = (button[1], button[2]) + up_event.pos = (button_point.x, button_point.y) # Add this event to the event dictionary, # using the corresponding Button text as the key - self.button_up_events[button[0]] = up_event + self.button_up_events[button_text] = up_event def _press_button(self, button_text): """Fake a Button press corresponding to button_text.""" diff --git a/test/test_icon.py b/test/test_icon.py index e300865..5c002ad 100644 --- a/test/test_icon.py +++ b/test/test_icon.py @@ -3,6 +3,7 @@ import pygame from src.GameWindow import GameWindow from src.Icon import Icon +from src.Point import Point class TestIcon(unittest.TestCase): @@ -13,11 +14,11 @@ def setUp(self): pygame.init() self.test_text_icon = Icon('test icon', GameWindow.BLACK, GameWindow.WHITE, - (0, 0), (10, 10)) + Point(0, 0), (10, 10)) self.test_vertex_icon = Icon('vertex icon', GameWindow.BLACK, GameWindow.WHITE, - (0, 0), (10, 10)) + Point(0, 0), (10, 10)) self.test_vertex_icon.vertices = [(10, 10), (-10, -10), (0, 0)] @@ -26,17 +27,17 @@ def test_init(cls): """Test the init method of the Icon class.""" _unused_icon = Icon('test icon', GameWindow.BLACK, GameWindow.WHITE, - (0, 0), (10, 10)) + Point(0, 0), (10, 10)) def test_get_vertex_list(self): """Test the _get_vertex_list method.""" - input_list = [(0, 0), (5, 0), (0, -5)] + input_list = [Point(0, 0), Point(5, 0), Point(0, -5)] center = (10, 10) # _get_vertex_list(input_list, center_x, center_y) should map # (0,0) of the input_list to center_x and center_y (and all other # points accordingly) - expected_output = [(10, 10), (15, 10), (10, 5)] + expected_output = [Point(10, 10), Point(15, 10), Point(10, 5)] # We need to use an instance of the Icon class # to call the _get_vertex_list method diff --git a/test/test_point.py b/test/test_point.py new file mode 100644 index 0000000..a0fe635 --- /dev/null +++ b/test/test_point.py @@ -0,0 +1,64 @@ +"""This class contains the TestPoint class.""" +import unittest +from src.Point import Point + + +class TestPoint(unittest.TestCase): + """This class unit tests the Point class.""" + + def setUp(self): + """Define two points for use in this test case.""" + self.point1 = Point(2, 3) + self.point2 = Point(6, 12) + + def test_x(self): + """Test using the 'x' property.""" + self.assertEqual(self.point1.x, 2) + self.assertEqual(self.point2.x, 6) + + def test_y(self): + """Test using the 'y' property.""" + self.assertEqual(self.point1.y, 3) + self.assertEqual(self.point2.y, 12) + + def test_add(self): + """Test adding two Point objects.""" + add_result = self.point1 + self.point2 + self.assertTrue(add_result.is_equal_to(Point(8, 15))) + + def test_multiply(self): + """Test multiplying two Point objects.""" + multiply_result = self.point1 * self.point2 + self.assertTrue(multiply_result.is_equal_to(Point(12, 36))) + + def test_divide(self): + """Test dividing two Point objects.""" + divide_result = self.point2 / self.point1 + self.assertTrue(divide_result.is_equal_to(Point(3, 4))) + + def test_subtract(self): + """Test subtracting two Point objects.""" + subtract_result = self.point2 - self.point1 + self.assertTrue(subtract_result.is_equal_to(Point(4, 9))) + + def test_scale(self): + """Test scaling a Point object.""" + scale_result = self.point1.scale(2) + self.assertTrue(scale_result.is_equal_to(Point(4, 6))) + + def test_copy(self): + """Test copying a Point object.""" + copy_result = self.point1.copy() + + # Check the copied Point object is equal to the original but is not + # the original object. + self.assertTrue(copy_result.is_equal_to(self.point1)) + self.assertTrue(copy_result is not self.point1) + + def test_is_equal(self): + """Test the Point is_equal_to method.""" + self.assertTrue(self.point1.is_equal_to(Point(2, 3))) + + +if __name__ == "__main__": + unittest.main()