class Car: num_wheels = 4 gas = 30 headlights = 2 size = 'Tiny'
def __init__(self, make, model): self.make = make self.model = model self.color = 'No color yet. You need to paint me.' self.wheels = Car.num_wheels self.gas = Car.gas
def paint(self, color): self.color = color return self.make + ' ' + self.model + ' is now ' + color
Letâs say weâd like to model a bank account that can handle interactions such as depositing funds or gaining interest on current funds. In the following questions, we will be building off of the Account class. Hereâs our current definition of the class:
def withdraw(self, amount): 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
Q2: Retirement
Add a time_to_retire method to the Account class. This method takes in an amount and returns how many years the holder would need to wait in order for the current balance to grow to at least amount, assuming that the bank adds balance times the interest rate to the total balance at the end of every year.
1 2 3 4 5 6 7 8 9 10
def time_to_retire(self, amount): """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 ***" future_balance = self.balance time = 0 while future_balance < amount: future_balance *= (1+self.interest) time += 1 return time
Q3: FreeChecking
Implement the FreeChecking class, which is like the Account class from lecture except that it charges a withdraw fee after 2 withdrawals. If a withdrawal is unsuccessful, it still counts towards the number of free withdrawals remaining, but no fee for the withdrawal will be charged.
Hint: Donât forget that FreeChecking inherits from Account! Check the Inheritance section in Topics for a refresher.
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) # Ok, two free withdrawals is enough 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 = 1 free_withdrawals = 2
"*** YOUR CODE HERE ***" def withdraw(self, amount): if self.free_withdrawals > 0: self.free_withdrawals -= 1 return super().withdraw(amount) else: return super().withdraw((amount+self.withdraw_fee))
Magic: the Lambda-ing
In the next part of this lab, we will be implementing a card game! This game is inspired by the similarly named Magic: The Gathering.
Once youâve implemented the game, you can start it by typing:
1
python3 cardgame.py
While playing the game, you can exit it and return to the command line with Ctrl-C or Ctrl-D.
This game uses several different files.
Code for all questions can be found in classes.py.
The game loop can be found in cardgame.py, and is responsible for running the game. You wonât need to open or read this file to receive full credit.
If you want to modify your game later to add your own custom cards and decks, you can look in cards.py to see all the standard cards and the default deck; here, you can add more cards and change what decks you and your opponent use. If youâre familiar with the original game, you may notice the cards were not created with balance in mind, so feel free to modify the stats and add or remove cards as desired.
Rules of the Game
Hereâs how the game goes:
There are two players. Each player has a hand of cards and a deck, and at the start of each round, each player draws a random card from their deck. If a playerâs deck is empty when they try to draw, they will automatically lose the game.
Cards have a name, an attack value, and a defense value. Each round, each player chooses one card to play from their own hands. The cardsâ power values are then calculated and compared. The card with the higher power wins the round. Each played cardâs power value is calculated as follows:
For example, letâs say Player 1 plays a card with 2000 attack and 1000 defense and Player 2 plays a card with 1500 attack and 3000 defense. Their cardsâ powers are calculated as:
However, there are a few effects we can add (in the optional questions section) to make this game a more interesting. A card can be of type AI, Tutor, TA, or Instructor, and each type has a different effect when they are played. Note that when a card is played, the card is removed from the playerâs hand. This means that the card is no longer in the hand when the effect takes place. All effects are applied before power is calculated during that round:
An AICard will allow you to add the top two cards of your deck to your hand via drawing.
A TutorCard will add a copy of the first card in your hand to your hand, at the cost of automatically losing the current round.
A TACard discards the card with the highest power in your hand, and adds the discarded cardâs attack and defense to the played TACardâs stats.
An InstructorCard can survive multiple rounds, as long as it has a non-negative attack or defense. However, at the beginning of each round that it is played, its attack and defense are reduced by 1000 each.
Feel free to refer back to these series of rules later on, and letâs start making the game!
Q4: Making Cards
To play a card game, weâre going to need to have cards, so letâs make some! Weâre gonna implement the basics of the Card class first.
First, implement the Card classâ constructor in classes.py. This constructor takes three arguments:
a string as the name of the card
an integer as the attack value of the card
an integer as the defense value of the card
Each Card instance should keep track of these values using instance attributes called name, attack, and defense.
You should also implement the power method in Card, which takes in another card as an input and calculates the current cardâs power. Refer to the Rules of the Game if youâd like a refresher on how power is calculated.
Now that we have cards, we can make a deck, but we still need players to actually use them. Weâll now fill in the implementation of the Player class.
A Player instance has three instance attributes:
name is the playerâs name. When you play the game, you can enter your name, which will be converted into a string to be passed to the constructor.
deck is an instance of the Deck class. You can draw from it using its .draw() method.
hand is a list of Card instances. Each player should start with 5 cards in their hand, drawn from their deck. Each card in the hand can be selected by its index in the list during the game. When a player draws a new card from the deck, it is added to the end of this list.
Complete the implementation of the constructor for Player so that self.hand is set to a list of 5 cards drawn from the playerâs deck.
Next, implement the draw and play methods in the Player class. The draw method draws a card from the deck and adds it to the playerâs hand. The play method removes and returns a card from the playerâs hand at the given index.
Hint: use methods from the Deck class wherever possible when attempting to draw from the deck when implementing Player.__init__ and Player.draw.
class Player: def __init__(self, deck, name): """Initialize a Player object. A Player starts the game by drawing 5 cards from their deck. Each turn, a Player draws another card from the deck and chooses one to play. >>> test_card = Card('test', 100, 100) >>> test_deck = Deck([test_card.copy() for _ in range(6)]) >>> test_player = Player(test_deck, 'tester') >>> len(test_deck.cards) 1 >>> len(test_player.hand) 5 """ self.deck = deck self.name = name "*** YOUR CODE HERE ***" for i in range(5): self.hand.append(self.deck.draw())
def draw(self): """Draw a card from the player's deck and add it to their hand. >>> test_card = Card('test', 100, 100) >>> test_deck = Deck([test_card.copy() for _ in range(6)]) >>> test_player = Player(test_deck, 'tester') >>> test_player.draw() >>> len(test_deck.cards) 0 >>> len(test_player.hand) 6 """ assert not self.deck.is_empty(), 'Deck is empty!' "*** YOUR CODE HERE ***" self.hand.append(self.deck.draw())
def play(self, index): """Remove and return a card from the player's hand at the given INDEX. >>> from cards import * >>> test_player = Player(standard_deck, 'tester') >>> ta1, ta2 = TACard("ta_1", 300, 400), TACard("ta_2", 500, 600) >>> tutor1, tutor2 = TutorCard("t1", 200, 500), TutorCard("t2", 600, 400) >>> test_player.hand = [ta1, ta2, tutor1, tutor2] >>> test_player.play(0) is ta1 True >>> test_player.play(2) is tutor2 True >>> len(test_player.hand) 2 """ "*** YOUR CODE HERE ***" if len(self.hand) > index: return self.hand.pop(index) else: return False
Optional Questions
To make the card game more interesting, letâs add effects to our cards! We can do this by implementing an effect function for each card class, which takes in the opponent card, the current player, and the opponent player. Remember that by the time effect is called, the played card is no longer in the playerâs hand.
You can find the following questions in classes.py.
Important: For the following sections, do not overwrite any lines already provided in the code.
Q6: AIs: Resourceful Resources
In the AICard class, implement the effect method for AIs. An AICard will allow you to add the top two cards of your deck to your hand via drawing from your deck.
Once you have finished writing your code for this problem, set implemented to True so that the text is printed when playing an AICard! This is specifically for theAICard! For future questions, make sure to look at the problem description carefully to know when to reassign any pre-designated variables.
def effect(self, opponent_card, player, opponent): """ Add the top two cards of your deck to your hand via drawing. Once you have finished writing your code for this problem, set implemented to True so that the text is printed when playing an AICard.
>>> from cards import * >>> player1, player2 = Player(standard_deck.copy(), 'p1'), Player(standard_deck.copy(), 'p2') >>> opponent_card = Card("other", 500, 500) >>> test_card = AICard("AI Card", 500, 500) >>> initial_deck_length = len(player1.deck.cards) >>> initial_hand_size = len(player1.hand) >>> test_card.effect(opponent_card, player1, player2) AI Card allows me to draw two cards! >>> initial_hand_size == len(player1.hand) - 2 True >>> initial_deck_length == len(player1.deck.cards) + 2 True """ "*** YOUR CODE HERE ***" player.hand.append(player.deck.cards.pop(0)) player.hand.append(player.deck.cards.pop(0)) implemented = True # You should add your implementation above this. if implemented: print(f"{self.name} allows me to draw two cards!")
Q7: Tutors: Sneaky Search
In the TutorCard class, implement the effect method for Tutors. A TutorCard will add a copy of the first card in your hand to your hand, at the cost of automatically losing the current round. Note that if there are no cards in hand, a TutorCard will not add any cards to the hand, but must still lose the round.
To implement the âlosingâ functionality, it is sufficient to override Cardâs power method to return -float('inf') in the TutorCard class. In addition, be sure to add copies of cards, instead of the chosen card itself! Class methods may come in handy.
def effect(self, opponent_card, player, opponent): """ Add a copy of the first card in your hand to your hand, at the cost of losing the current round. If there are no cards in hand, this card does not add any cards, but still loses the round. To implement the second part of this effect, a Tutor card's power should be less than all non-Tutor cards.
>>> from cards import * >>> player1, player2 = Player(standard_deck.copy(), 'p1'), Player(standard_deck.copy(), 'p2') >>> opponent_card = Card("other", 500, 500) >>> test_card = TutorCard("Tutor Card", 10000, 10000) >>> player1.hand = [Card("card1", 0, 100), Card("card2", 100, 0)] >>> test_card.effect(opponent_card, player1, player2) Tutor Card allows me to add a copy of a card to my hand! >>> print(player1.hand) [card1: Staff, [0, 100], card2: Staff, [100, 0], card1: Staff, [0, 100]] >>> player1.hand[0] is player1.hand[2] # must add a copy! False >>> player1.hand = [] >>> test_card.effect(opponent_card, player1, player2) >>> print(player1.hand) # must not add a card if not available [] >>> test_card.power(opponent_card) < opponent_card.power(test_card) True """ "*** YOUR CODE HERE ***" if len(player.hand) > 0: player.hand.append(player.hand[0].copy()) added = True else: added = False # You should add your implementation above this. if added: print(f"{self.name} allows me to add a copy of a card to my hand!")
"*** YOUR CODE HERE ***"
Q8: TAs: Power Transfer
In the TACard class, implement the effect method for TAs. A TACard discards the card with the highest power in your hand, and adds the discarded cardâs attack and defense to the played TACardâs stats. Discarding a card removes the card from your hand. If there are no cards in hand, the TACard should not do anything for its effect.
def effect(self, opponent_card, player, opponent, arg=None): """ Discard the card with the highest `power` in your hand, and add the discarded card's attack and defense to this card's own respective stats.
>>> from cards import * >>> player1, player2 = Player(standard_deck.copy(), 'p1'), Player(standard_deck.copy(), 'p2') >>> opponent_card = Card("other", 500, 500) >>> test_card = TACard("TA Card", 500, 500) >>> player1.hand = [] >>> test_card.effect(opponent_card, player1, player2) # if no cards in hand, no effect. >>> print(test_card.attack, test_card.defense) 500 500 >>> player1.hand = [Card("card1", 0, 100), TutorCard("tutor", 10000, 10000), Card("card3", 100, 0)] >>> test_card.effect(opponent_card, player1, player2) # must use card's power method. TA Card discards card3 from my hand to increase its own power! >>> print(player1.hand) [card1: Staff, [0, 100], tutor: Tutor, [10000, 10000]] >>> print(test_card.attack, test_card.defense) 600 500 """ "*** YOUR CODE HERE ***" best_card = None # You should add your implementation above this. if best_card: print(f"{self.name} discards {best_card.name} from my hand to increase its own power!")
Q9: Instructors: Immovable
In the InstructorCard class, implement the effect method for Instructors. An InstructorCard can survive multiple rounds, as long as it has a non-negative attack or defense at the end of a round. However, at the beginning of each round that it is played (including the first time!), its attack and defense are permanently reduced by 1000 each.
To implement the âsurviveâ functionality, the InstructorCard should re-add itself to the playerâs hand.
class InstructorCard(Card): cardtype = 'Instructor'
def effect(self, opponent_card, player, opponent, arg=None): """ Survives multiple rounds, as long as it has a non-negative attack or defense at the end of a round. At the beginning of the round, its attack and defense are permanently reduced by 1000 each. If this card would survive, it is added back to the hand.
>>> from cards import * >>> player1, player2 = Player(standard_deck.copy(), 'p1'), Player(standard_deck.copy(), 'p2') >>> opponent_card = Card("other", 500, 500) >>> test_card = InstructorCard("Instructor Card", 1000, 1000) >>> player1.hand = [Card("card1", 0, 100)] >>> test_card.effect(opponent_card, player1, player2) Instructor Card returns to my hand! >>> print(player1.hand) # survives with non-negative attack [card1: Staff, [0, 100], Instructor Card: Instructor, [0, 0]] >>> player1.hand = [Card("card1", 0, 100)] >>> test_card.effect(opponent_card, player1, player2) >>> print(player1.hand) [card1: Staff, [0, 100]] >>> print(test_card.attack, test_card.defense) -1000 -1000 """ "*** YOUR CODE HERE ***" if self.attack>0 and self.defense>0: self.attack-=1000 self.defense-=1000 player.hand.append(InstructorCard(self.name, self.attack, self.defense)) re_add = True else : self.attack-=1000 self.defense-=1000 re_add = False # You should add your implementation above this. if re_add: print(f"{self.name} returns to my hand!")