Homework 7: OOP and Inheritance
Due by 11:59pm on Tuesday, March 31
Instructions
Download hw07.zip. Inside the archive, you will find a file called
hw07.py, along with a copy of the ok autograder.
Submission: When you are done, submit the assignment to Gradescope. You may submit more than once before the deadline; only the final submission will be scored. Check that you have successfully submitted your code on Gradescope. See Lab 0 for more instructions on submitting assignments.
Using Ok: If you have any questions about using Ok, please refer to this guide.
Readings: You might find the following references useful:
Grading: Homework is graded based on correctness. Each incorrect problem will decrease the total score by one point. This homework is out of 6 points.
Required Questions
Getting Started Videos
These videos may provide some helpful direction for tackling the coding problems on this assignment.
To see these videos, you should be logged into your berkeley.edu email.
Please note that Question 3 and Question 5 are optional! You can try them if you want an extra challenge, but they are out of scope until we cover inheritance. We will not be prioritizing support for these questions on Ed or during office hours.
Election
Let's implement a game called Election. In this game, two players compete to try and earn the most votes. Both players start with 0 votes and 100 popularity.
The two players alternate turns, and the first player starts. Each turn, the current player chooses an action. There are two types of actions:
- The player can debate, and either gain or lose 50 popularity. If the player
has popularity
p1and the other player has popularityp2, then the probability that the player gains 50 popularity ismax(0.1, p1 / (p1 + p2)). Note that themaxhere ensures that the probability is never lower than 0.1. - The player can give a speech. If the player has popularity
p1and the other player has popularityp2, then the player gainsp1 // 10votes and popularity and the other player losesp2 // 10popularity.
The game ends when a player reaches 50 votes, or after a total of 10 turns have been played (each player has taken 5 turns). Whoever has more votes at the end of the game is the winner!
Q1: Player
First, let's implement the Player class. Fill in the debate and speech
methods, that take in another Player other, and implement the correct
behavior as detailed above. Here are a few additional things to keep in mind:
- Each player carries a random number generator (the
random_funcinstance attribute), which is a function that returns a random float between 0 and 1 when called. - In the
debatemethod, you should call therandom_funcfunction to get a random number. The player should gain 50 popularity if the random number is smaller than the probability described above, or lose 50 popularity otherwise. - Neither players' popularity should ever become negative. If this happens, set it equal to 0 instead.
### Phase 1: The Player Class
class Player:
"""
>>> random = make_test_random()
>>> p1 = Player('Hill', random)
>>> p2 = Player('Don', random)
>>> p1.popularity
100
>>> p1.debate(p2) # random() should return 0.0
>>> p1.popularity
150
>>> p2.popularity
100
>>> p2.votes
0
>>> p2.speech(p1)
>>> p2.votes
10
>>> p2.popularity
110
>>> p1.popularity
135
>>> p1.speech(p2)
>>> p1.votes
13
>>> p1.popularity
148
>>> p2.votes
10
>>> p2.popularity
99
>>> for _ in range(4): # 0.1, 0.2, 0.3, 0.4
... p1.debate(p2)
>>> p2.debate(p1)
>>> p2.popularity
49
>>> p2.debate(p1)
>>> p2.popularity
0
"""
def __init__(self, name, random_func):
self.name = name
self.votes = 0
self.popularity = 100
self.random_func = random_func
def debate(self, other):
"*** YOUR CODE HERE ***"
def speech(self, other):
"*** YOUR CODE HERE ***"
def choose(self, other):
return self.speech
Use Ok to test your code:
python3 ok -q Player
Q2: Game
Now, implement the Game class. Fill in the play method, which should
alternate between the two players, starting with p1, and have each player take
one turn at a time. The choose method in the Player class returns the
method, either debate or speech, that should be called to perform the
action.
In addition, fill in the winner method, which should return the
player with more votes, or None if the players are tied.
### Phase 2: The Game Class
class Game:
"""
>>> random = make_test_random()
>>> p1, p2 = Player('Hill',random), Player('Don', random)
>>> g = Game(p1, p2)
>>> winner = g.play()
>>> p1 is winner
True
>>> # Additional correctness tests
>>> winner is g.winner()
True
>>> g.turn
10
>>> p1.votes = p2.votes
>>> print(g.winner())
None
"""
def __init__(self, player1, player2):
self.p1 = player1
self.p2 = player2
self.turn = 0
def play(self):
while not self.game_over():
"*** YOUR CODE HERE ***"
return self.winner()
def game_over(self):
return max(self.p1.votes, self.p2.votes) >= 50 or self.turn >= 10
def winner(self):
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q Game
Q3: New Players
The choose method in the Player class is boring because it always returns
the speech method. Let's implement two new classes that inherit from Player,
but have more interesting choose methods.
Implement the choose method in the AggressivePlayer class, which returns the
debate method if the player's popularity is less than or equal to other's
popularity, and speech otherwise. Also implement the choose method in the
CautiousPlayer class, which returns the debate method if the player's
popularity is 0, and speech otherwise.
### Phase 3: New Players
class AggressivePlayer(Player):
"""
>>> random = make_test_random()
>>> p1, p2 = AggressivePlayer('Don', random), Player('Hill', random)
>>> g = Game(p1, p2)
>>> winner = g.play()
>>> p1 is winner
True
>>> # Additional correctness tests
>>> p1.popularity = p2.popularity
>>> p1.choose(p2) == p1.debate
True
>>> p1.popularity += 1
>>> p1.choose(p2) == p1.debate
False
>>> p2.choose(p1) == p2.speech
True
"""
def choose(self, other):
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q AggressivePlayer
class CautiousPlayer(Player):
"""
>>> random = make_test_random()
>>> p1, p2 = CautiousPlayer('Hill', random), AggressivePlayer('Don', random)
>>> p1.popularity = 0
>>> p1.choose(p2) == p1.debate
True
>>> p1.popularity = 1
>>> p1.choose(p2) == p1.debate
False
>>> # Additional correctness tests
>>> p2.choose(p1) == p2.speech
True
"""
def choose(self, other):
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q CautiousPlayer
Checking Accounts
Let's improve the Account class from lecture, which models a bank account
that can process deposits and withdrawals.
class Account:
"""An account has a balance and a holder.
>>> a = Account('John')
>>> a.deposit(10)
10
>>> a.balance
10
>>> a.interest
0.02
>>> a.time_to_retire(10.25) # 10 -> 10.2 -> 10.404
2
>>> a.balance # Calling time_to_retire method should not change the balance
10
>>> a.time_to_retire(11) # 10 -> 10.2 -> ... -> 11.040808032
5
>>> a.time_to_retire(100)
117
"""
max_withdrawal: int = 10
interest: float = 0.02
def __init__(self, account_holder: str):
self.balance = 0
self.holder = account_holder
def deposit(self, amount: int) -> int:
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount: int) -> int | str:
if amount > self.balance:
return "Insufficient funds"
if amount > self.max_withdrawal:
return "Can't withdraw that amount"
self.balance = self.balance - amount
return self.balance
Q4: Retirement
Add a time_to_retire method to the Account class. This method takes in an
amount and returns the number of years until the current balance grows to at
least amount, assuming that the bank adds the interest (calculated as the
current balance multiplied by the interest rate) to the balance at the end
of each year. Make sure you're not modifying the account's balance!
Important: Calling the
time_to_retiremethod should not change the account balance.
def time_to_retire(self, amount: float) -> int:
"""Return the number of years until balance would grow to amount."""
assert self.balance > 0 and amount > 0 and self.interest > 0
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q Account
Q5: FreeChecking
Implement the FreeChecking class, which is like the Account class
except that it charges a withdraw fee withdraw_fee after
withdrawing free_withdrawals number of times.
If a withdrawal is unsuccessful, no withdrawal fee will be charged, but it still counts towards the number of free
withdrawals remaining.
class FreeChecking(Account):
"""A bank account that charges for withdrawals, but the first two are free!
>>> ch = FreeChecking('Jack')
>>> ch.balance = 20
>>> ch.withdraw(100) # First one's free. Still counts as a free withdrawal even though it was unsuccessful
'Insufficient funds'
>>> ch.withdraw(3) # Second withdrawal is also free
17
>>> ch.balance
17
>>> ch.withdraw(3) # Now there is a fee because free_withdrawals is only 2
13
>>> ch.withdraw(3)
9
>>> ch2 = FreeChecking('John')
>>> ch2.balance = 10
>>> ch2.withdraw(3) # No fee
7
>>> ch.withdraw(3) # ch still charges a fee
5
>>> ch.withdraw(5) # Not enough to cover fee + withdraw
'Insufficient funds'
"""
withdraw_fee: int = 1
free_withdrawals: int = 2
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q FreeChecking
Check Your Score Locally
You can locally check your score on each question of this assignment by running
python3 ok --score
This does NOT submit the assignment! When you are satisfied with your score, submit the assignment to Gradescope to receive credit for it.
Submit Assignment
Submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.