"""
PRACTICE Exam 3.

This problem provides practice at:  *** IMPLEMENTING CLASSES. ***

Authors: David Mutchler, Vibha Alangar, Dave Fisher, Matt Boutell, Mark Hays,
         Mohammed Noureddine, Sana Ebrahimi, Sriram Mohan, their colleagues and
         PUT_YOUR_NAME_HERE.
"""  # TODO: 1. PUT YOUR NAME IN THE ABOVE LINE.

###############################################################################
# TODO: 2.
#  Students:
#  __
#  These problems have DIFFICULTY and TIME ratings:
#    DIFFICULTY rating:  1 to 10, where:
#       1 is very easy
#       3 is an "easy" Exam 3 question.
#       5 is a "typical" Exam 3 question.
#       7 is a "hard" Exam 3 question.
#      10 is an EXTREMELY hard problem (too hard for a Exam 3 question)
#  __
#    TIME ratings: A ROUGH estimate of the number of minutes that we
#       would expect a well-prepared student to take on the problem.
#  __
#    IMPORTANT: For ALL the problems in this module,
#      if you reach the time estimate and are NOT close to a solution,
#      STOP working on that problem and ASK YOUR INSTRUCTOR FOR HELP on it,
#      in class or via Piazza.
#  __
#  After you read (and understand) the above, change this _TODO_ to DONE.
###############################################################################

import math
import time
from numbers import Number
from typing import List
import testing_helper


###############################################################################
# TODO: 3.  READ the   Point   class defined below.
#  Note especially its methods:
#      clone
#      distance_from
#  For full credit, you must use (call) these as appropriate in your code.
#  After you UNDERSTAND the Point class, change the above _TODO_ to DONE.
###############################################################################
class Point:
    """ Represents a point in 2-dimensional space. """

    def __init__(self, x, y):
        """ Sets instance variables  x  and  y  to the given coordinates. """
        self.x = x
        self.y = y

    def __repr__(self):
        """
        Returns a string representation of this Point.
        For each coordinate (x and y), the representation:
          - Uses no decimal points if the number is close to an integer,
          - Else it uses 2 decimal places after the decimal point.
        Examples:
           Point(10, 3.14)
           Point(3.01, 2.99)
        """
        decimal_places = 2  # Use 2 places after the decimal point

        formats = []
        numbers = []
        for coordinate in (self.x, self.y):
            if abs(coordinate - round(coordinate)) < (10 ** -decimal_places):
                # Treat it as an integer:
                formats.append('{}')
                numbers.append(round(coordinate))
            else:
                # Treat it as a float to decimal_places decimal places:
                formats.append('{:.' + str(decimal_places) + 'f}')
                numbers.append(round(coordinate, decimal_places))

        format_string = 'Point(' + formats[0] + ', ' + formats[1] + ')'
        return format_string.format(numbers[0], numbers[1])

    def __eq__(self, p2):
        """
        Defines == for Points:  a == b   is equivalent to  a.__eq__(b).
        Treats two numbers as "equal" if they are within 6 decimal
        places of each other for both x and y coordinates.
        """
        return (round(self.x, 6) == round(p2.x, 6) and
                round(self.y, 6) == round(p2.y, 6))

    def clone(self):
        """ Returns a Point whose x and y are the same as this Point's. """
        return Point(self.x, self.y)

    def distance_from(self, p2):
        """ Returns the distance this Point is from the given Point. """
        dx_squared = (self.x - p2.x) ** 2
        dy_squared = (self.y - p2.y) ** 2
        return math.sqrt(dx_squared + dy_squared)


###############################################################################
# The  main  function, which calls the tests.
###############################################################################
def main():
    """ Calls the   TEST   functions in this module. """
    # -------------------------------------------------------------------------
    # Uncomment the following calls to the testing functions one at a time
    # as you work the problems.
    # -------------------------------------------------------------------------
    print()
    print('Un-comment the calls in MAIN one by one')
    print(' to run the testing code as you complete the TODOs.')

    # run_test_init()
    # run_test_multiply_me()
    # run_test_make_child()
    # run_test_get_point()
    # run_test_get_distance()
    # run_test_swap_colors()
    # run_test_get_recent_color()
    # run_test_get_bigger_size_color()


###############################################################################
# The   Blob   class (and its methods) begins here.
###############################################################################
# -----------------------------------------------------------------------------
# DIFFICULTY AND TIME RATINGS (see top of this file for explanation)
#    DIFFICULTY for the methods in this class: the first several are 3-5,
#       the last two are 5-6.
#    TIME ESTIMATE for implementing the ENTIRE class:   35 to 45 minutes.
# -----------------------------------------------------------------------------

class Blob:

    def __init__(self, c, n):
        """
        What comes in:
          -- self
          -- a string that represents a color
          -- a positive number
        What goes out: Nothing (i.e., None).
        Side effects:
           Sets instance variables to the given arguments, naming them:
             -- self.color
             -- self.size
           Also, sets other instance variables as needed by other Blob methods.
        Type hints:
          :type c: str
          :type n: float
        """
        # ---------------------------------------------------------------------
        # TODO: 4.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def multiply_me(self):
        """
        What comes in:
          -- self
        What goes out: Nothing (i.e., None).
        Side effects:
           Mutates this Blob by multiplying its size by 10.
        """
        # ---------------------------------------------------------------------
        # TODO: 5.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def make_child(self, other_blob):
        """
        What comes in:
          -- self
          -- other_blob:  Another Blob
        What goes out:
          Returns a new Blob object whose:
             -- color is the same as this Blob's color
             -- size is the same as the given other_blob's size
        Side effects: None.
        Type hints:
          :type other_blob: Blob
          :rtype: Blob
        """
        # ---------------------------------------------------------------------
        # TODO: 6.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def get_point(self, blob2):
        """
        What comes in:
          -- self
          -- blob2:  Another Blob
        What goes out:
          Returns a Point whose
            -- x-coordinate is this Blob's size
            -- y-coordinate is the given blob2's size.
        Side effects: None.
        Type hints:
          :type blob2: Blob
          :rtype: Point
        """
        # ---------------------------------------------------------------------
        # TODO: 7.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def get_distance(self, other_blob):
        """
        What comes in:
          -- self
          -- other_blob:  Another Blob
        What goes out:
          Returns the distance between the following two Points:
            -- the Point obtained by calling  get_point  (above) on this Blob
                 with argument the given other_blob
            -- the Point obtained by calling  get_point  on the given other_blob
                 with argument this Blob

        For credit, you:
           MUST call the  get_point      method in this Blob class!!!
           MUST call the  distance_from  method in the Point class!!!

        Side effects: None.
        Type hints:
          :type other_blob: Blob
          :rtype: float
        """
        # ---------------------------------------------------------------------
        # TODO: 8.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def swap_colors(self, blob2):
        """
        What comes in:
          -- self
          -- blob2:  Another Blob
        What goes out: Nothing (i.e., None)
        Side effects:
          Swaps this Blob's color with the given blob2's color.
        Type hints:
          :type blob2: Blob
        """
        # ---------------------------------------------------------------------
        # TODO: 9.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def get_recent_color(self):
        """
        Returns the color of the Blob that this Blob's  make_child  method
        has most recently returned.  If this Blob's  make_child  method
        has not yet been called, returns None.
        """
        # ---------------------------------------------------------------------
        # TODO: 10.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------

    def get_bigger_size_color(self, more_blobs):
        """
        What comes in:
          -- self
          -- a sequence of Blob objects
        What goes out:
          Returns the color of the first (i.e., lowest-index) Blob in the given
          sequence of Blob objects whose size is greater than this Blob's size.
          Returns "no color" (spelled just like that) if no Blob objects in the
          given sequence of Blob objects have size greater than this Blob's
          size.
        Side effects: None.
        Type hints:
          :type more_blobs: list[Blob]
          :rtype: str
        """
        # ---------------------------------------------------------------------
        # TODO: 11.
        #  a. READ specifications of this method (above).
        #       Also READ its tests (below) if you need additional
        #       clarification of the specification of this method.
        #  b. Implement and test this method.
        # ---------------------------------------------------------------------


###############################################################################
# The TEST functions for the  Blob  class begin here.
###############################################################################
def run_test_init():
    """ Tests the   __init__   method of the Blob class. """

    print()
    print('-----------------------------------------------------------')
    print('Testing the   __init__   method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 50)
    run_test_instance_variables(blob_1, "red", 50)

    # Test 2
    print('\nTest 2:')
    blob_2 = Blob("blue", 88)
    run_test_instance_variables(blob_2, "blue", 88)

    # Test 3
    print('\nTest 3:')
    blob_1.color = "green"
    run_test_instance_variables(blob_1, "green", 50)


def run_test_multiply_me():
    """ Tests the   multiply_me   method of the Blob class. """

    print()
    print('-----------------------------------------------------------')
    print('Testing the  multiply_me  method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 50)
    blob_1.multiply_me()
    run_test_instance_variables(blob_1, "red", 500)

    # Test 2
    print('\nTest 2:')
    blob_2 = Blob("blue", 88)
    blob_2.multiply_me()
    blob_2.multiply_me()
    run_test_instance_variables(blob_2, "blue", 8800)

    # Test 3
    print('\nTest 3:')
    blob_1.color = "green"
    blob_1.multiply_me()
    run_test_instance_variables(blob_1, "green", 5000)

    # Test 4
    print('\nTest 3:')
    blob_1.color = "black"
    blob_1.size = 7
    blob_1.multiply_me()
    run_test_instance_variables(blob_1, "black", 70)


def run_test_make_child():
    """ Tests the   make_child   method of the Blob class. """

    print()
    print('-----------------------------------------------------------')
    print('Testing the   make_child   method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 50)
    blob_2 = Blob("blue", 88)
    blob_3 = blob_1.make_child(blob_2)

    print()
    print("For the RETURNED Blob:")
    run_test_instance_variables(blob_3, "red", 88)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "red", 50)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "blue", 88)

    # Test 2
    print('\nTest 2:')
    blob_1 = Blob("white", 500)
    blob_2 = Blob("black", 888)
    blob_3 = blob_2.make_child(blob_1)

    print()
    print("For the RETURNED Blob:")
    run_test_instance_variables(blob_3, "black", 500)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "white", 500)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "black", 888)

    # Test 3
    print('\nTest 3:')
    blob_1.color = "green"
    blob_2.size = 777
    blob_3 = blob_1.make_child(blob_2)

    print()
    print("For the RETURNED Blob:")
    run_test_instance_variables(blob_3, "green", 777)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "green", 500)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "black", 777)


def run_test_get_point():
    """ Tests the   get_point   method of the Blob class. """

    print()
    print('-----------------------------------------------------------')
    print('Testing the   get_point   method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 50)
    blob_2 = Blob("blue", 88)
    p1 = blob_1.get_point(blob_2)

    print()
    print("For the RETURNED Point:")
    print('Expected: ', Point(50, 88))
    print('Actual:   ', p1)
    print_result_of_test(Point(50, 88), p1)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "red", 50)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "blue", 88)

    # Test 2
    print('\nTest 2:')
    blob_1 = Blob("white", 33)
    blob_2 = Blob("black", 44)
    p1 = blob_1.get_point(blob_2)

    print()
    print("For the RETURNED Point:")
    print('Expected: ', Point(33, 44))
    print('Actual:   ', p1)
    print_result_of_test(Point(33, 44), p1)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "white", 33)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "black", 44)

    # Test 3
    blob_1.color = "green"
    blob_2.size = 99
    p2 = blob_2.get_point(blob_1)

    print()
    print("For the RETURNED Point:")
    print('Expected: ', Point(99, 33))
    print('Actual:   ', p1)
    print_result_of_test(Point(99, 33), p2)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "green", 33)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "black", 99)


def run_test_get_distance():
    """ Tests the    get_distance    method of the Blob class. """
    print()
    print('-----------------------------------------------------------')
    print('Testing the   get_distance   method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 10)
    blob_2 = Blob("blue", 20)
    p1 = Point(10, 20)
    p2 = Point(20, 10)
    distance = blob_1.get_distance(blob_2)

    print()
    print("For the RETURNED distance:")
    print('Expected: ', p1.distance_from(p2))
    print('Actual:   ', distance)
    print_result_of_test(p1.distance_from(p2), distance)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "red", 10)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "blue", 20)

    # Test 2
    print('\nTest 2:')
    blob_1 = Blob("cyan", 123)
    blob_2 = Blob("green", 456)
    p1 = Point(123, 456)
    p2 = Point(456, 123)
    distance = blob_1.get_distance(blob_2)

    print()
    print("For the RETURNED distance:")
    print('Expected: ', p1.distance_from(p2))
    print('Actual:   ', distance)
    print_result_of_test(p1.distance_from(p2), distance)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "cyan", 123)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "green", 456)

    # Test 3
    print('\nTest 3:')
    distance = blob_2.get_distance(blob_1)

    print()
    print("For the RETURNED distance:")
    print('Expected: ', p1.distance_from(p2))
    print('Actual:   ', distance)
    print_result_of_test(p1.distance_from(p2), distance)

    print()
    print("The Blob acted upon should not have changed:")
    run_test_instance_variables(blob_1, "cyan", 123)

    print()
    print("The Blob argument should not have changed:")
    run_test_instance_variables(blob_2, "green", 456)


def run_test_swap_colors():
    """ Tests the   swap_colors   method of the Blob class. """

    print()
    print('-----------------------------------------------------------')
    print('Testing the  swap_colors  method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 55)
    blob_2 = Blob("blue", 66)
    blob_1.swap_colors(blob_2)

    print()
    print("Blob 1 should now be:")
    run_test_instance_variables(blob_1, "blue", 55)

    print()
    print("Blob 2 should now be:")
    run_test_instance_variables(blob_2, "red", 66)

    # Test 2
    print('\nTest 2:')
    blob_1.swap_colors(blob_2)

    print()
    print("Blob 1 should now be:")
    run_test_instance_variables(blob_1, "red", 55)

    print()
    print("Blob 2 should now be:")
    run_test_instance_variables(blob_2, "blue", 66)

    # Test 3
    print('\nTest 3:')
    blob_1.color = "green"
    blob_2.swap_colors(blob_1)

    print()
    print("Blob 1 should now be:")
    run_test_instance_variables(blob_1, "blue", 55)

    print()
    print("Blob 2 should now be:")
    run_test_instance_variables(blob_2, "green", 66)

    # Test 4
    print('\nTest 2:')
    blob_2.size = 999
    blob_1.swap_colors(blob_2)

    print()
    print("Blob 1 should now be:")
    run_test_instance_variables(blob_1, "green", 55)

    print()
    print("Blob 2 should now be:")
    run_test_instance_variables(blob_2, "blue", 999)

    # Test 5
    print('\nTest 2:')
    blob_1 = Blob("black", 101)
    blob_1.swap_colors(blob_1)

    print("Blob 1 should now be:")
    run_test_instance_variables(blob_1, "black", 101)


def run_test_get_recent_color():
    """ Tests the get_recent_color method. """
    print()
    print('-----------------------------------------------------------')
    print('Testing the   get_recent_color   method of the Blob class.')
    print('-----------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 10)
    blob_2 = Blob("blue", 20)

    print()
    print("Testing blob_1 before doing make_child on it:")
    print('Expected for returned value: ', None)
    print('Actual:                      ', blob_1.get_recent_color())
    print_result_of_test(None, blob_1.get_recent_color())

    blob_unused = blob_1.make_child(blob_2)

    print()
    print("Testing blob_1 after doing make_child on it:")
    print('Expected for returned value: ', "red")
    print('Actual:                      ', blob_1.get_recent_color())
    print_result_of_test("red", blob_1.get_recent_color())

    print()
    print("Testing that blob_2 did not get affected:")
    print('Expected for returned value: ', None)
    print('Actual:                      ', blob_2.get_recent_color())
    print_result_of_test(None, blob_2.get_recent_color())

    # Test 2
    print('\nTest 2:')
    blob_1 = Blob("red", 10)
    blob_2 = Blob("blue", 20)
    blob_unused = blob_1.make_child(blob_2)
    blob_1.color = "green"
    blob_unused = blob_1.make_child(blob_2)
    blob_2.color = "black"
    blob_unused = blob_2.make_child(blob_1)
    blob_1.color = "white"

    print()
    print("Testing blob_2 after a make_child:")
    print('Expected for returned value: ', "black")
    print('Actual:                      ', blob_2.get_recent_color())
    print_result_of_test("black", blob_2.get_recent_color())


def run_test_get_bigger_size_color():
    """ Tests the    get_bigger_size_color    method of the Blob class. """
    print()
    print('---------------------------------------------------------------')
    print('Testing the   get_bigger_size_color   method of the Blob class.')
    print('---------------------------------------------------------------')

    # Test 1
    print('\nTest 1:')
    blob_1 = Blob("red", 100)
    blobs = [Blob("green", 99),
             Blob("white", 20),
             Blob("yellow", 101),
             Blob("black", 30),
             Blob("blue", 40)]

    color = blob_1.get_bigger_size_color(blobs)
    print("For the RETURNED size:")
    print('Expected: ', "yellow")
    print('Actual:   ', color)
    print_result_of_test("yellow", color)

    # Test 2
    print('\nTest 2:')
    blob_1 = Blob("red", 200)
    blobs = [Blob("green", 202),
             Blob("white", 20),
             Blob("yellow", 201),
             Blob("black", 30),
             Blob("blue", 40)]

    color = blob_1.get_bigger_size_color(blobs)
    print("For the RETURNED size:")
    print('Expected: ', "green")
    print('Actual:   ', color)
    print_result_of_test("green", color)

    # Test 3
    print('\nTest 3:')
    blob_1 = Blob("red", 102)
    blobs = [Blob("green", 99),
             Blob("white", 20),
             Blob("yellow", 101),
             Blob("black", 30),
             Blob("blue", 40)]

    color = blob_1.get_bigger_size_color(blobs)
    print("For the RETURNED size:")
    print('Expected: ', "no color")
    print('Actual:   ', color)
    print_result_of_test("no color", color)


###############################################################################
# The following are HELPER functions that display error messages in RED
# and help make it easier for us to write tests.
# Do NOT change any of the following.
###############################################################################
def run_test_instance_variables(blob, expected_color, expected_size):
    """
    Tests whether the instance variables for the given Blob
    are per the given expected values.
      -- Prints relevant messages.
      -- Returns True if all is OK, else returns False.
    """
    try:
        return (run_test_type_of_object(blob) and
                run_test_types_of_instance_variables(blob) and
                run_test_values_of_instance_variables(
                    blob,
                    expected_color,
                    expected_size))
    except Exception:
        something_unexpected_happened_in_our_testing_code()
        return False


def run_test_values_of_instance_variables(blob, expected_color, expected_size):
    # Print the EXPECTED and ACTUAL values of the instance variables
    format_string = '  {:10} {:>9} {:>6}'
    print('  Testing instance variables:')
    print('                 color   size')
    print('                 -----   ----')
    print(format_string.format('Expected:', str(expected_color),
                               str(expected_size)))
    print(format_string.format('Actual:', blob.color, blob.size))

    # Print a message indicating whether or not
    # the EXPECTED values are equal to the ACTUAL values.
    expected = (expected_color, expected_size)
    actual = (blob.color, blob.size)
    return print_result_of_test(expected, actual)


def something_unexpected_happened_in_our_testing_code():
    print_failure_message()
    explanation = (
            '  Something unexpected has happened in the testing \n' +
            '  code that we supplied.  You should probably\n' +
            '  SEEK HELP FROM YOUR INSTRUCTOR NOW.')
    print_failure_message(explanation)


def run_test_type_of_object(blob):
    """ Returns True if the argument is in fact a Blob object """
    if isinstance(blob, Blob):
        return True
    else:
        explanation = ('  The following object to test:\n' +
                       '     ' + str(blob) + '\n' +
                       '  should be a Blob object,\n' +
                       '  but it is not.  Perhaps your code\n' +
                       '  returned something of the wrong type?')
        print_failure_message()
        print_failure_message(explanation)
        return False


def run_test_types_of_instance_variables(blob):
    """
    Returns True if the argument has the right instance variables.
    # and they are all numbers.
    """
    # If NONE of the expected instance variables exist,
    # then perhaps the only "problem" is that the  __init__  method
    # has not yet been implemented.
    attributes = dir(blob)
    if ('color' not in attributes
            and 'size' not in attributes):
        explanation = (
                '  This object:\n' +
                '     ' + str(blob) + '\n' +
                '  should have these instance variables:\n' +
                '     self.color\n' +
                '     self.size\n' +
                '  but it has NONE of them.\n' +
                '  Perhaps you simply have not yet\n' +
                '  implemented the   __init__   method?\n' +
                '  (If so, implement it now.)')
        print_failure_message()
        print_failure_message(explanation)
        return False

    # If SOME (but not all) of the expected instance variables exist,
    # then perhaps something was misspelled in __init__.
    if not ('color' in attributes
            and 'size' in attributes):
        explanation = (
                '  This object:\n' +
                '     ' + str(blob) + '\n' +
                '  should have these instance variables:\n' +
                '     self.color\n' +
                '     self.size\n' +
                '  but it is missing some of them.\n' +
                '  Perhaps you misspelled something\n' +
                '  in your   __init__   code?')
        print_failure_message()
        print_failure_message(explanation)
        return False

    # Check that the instance variables are of the right types:
    #     if not isinstance(cloud.capacity, str):
    #         explanation = (
    #             '  This object:\n' +
    #             '     ' + str(cloud) + '\n' +
    #             '  has an instance variable  capacity  with this value:\n' +
    #             '     capacity: ' + str(cloud.capacity) +
    #             '  That value should be a STRING, but is isn\'t.\n')
    #         print_failure_message()
    #         print_failure_message(explanation)
    #         return False
    #
    #     if not isinstance(cloud.water, list):
    #         explanation = (
    #             '  This object:\n' +
    #             '     ' + str(cloud) + '\n' +
    #             '  has an instance variable  water  with this value:\n' +
    #             '     water: ' + str(cloud.water) +
    #             '  That value should be a LIST, but is isn\'t.\n')
    #         print_failure_message()
    #         print_failure_message(explanation)
    #         return False
    #
    #     if not is_list_of_strings(cloud.water):
    #         explanation = (
    #             '  This object:\n' +
    #             '     ' + str(cloud) + '\n' +
    #             '  has an instance variable  water  with this value:\n' +
    #             '     water: ' + str(cloud.water) +
    #             '  That value should be a list of STRINGS, but is isn\'t.\n')
    #         print_failure_message()
    #         print_failure_message(explanation)
    #         return False

    return True


def is_list_of_strings(strings):
    return ((strings == [])
            or (isinstance(strings[0], str)
                and is_list_of_strings(strings[1:])))


def print_result_of_test(expected, actual):
    if are_equal(expected, actual):
        print("  PASSED the above test -- good!", color="blue")
        return True

    print_failure_message()

    if isinstance(expected, list) or isinstance(expected, tuple):
        explanation = (
                '  For at least one of the above, its Expected value\n' +
                '  does not equal its Actual value.')
        #          Note: the printed\n' +
        #             '  values are the actual values ROUNDED to 1 decimal place.')
        print_failure_message(explanation)

    return False


def are_equal(a, b):
    # We will treat two numbers as being "equal" if they are
    # the same when each is rounded to 12 decimal places.
    if isinstance(a, Number) and isinstance(b, Number):
        return (round(a, 12) == round(b, 12))

    # For lists and tuples, their items have to be equal for the
    # lists/tuples to be equal.
    if isinstance(a, list) and isinstance(b, list):
        if len(a) != len(b):
            return False
        for k in range(len(a)):
            if not are_equal(a[k], b[k]):
                return False
        return True  # All the items were equal.

    if isinstance(a, tuple) and isinstance(b, tuple):
        if len(a) != len(b):
            return False
        for k in range(len(a)):
            if not are_equal(a[k], b[k]):
                return False
        return True  # All the items were equal.

    # For all else, they must be equal in the "usual" way:
    return a == b


def print_failure_message(message='  *** FAILED the above test. ***',
                          flush_time=0.5):
    """ Prints a message onto stderr, hence in RED. """
    time.sleep(flush_time)
    print(message, flush=True, color="red")
    time.sleep(flush_time)


# To allow color-coding the output to the console:
USE_COLORING = True  # Change to False to revert to OLD style coloring

testing_helper.USE_COLORING = USE_COLORING
if USE_COLORING:
    # noinspection PyShadowingBuiltins
    print = testing_helper.print_colored
else:
    # noinspection PyShadowingBuiltins
    print = testing_helper.print_uncolored

# -----------------------------------------------------------------------------
# Calls  main  to start the ball rolling.
# The   try .. except   prevents error messages on the console from being
# intermingled with ordinary output to the console.
# -----------------------------------------------------------------------------
try:
    main()
except Exception:
    print('ERROR - While running this test,', color='red')
    print('your code raised the following exception:', color='red')
    print()
    time.sleep(1)
    raise