Problem Set 11: Inheritance and Polymorphism; Artificial Intelligience for Games

Preliminaries

In your work on this assignment, make sure to abide by the collaboration policies of the course.

For each problem in this problem set, we will be writing or evaluating some Python code. You are encouraged to use the Spyder IDE which will be discussed/presented in class, but you are welcome to use another IDE if you choose.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.

Programming Guidelines

  • Refer to the class Coding Standards for important style guidelines. The grader will be awarding/deducting points for writing code that comforms to these standards.

  • Every program file must begin with a descriptive header comment that includes your name, username/BU email, and a brief description of the work contained in the file.

  • Every function must include a descriptive docstring that explains what the function does and identifies/defines each of the parameters to the function.

  • Your functions must have the exact names specified below, or we won’t be able to test them. Note in particular that the case of the letters matters (all of them should be lowercase), and that some of the names include an underscore character (_).

  • Make sure that your functions return the specified value, rather than printing it. None of these functions should use a print statement.

  • If a function takes more than one input, you must keep the inputs in the order that we have specified.

  • You should not use any Python features that we have not discussed in class or read about in the textbook.

  • Your functions do not need to handle bad inputs – inputs with a type or value that doesn’t correspond to the description of the inputs provided in the problem.

  • You must test your work before you submit it You can prove to yourself whether it works correctly – or not – and make corrections before submission. If you need help testing your code, please ask the course staff!

  • Do not submit work with syntax errors. Syntax errors will cause the Gradescope autograder to fail, resulting in a grade of 0.

Warnings: Individual Work and Academic Conduct!!

  • This is an individual assignment. You may discuss the problem statement/requirements, Python syntax, test cases, and error messages with your classmates. However, each student must write their own code without copying or referring to other student’s work.

  • It is strictly forbidden to use any code that you find from online websites including but not limited to as CourseHero, Chegg, or any other sites that publish homework solutions.

  • It is strictly forbidden to use any generative AI (e.g., ChatGPT or any similar tools**) to write solutions for for any assignment.

Students who submit work that is not authentically their own individual work will earn a grade of 0 on this assignment and a reprimand from the office of the Dean.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.




Problem 1: A Holiday class

10 points; individual-only

In this problem, you will extend your Date class from assignment 10 to create a Holiday class. The Holiday is a Date, but it also has a special data attribute to encapsulate its name. In addition, it has a special string representation.

Here is an example using an object of the Holiday class:

    if __name__ == '__main__':

        today = Date(6, 21, 2021)
        print('today =', today)

        m = Holiday(10, 11, 2021, "Marathon Monday")
        print('m =', m)

        # methods `is_before` and `days_between` are inherited from class Date:
        print('m.is_before(today) =', m.is_before(today)) 
        print('m.days_between(today) =', m.days_between(today))

The above prints out:

        today = 03/01/2021
        m = Marathon Monday (10/11/2021)
        m.is_before(today) = False
        m.days_between(today) = 112

Getting started

Begin by creating a new file ps11pr1.py in Spyder, and saving it in the same directory as your ps9pr1.py file. In this new file, you should begin by importing your Date class:

    from ps9pr1 import Date

Inheriting from class Date

  1. Create a new class Holiday that inhereits from class Date.

  2. Create a specialized version of the __init__ method, which will take parameters for the month, day, year, and name. The constructor must call the superclass (Date) constructor to initialize its data memebrs, and then must initialize the specialized parameter name. You will know this works when you can create an object of type Holiday, for example:

    if __name__ == '__main__':
    
        m = Holiday(4, 17, 2023, "Marathon Monday")
        print('m =', m)
    
  3. Override the __repr__(self) method, which returns a string representation of a Holiday object. This method will be called when an object of type Holiday is printed. This method must return a beautifully-formatted string that shows the holiday’s name, as well as its 'mm/dd/yyyy' representation. For example:

        Marathon Monday (4/17/2023)
    

    The implementation of the Holiday‘s __repr__(self) method must call the super-class method’s __repr__ method, and use the result as part of the Holiday string representation.


Problem 2: Holiday clients

20 points; individual-only

Now that you have written a functional Holiday class, we will put it to use! Remember that the Holiday class is only a blueprint, or template, for how Holiday objects should behave. We can now create Holiday objects according to that template and use them in client code.

Getting started

To start, open a new file in Spyder and save it as ps11pr2.py. Put all of the code that you write for this problem in this file. Don’t forget to include appropriate comments at the top of the file, and a docstring for your functions.

IMPORTANT: Since your clients will need to construct Holiday objects, you need to import the Holiday class.

Therefore, make sure that ps11pr2.py is in the same directory as ps11pr1.py, and include the following statement at the top of ps11pr2.py:

    from ps11pr1 import Holiday

Your tasks

  1. We will use our Holiday class and it’s superclass Date class methods to find the dates of holidays for any year. The following bank holidays (when banks and the stock market are closed) are observed in the United States:

    • New Year’s Day (January 1st, but observed on January 2nd if January 1st is a Sunday)
    • Martin Luther King Day (the third Monday in January)
    • President’s Day (the third Monday in February)
    • Memorial Day (the last Monday in May)
    • Juneteenth (the 19th of June, observed on June 20th if the 19th is a Sunday)
    • Independence Day (July 4th, observed on July 5th if July 4th is a Sunday)
    • Labor Day (the first Monday in September)
    • Indigenous People’s Day (the second Monday in October)
    • Thanksgiving Day (the fourth Thursday in November)
    • Christmas Day (the 25th of December, observed on December 26th if the 25th is a Sunday)
    • (The markets are also closed on Good Friday, but its date depends on the Lunar calendar and not the Gregorian Calendar, so we will skip it for now.)

    Write a function get_holidays(year) that will returns a list of the Holidayss for any given year. The function will return a list of holidays, which we can print out in our client (test) code.

    Example:

    if __name__ == '__main__':
    
        ## TEST CODE:
        holidays = get_holidays(2019)
        print() # blank line
        for h in holidays:
            print(h, "is observed on a", h.day_name())
    

    Sample output:

    New Year's Day (01/01/2019) is on a Tuesday
    Martin Luther King Day (01/21/2019) is on a Monday
    Presidents' Day (02/18/2019) is on a Monday
    Memorial Day (05/27/2019) is on a Monday
    Juneteenth (06/19/2019) is a Wednesday
    Independence Day (07/04/2019) is on a Thursday
    Labor Day (09/02/2019) is on a Monday
    Indigenous People's Day (10/14/2019) is on a Monday
    Thanksgiving Day (11/28/2019) is on a Thursday
    Christmas Day (12/25/2019) is on a Wednedsay
    

    Note the special case when a fixed-date legal holiday (e.g., New Year’s Day, Independence Day, or Christmas) falls on a Sunday, and thus is observed on the following Monday:

    Example 2:

    if __name__ == '__main__':
    
        ## TEST CODE:
        holidays = get_holidays(2023)
        print() # blank line
        for h in holidays:
            print(h, "is observed on a", h.day_name())
    

    Sample output:

    New Year's Day (01/01/2023) is on a Sunday
    New Year's Day (observed) (01/02/2023) is on a Monday
    Martin Luther King Day (01/16/2023) is on a Monday
    Presidents' Day (02/20/2023) is on a Monday
    Memorial Day (05/29/2023) is on a Monday
    Juneteenth (06/19/2023) is a Monday
    Independence Day (07/04/2023) is on a Tuesday
    Labor Day (09/04/2023) is on a Monday
    Indigenous People's Day (10/09/2023) is on a Monday
    Thanksgiving Day (11/23/2023) is on a Thursday
    Christmas Day (12/25/2023) is on a Monday
    

    Notes:

    • In your function, use multiple Holiday objects to represent each of the holidays, and use the Holiday methods to find the correct day of week as needed.

    • Some holidays must occur on a specific weekday (for example, MLK day is always the third Monday in January; it could be as early as January 15th, or as late as January 21st), a good strategy is to pick the earliest possible date, and then use a loop to increment the day until you get the required weekday. Use the keyword break to stop a loop (without stopping the function as a whole).

    • The sample output above includes the print out generated by the test code. However, the completed function will only return a list of Holiday objects.

  2. Write a function closest_holiday(date), which will find the Holiday that is closest to that parameter date, which is a Date object. Here are some examples:

    if __name__ == '__main__':
    
        date = Date(3, 24, 2021)
        h = closest_holiday(date)
        print(h, 'is the closest holiday to', date)
    
        date = Date(8, 26, 2021)
        h = closest_holiday(date)
        print(h, 'is the closest holiday to', date)
    
        date = Date(9, 26, 2021)
        h = closest_holiday(date)
        print(h, 'is the closest holiday to', date)
    

    These would produce the following outputs:

        President's Day (02/15/2021) is the closest holiday to 03/24/2021
        Labor Day (09/06/2021) is the closest holiday to 08/26/2021
        Indigenous People's Day (10/11/2021) is the closest holiday to 09/26/2021
    

    Notes:

    • This problem is similar to some of the problems that you solved in assignment 9, and can be solved by using a list comprehension and list of lists technique.

    • Call the function get_holidays from above to obtain a list of possible holidays to consider, and then use the list comprehension to find the one closest to your date.

    • Be careful! The closest Holiday might be in a different year! For example, the closest holiday to December 28th is New Years’ Day:

      date = Date(12, 29, 2020)
      h = closest_holiday(date)
      print(h, 'is the closest holiday to', date)
      

      should produce the following result:

       New Year's Day (01/01/2021) is the closest holiday to 12/29/2020
      

Problem 3: An Unintelligent Computerized Player

20 points; individual-only

Overview
This problem builds upon the work you did in Problem Set 10. You must complete Problem Set 10 before you can begin to work on this part of Problem Set 11. In this problem you will define an unintelligent computer player – one that uses random number generator to choose its next move.

Getting started
Begin by downloading the file ps11pr3.py and opening it in Spyder. Make sure that you put the file in the same folder as your other files for this problem set.

We have included an import statement at the top of ps11pr3.py that should allow you to use the other code that you have written for this assignment — in particular, the Board and Player classes.

Your tasks

Define a class called RandomPlayer that can be used for an unintelligent computer player that chooses at random from the available columns.

This class should be a subclass of the Player class that you implemented in Problem Set 10, Problem 2, and you should take full advantage of inheritance. In particular, you should not need to include any attributes in your RandomPlayer class, because all of the necessary attributes (the player’s checker, and its count of the number of moves) will be inherited from Player.

Similarly, you should not need to redefine the __repr__ or opponent_checker methods, because they will be inherited from Player, and we don’t want these methods to behave any differently for a RandomPlayer than they do for a Player.

However, you will need to do the following:

  1. Make sure that your class header specifies that RandomPlayer inherits from Player.

  2. Because all of the attributes of a RandomPlayer are inherited from Player, you will not need to define a constructor for this class. Rather, we can just use the constructor that is inherited from Player.

  3. Write a method next_move(self, board) that overrides (i.e., replaces) the next_move method that is inherited from Player. Rather than asking the user for the next move, this version of next_move should choose at random from the columns in the specified board that are not yet full, and return the index of that randomly selected column. You may assume that this method will only be called in cases in which there is at least one available column.

    In addition, make sure that you increment the number of moves that the RandomPlayer object has made.

Notes:

Testing your RandomPlayer class
You can test your new class from the Shell. For example:

>>> p = RandomPlayer('X')
>>> p
Player X      # uses the inherited __repr__
>>> p.opponent_checker()
'O'           # uses the inherited version of this method
>>> b = Board(2, 4)
>>> b.add_checkers('001223')
>>> b
|O| |X| |
|X|X|O|O|
---------
 0 1 2 3

>>> p.next_move(b)
3             # can be either 1 or 3
>>> p.next_move(b)
1             # can be either 1 or 3
>>> p.next_move(b)
1             # can be either 1 or 3
>>> b.add_checker('O', 1)
>>> b
|O|O|X| |
|X|X|O|O|
---------
 0 1 2 3

>>> p.next_move(b)
3             # must be 3!
>>> p.next_move(b)
3             # must be 3!
>>> b.add_checker('X', 3)
>>> b.remove_checker(2)
>>> b
|O|O| |X|
|X|X|O|O|
---------
 0 1 2 3

>>> p.next_move(b)
2             # must be 2!
>>> p.next_move(b)
2             # must be 2!

You should also test it from within the context of the connect_four function that we have given you. To play against a random player, enter something like this:

>>> connect_four(Player('X'), RandomPlayer('O'))

You’ll see that it’s pretty easy to win against someone who chooses randomly!

You could also pit two random players against each other and see who wins:

>>> connect_four(RandomPlayer('X'), RandomPlayer('O'))

Problem 4: An AI Player

35 points; individual-only

Overview
In this problem you will define a more “intelligent” computer player – one that uses techniques from artificial intelligence (AI) to choose its next move.

In particular, this “AI player” will look ahead some number of moves into the future to assess the impact of each possible move that it could make for its next move, and it will assign a score to each possible move. And since each move corresponds to a column number, it will effectively assign a score to each column.

The possible column scores are:

After obtaining a list of scores for each column, it will choose as its next move the column with the maximum score. This will be the player’s judgment of its best possible move.

If there are ties, the player will use one of the following tiebreaking strategies, each of which is represented by a single-word string:

When looking ahead, the player will assume that its opponent is using a comparable strategy – assigning scores to columns based on some degree of lookahead, and choosing what it judges to be the best possible move for itself.

Getting started
Begin by downloading the file ps11pr4.py and opening it in Spyder. Make sure that you put the file in the same folder as your other files for this problem set.

We have included an import statement at the top of ps11pr4.py that should allow you to use the other code that you have written for this assignment — in particular, the Board and Player classes.

Your tasks
You will define a class called AIPlayer that takes the approach outlined above (and in more detail below) to choose its next move.

Like the RandomPlayer class that you implemented for [Problem 1][pr1], this class should be a subclass of the Player class that you implemented in Problem Set 10, Problem 2, and you should take full advantage of inheritance.

In addition to the attributes inherited from Player, an AIPlayer object should include two new attributes:

AIPlayer will inherit the methods of the Player class, but it will override some of them so that it can change their behavior. In addition, AIPlayer will include two new methods that were not needed in Player.

Here are the steps you should take:

  1. Make sure that your class header specifies that AIPlayer inherits from Player.

  2. Write a constructor __init__(self, checker, tiebreak, lookahead) that constructs a new AIPlayer object. Begin the method with assert statements that validate the inputs:

    def __init__(self, checker, tiebreak, lookahead):
        """ put your docstring here
        """
        assert(checker == 'X' or checker == 'O')
        assert(tiebreak == 'LEFT' or tiebreak == 'RIGHT' or tiebreak == 'RANDOM')
        assert(lookahead >= 0)
    

    Next, call the Player constructor so that it can initialize the attributes that are inherited from that class:

        Player.__init__(self, checker)
    

    Finally, your new constructor should initialize the two attributes that are not inherited from Player (see above) – assigning them the values that are passed in as parameters.

    Make sure that you do not redefine the inherited attributes by trying to assign something to them here.

  3. Write a method __repr__(self) that returns a string representing an AIPlayer object. This method will override/replace the __repr__ method that is inherited from Player. In addition to indicating which checker the AIPlayer object is using, the returned string should also indicate the player’s tiebreaking strategy and lookahead. For example:

    >>> p1 = AIPlayer('X', 'LEFT', 1)
    >>> p1
    Player X (LEFT, 1)
    >>> p2 = AIPlayer('O', 'RANDOM', 2)
    >>> p2
    Player O (RANDOM, 2)
    

    The results of your __repr__ method should exactly match the results shown above. Remember that your __repr__ method should return a string. It should not do any printing.

  4. Write a method max_score_column(self, scores) that takes a list scores containing a score for each column of the board, and that returns the index of the column with the maximum score. If one or more columns are tied for the maximum score, the method should apply the called AIPlayer‘s tiebreaking strategy to break the tie. Make sure that you return the index of the appropriate column, and not the column’s score.

    Notes:

    • One good way to implement this method is to first determine the maximum score in scores (you can use the built-in max function for this), and to then create a list containing the indices of all elements in scores that match this maximum score. For example, if scores consisted of the list [50,50,50,50,50,50,50], the list of indices that you would build would be [0,1,2,3,4,5,6], because all of these scores are tied for the maximum score. If scores consisted of the list [50,100,100,50,50,100,50], you would build the list of indices [1,2,5]. Then once you have this list of indices, you can choose from the list based on the AIPlayer‘s tiebreaking strategy.

    • If you take this approach, then you don’t really need to worry about whether there is a tie. You can always use the tiebreaking strategy when choosing from the list of indices that you construct!

    • We have included an import statement for the random module so that you can use the appropriate function to make a random choice for players that use the 'RANDOM' tiebreaking strategy.

    Examples:

    >>> scores = [0, 0, 50, 0, 50, 50, 0]
    >>> p1 = AIPlayer('X', 'LEFT', 1)
    >>> p1.max_score_column(scores)
    2
    >>> p2 = AIPlayer('X', 'RIGHT', 1)
    >>> p2.max_score_column(scores)
    5
    
  5. Write a method scores_for(self, board) that takes a Board object board and determines the called AIPlayer‘s scores for the columns in board. Each column should be assigned one of the four possible scores discussed in the [Overview][pr4] at the start of this problem, based on the called AIPlayer‘s lookahead value. The method should return a list containing one score for each column.

    This method should take advantage of both the other methods in the called AIPlayers object (including the inherited ones) and the methods in the Board object that it is given as a parameter. Don’t repeat work that can be done using one of those methods!

    You should begin by creating a list (call it scores) that is long enough to store a score for each column. You can use list multiplication for this, and it doesn’t really matter what initial value you use for the elements of the list.

    You should then loop over all of the columns in board, determine a score for each column, and assign the score to the appropriate element of scores. Here is an outline of the logic:

    1. If the current column is full, use a score of -1 for it. In other words, assign -1 to the appropriate element of your scores list.
    2. Otherwise, if board is already a win for the called AIPlayer (i.e., for self), use a score of 100 for the current column.
    3. Otherwise, if board is already a win for the player’s opponent, use a score of 0 for the current column.
    4. Otherwise, if the player has a lookahead of 0, use a score of 50 for the column. (Remember, a lookahead of 0 means that the player is only assessing the current board, and since we already checked for wins or losses, the current board must be neither a win nor a loss.)
    5. Otherwise, we need to look ahead! This involves taking several steps:

      1. Add one of the called AIPlayer‘s checkers to the current column using the appropriate Board method.

      2. Determine what scores the opponent would give to the resulting board. To do so, create an opponent (an AIPlayer object) with the same tiebreaking strategy as self, but with a lookahead that is one less than the one used by self. Make a recursive call to determine the scores that this created opponent would give to the current board (the one that resulted from adding a checker to the current column).

      3. Following the approach discussed in lecture, use the opponent’s scores (the scores returned by the recursive call) to determine what score self should use for the current column.

      4. Remove the checker that was placed in this column so that you can restore board to its prior state.

    Once the loop has considered all of the columns, the method should return the complete list of scores.

    Examples:

    >>> b = Board(6, 7)
    >>> b.add_checkers('1211244445')
    >>> b
    | | | | | | | |
    | | | | | | | |
    | | | | |X| | |
    | |O| | |O| | |
    | |X|X| |X| | |
    | |X|O| |O|O| |
    ---------------
     0 1 2 3 4 5 6
    
    # A lookahead of 0 doesn't see threats!
    >>> AIPlayer('X', 'LEFT', 0).scores_for(b)
    [50, 50, 50, 50, 50, 50, 50]
    # A lookahead of 1 sees immediate wins.
    # (O would win if it put a checker in column 3.)
    >>> AIPlayer('O', 'LEFT', 1).scores_for(b)
    [50, 50, 50, 100, 50, 50, 50]
    # But a lookahead of 1 doesn't see possible losses!
    # (X doesn't see that O can win if column 3 is left open.)
    >>> AIPlayer('X', 'LEFT', 1).scores_for(b)
    [50, 50, 50, 50, 50, 50, 50]
    # A lookahead of 2 sees possible losses.
    # (All moves by X other than column 3 leave it open to a loss.
    # note that X's score for 3 is 50 instead of 100, because it
    # assumes that O will follow X's move to 3 with its own move to 3,
    # which will block X's possible horizontal win.)
    >>> AIPlayer('X', 'LEFT', 2).scores_for(b)
    [0, 0, 0, 50, 0, 0, 0]
    # A lookahead of 3 sees set-up wins!
    # (If X chooses column 3, O will block its horizontal win, but
    # then X can get a diagonal win by choosing column 3 again!)
    >>> AIPlayer('X', 'LEFT', 3).scores_for(b)
    [0, 0, 0, 100, 0, 0, 0]
    # With a lookahead of 3, O doesn't see the danger of not
    # choosing 3 for its next move (hence the 50s in columns
    # other than column 3).
    >>> AIPlayer('O', 'LEFT', 3).scores_for(b)
    [50, 50, 50, 100, 50, 50, 50]
    # With a lookahead of 4, O **does** see the danger of not
    # choosing 3 for its next move (hence the 0s in columns
    # other than column 3).
    >>> AIPlayer('O', 'LEFT', 4).scores_for(b)
    [0, 0, 0, 100, 0, 0, 0]
    
  6. Write a method next_move(self, board) that overrides (i.e., replaces) the next_move method that is inherited from Player. Rather than asking the user for the next move, this version of next_move should return the called AIPlayer‘s judgment of its best possible move. This method won’t need to do much work, because it should use your scores_for and max_score_column methods to determine the column number that should be returned.

    In addition, make sure that you increment the number of moves that the AIPlayer object has made.

    Examples:

    >>> b = Board(6, 7)
    >>> b.add_checkers('1211244445')
    >>> b
    | | | | | | | |
    | | | | | | | |
    | | | | |X| | |
    | |O| | |O| | |
    | |X|X| |X| | |
    | |X|O| |O|O| |
    ---------------
     0 1 2 3 4 5 6
    
    # With a lookahead of 1, gives all columns a score of 50, and its
    # tiebreaking strategy leads it to pick the leftmost one.
    >>> AIPlayer('X', 'LEFT', 1).next_move(b)
    0      
    # Same lookahead means all columns are still tied, but a different
    # tiebreaking stategy that leads it to pick the rightmost column.
    >>> AIPlayer('X', 'RIGHT', 1).next_move(b)
    6
    # With the larger lookahead, X knows it must pick column 3!
    >>> AIPlayer('X', 'LEFT', 2).next_move(b)
    3      
    # The tiebreaking strategy doesn't matter if there's only one best move!
    >>> AIPlayer('X', 'RIGHT', 2).next_move(b)
    3      
    >>> AIPlayer('X', 'RANDOM', 2).next_move(b)
    3
    

Playing the game with AIPlayer objects!
Because our AIPlayer class inherits from Player, we can use it in conjunction with our connect_four function from [Problem Set 13, Problem 2][ps13pr2].

You can play against an AIPlayer by doing something like:

>>> connect_four(Player('X'), AIPlayer('O', 'RANDOM', 3))

Below some examples in which two AIPlayer objects play against each other. And because we’re using non-random tiebreaking strategies for both players, you should obtain the same results.

>>> connect_four(AIPlayer('X', 'LEFT', 0), AIPlayer('O', 'LEFT', 0))
# omitting everything but the final result...

Player X (LEFT, 0) wins in 10 moves.
Congratulations!
|O|O|O| | | | |
|X|X|X| | | | |
|O|O|O| | | | |
|X|X|X| | | | |
|O|O|O| | | | |
|X|X|X|X| | | |
---------------
 0 1 2 3 4 5 6

>>> connect_four(AIPlayer('X', 'LEFT', 1), AIPlayer('O', 'LEFT', 1))
# omitting everything but the final result...

Player X (LEFT, 1) wins in 8 moves.
Congratulations!
|O|O| | | | | |
|X|X| | | | | |
|O|O| | | | | |
|X|X| | | | | |
|O|O|O| | | | |
|X|X|X|X| | | |
---------------
 0 1 2 3 4 5 6

# The player with the larger lookahead doesn't always win!
>>> connect_four(AIPlayer('X', 'LEFT', 3), AIPlayer('O', 'LEFT', 2))
# omitting everything but the final result...

Player O (LEFT, 2) wins in 19 moves.
Congratulations!
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|X|O|O| |
|X|X|O|O|X|X| |
|O|O|X|O|O|O|O|
|X|X|X|O|X|X|X|
---------------
 0 1 2 3 4 5 6


Submitting Your Work

You should use Gradesope to submit the following files:


Warnings

  • Make sure to use these exact file names, or Gradescope will not accept your files. If Gradescope reports that a file does not have the correct name, you should rename the file using the name listed in the assignment page.

  • If you make any last-minute changes to one of your Python files (e.g., adding additional comments), you should run the file in Spyder after you make the changes to ensure that it still runs correctly. Even seemingly minor changes can cause your code to become unrunnable.

  • If you submit an unrunnable file, Gradescope will accept your file, but it will not be able to auto-grade it. If time permits, you are strongly encouraged to fix your file and resubmit. Otherwise, your code will fail most if not all of our tests.

Warning: Beware of Global print statements

  • The autograder script cannot handle print statements in the global scope, and their inclusion causes this error:

autograder_fail

  • Why does this happen? When the autograder imports your file, the print statement(s) execute (at import time), which causes this error.

  • You can prevent this error by not having any print statements in the global scope. Instead, create an if __name__ == '__main__': section at the bottom of the file, and put any test cases/print statements in that controlled block. For example:

    if __name__ == '__main__':
    
        ## put test cases here:
        print('future_value(0.05, 2, 100)', future_value(0.05, 2, 100))
    
  • print statements inside of functions do not cause this problem.