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
-
Create a new class
Holiday
that inhereits from classDate
. -
Create a specialized version of the
__init__
method, which will take parameters for themonth
,day
,year
, andname
. The constructor must call the superclass (Date
) constructor to initialize its data memebrs, and then must initialize the specialized parametername
. You will know this works when you can create an object of typeHoliday
, for example:if __name__ == '__main__': m = Holiday(4, 17, 2023, "Marathon Monday") print('m =', m)
-
Override the
__repr__(self)
method, which returns a string representation of aHoliday
object. This method will be called when an object of typeHoliday
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 theHoliday
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
-
We will use our
Holiday
class and it’s superclassDate
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 theHolidays
s 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 theHoliday
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.
-
Write a function
closest_holiday(date)
, which will find theHoliday
that is closest to that parameterdate
, which is aDate
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 yourdate
. -
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:
-
Make sure that your class header specifies that
RandomPlayer
inherits fromPlayer
. -
Because all of the attributes of a
RandomPlayer
are inherited fromPlayer
, you will not need to define a constructor for this class. Rather, we can just use the constructor that is inherited fromPlayer
. -
Write a method
next_move(self, board)
that overrides (i.e., replaces) thenext_move
method that is inherited fromPlayer
. Rather than asking the user for the next move, this version ofnext_move
should choose at random from the columns in the specifiedboard
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:
-
To ensure that the method does not select the index of a column that is already full, we recommend that you begin by constructing a list containing the indices of all available columns — i.e., all columns to which you can still add a checker. For example, let’s say that the parameter
board
represents the following board:|O| | |X| | | | |X| | |O| | | | |X| | |O| |O| | |X|X| |O|X|X|O| |O|X|O|X|X|O|X| |O|O|X|O|O|O|X| --------------- 0 1 2 3 4 5 6
The list of available columns in this case would be
[1,2,4,5,6]
.To build this list, you should consider the columns one at a time, and add the index of any available column to the list. This can be done using a loop, or you might want to use a list comprehension instead. We also encourage you to take advantage of one of your
Board
methods to determine if a given column is available! -
We have included an
import
statement for therandom
module so that you can use the appropriate function to make a random choice from the list of available columns.
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:
-
-1 for a column that is already full
-
0 for a column that, if chosen as the next move, will result in a loss for the player at some point during the number of moves that the player looks ahead.
-
100 for a column that, if chosen as the next move, will result in a win for the player at some point during the number of moves that the player looks ahead.
-
50 for a column that, if chosen as the next move, will result in neither a win nor a loss for the player at any point during the number of moves that the player looks ahead.
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:
'LEFT'
: out of all the columns that are tied for the highest score, pick the leftmost one.'RIGHT'
: out of all the columns that are tied for the highest score, pick the rightmost one.'RANDOM'
: out of all the columns that are tied for the highest score, pick one of them at random.
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:
-
one called
tiebreak
that stores a string specifying the player’s tiebreaking strategy ('LEFT'
,'RIGHT'
, or'RANDOM'
) -
one called
lookahead
that stores an integer specifying how many moves the player looks ahead in order to evaluate possible moves.
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:
-
Make sure that your class header specifies that
AIPlayer
inherits fromPlayer
. -
Write a constructor
__init__(self, checker, tiebreak, lookahead)
that constructs a newAIPlayer
object. Begin the method withassert
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.
-
Write a method
__repr__(self)
that returns a string representing anAIPlayer
object. This method will override/replace the__repr__
method that is inherited fromPlayer
. In addition to indicating which checker theAIPlayer
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. -
Write a method
max_score_column(self, scores)
that takes a listscores
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 calledAIPlayer
‘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-inmax
function for this), and to then create a list containing the indices of all elements inscores
that match this maximum score. For example, ifscores
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. Ifscores
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 theAIPlayer
‘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 therandom
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
-
-
Write a method
scores_for(self, board)
that takes aBoard
objectboard
and determines the calledAIPlayer
‘s scores for the columns inboard
. 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 calledAIPlayer
‘slookahead
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
AIPlayer
s object (including the inherited ones) and the methods in theBoard
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 ofscores
. Here is an outline of the logic:- 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. - Otherwise, if
board
is already a win for the calledAIPlayer
(i.e., forself
), use a score of 100 for the current column. - Otherwise, if
board
is already a win for the player’s opponent, use a score of 0 for the current column. - 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.) -
Otherwise, we need to look ahead! This involves taking several steps:
-
Add one of the called
AIPlayer
‘s checkers to the current column using the appropriateBoard
method. -
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 asself
, but with a lookahead that is one less than the one used byself
. 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). -
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. -
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]
- If the current column is full, use a score of -1 for it.
In other words, assign -1 to the appropriate element of
your
-
Write a method
next_move(self, board)
that overrides (i.e., replaces) thenext_move
method that is inherited fromPlayer
. Rather than asking the user for the next move, this version ofnext_move
should return the calledAIPlayer
‘s judgment of its best possible move. This method won’t need to do much work, because it should use yourscores_for
andmax_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:
- your modified
ps10pr1.py
file for Problem Set 10 Problem 1. - your modified
ps10pr2.py
file for Problem Set 10 Problem 2. - your modified
ps10pr3.py
file for Problem Set 10 Problem 3. - your modified
ps11pr1.py
file containing your solutions for Problem 1 - your modified
ps11pr2.py
file containing your solutions for Problem 2 - your modified
ps11pr3.py
file containing your solutions for Problem 3 - your modified
ps11pr4.py
file containing your solutions for Problem 4
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:
-
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 anif __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.