Lab07 题解 (UCB CS61A@2021 Fall)


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.

问题描述如下: 每一年你的 balance 都会有利息, 问你哪一年你达到了 amount 可以退休了, 用代码模拟这个过程即可

python

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
    year, curAmount = 0, self.balance
    while True:
        year += 1
        curAmount *= (1 + self.interest)
        if curAmount > amount:
            return year

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.

和普通的 withdraw 方法相比, 我们要额外检查一个参数 - free_withdrawals, 它表示我们可以在不支付消费的情况下取款的次数. 这里要注意两个点:

  1. 取款失败也会扣免费次数
  2. 在要支付小费的情况下, 得小费+余额>取款金额才能成功取款

注意好这两个点写出代码就没有什么难度了

python

def withdraw(self, amount):
    if self.free_withdrawals > 0:
        if amount > self.balance:
            self.free_withdrawals -= 1
            return "Insufficient funds"
        if amount > self.max_withdrawal:
            self.free_withdrawals -= 1
            return "Can't withdraw that amount"
        self.free_withdrawals -= 1
        self.balance = self.balance - amount
    else:
        if amount + self.withdraw_fee > self.balance:
            self.free_withdrawals -= 1
            return "Insufficient funds"
        if amount + self.withdraw_fee > self.max_withdrawal:
            self.free_withdrawals -= 1
            return "Can't withdraw that amount"
        self.balance = self.balance - amount - self.withdraw_fee

下面要实现的是一个卡牌游戏, 两个玩家都有一副卡牌, 并且持有手牌. 双方玩家需要在每个回合中打出一张手牌. 每张手牌都有攻击力和防御力, 能量高的人获胜. 能量计算方式如下 $$(持有手牌的攻击力)-(对方卡牌的防御力)/2$$

第一个胜利 8 回合以上的人获得游戏的胜利.

同时还有下面几张特殊卡牌:

  1. AI 卡: 强制让对手出的卡牌的攻击力减去它的防御力, 然后让它的防御力 * 2
  2. Tutor 卡: 强制让对手弃三张卡牌并重新抽三张牌
  3. TA 卡: 强制交换对手出的卡牌的攻击力和防御力
  4. Instructor 卡: 根据对手出的卡牌来增加自己卡牌堆里面的卡牌的攻击力和防御力, 然后删除对手卡牌堆里所有跟他出的卡身材(攻击力和防御力)一样的

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.

在本体中我们要完成构造函数的代码和计算能量的函数, 其实就是把上面的规则翻译成代码, 很简单.

python

class Card:
    cardtype = 'Staff'

    def __init__(self, name, attack, defense):
        """
        Create a Card object with a name, attack,
        and defense.
        """
        self.name = name
        self.attack = attack
        self.defense = defense

    def power(self, opponent_card):
        """
        Calculate power as:
        (player card's attack) - (opponent card's defense)/2
        """
        return self.attack - opponent_card.defense / 2

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.

Call deck.draw() when implementing Player.__init__ and Player.draw. Don’t worry about how this function works - leave it all to the abstraction!

在这一题中进一步对手牌完善了描述, 我们一开始有 5 张手牌. 我们需要完成下面的功能:

  1. 构造函数, 在这里要从 deck 中取出 5 张手牌, 为了代码的简洁我们这里可以在这里使用 List comprehension
  2. draw 函数, 从卡牌堆取一张牌到手牌, 这个直接用 deck.draw() 即可
  3. play 函数, 其实就是出牌函数, 我们需要根据索引出牌, 记得把牌删掉, 要区分 python 中的 .remove().pop() 方法

python

def __init__(self, deck, name):
    """Initialize a Player object.
    """
    self.deck = deck
    self.name = name
    self.hand = [deck.draw() for i in range(5)]

def draw(self):
    """Draw a card from the player's deck and add it to their hand.
    """
    assert not self.deck.is_empty(), 'Deck is empty!'
    self.hand.append(self.deck.draw())

def play(self, card_index):
    """Remove and return a card from the player's hand at the given index.
    """
    card = self.hand[card_index]
    self.hand.pop(card_index)
    return card

Implement the effect method for AIs, which reduces the opponent card’s attack by the opponent card’s defense, and then doubles the opponent card’s defense.

Note: The opponent card’s resulting attack value cannot be negative.

这个问题要我们实现前面提到过的 AI 卡的功能, 就按照那个功能写代码即可, 注意如果攻击力算出来小于 0, 我们需要把它设置为 0

python

def effect(self, opponent_card, player, opponent):
    """
    Reduce the opponent's card's attack by its defense,
    then double its defense.
    """
    opponent_card.attack -= opponent_card.defense
    if opponent_card.attack < 0:
        opponent_card.attack = 0
    opponent_card.defense *= 2

Implement the effect method for TAs, which swaps the attack and defense of the opponent’s card.

也就是实现上面的 Tutor 卡, 注意这里我们要如何实现弃牌, 我们不应该破坏封装, 也就是我们要假装不知道内部实现细节, 那么查看 Player 类有什么方法我们可以使用. 显然, 我们可以用 play 把前三张牌打出去, 然后从牌堆里 draw 三张牌出来

python

def effect(self, opponent_card, player, opponent):
    """
    Discard the first 3 cards in the opponent's hand and have
    them draw the same number of cards from their deck.
    """
    # discard 3 cards
    for i in range(3):
        opponent.play(i)
    # draw 3 cards
    for i in range(3):
        opponent.draw()
    # You should add your implementation above this.
    print('{} discarded and re-drew 3 cards!'.format(opponent.name))

Implement the effect method for TAs, which swaps the attack and defense of the opponent’s card.

TA 卡的功能很简单, 无非就是交换攻击力和防御力而已, 在 python 中要实现这个功能是十分简洁的

python

def effect(self, opponent_card, player, opponent):
    """
    Swap the attack and defense of an opponent's card.
    """
    opponent_card.attack, opponent_card.defense = opponent_card.defense, opponent_card.attack

A new challenger has appeared! Implement the effect method for the Instructors, who add the opponent card’s attack and defense to all cards in the player’s deck and then removes all cards in the opponent’s deck that have the same attack or defense as the opponent’s card.

Note: If you mutate a list while iterating through it, you may run into trouble. Try iterating through a copy of the list instead. You can use slicing to make a copy of a list:

text

 >>> original = [1, 2, 3, 4]
 >>> copy = original[:]
 >>> copy
 [1, 2, 3, 4]
 >>> copy is original
 False

也就是我们要实现上面的 Instructor 卡, 相比于其他三个卡来说, 这个的功能稍微复杂些, 但是也还好. 关键之处在于如何一边遍历列表, 一边删除自己想要的元素. 可以查看这里面的回答知道改怎么做, 知道了这一点之后做这一题就不难了

python

def effect(self, opponent_card, player, opponent):
    """
    Adds the attack and defense of the opponent's card to
    all cards in the player's deck, then removes all cards
    in the opponent's deck that share an attack or defense
    stat with the opponent's card.
    """
    orig_opponent_deck_length = len(opponent.deck.cards)

    # add the attack and defense of the opponent's card ...
    for card in player.deck.cards:
        card.attack += opponent_card.attack
        card.defense += opponent_card.defense

    # remove all cards in the opponent's deck that share ...
    for card in opponent.deck.cards[:]:
        if card.attack == opponent_card.attack and card.defense == opponent_card.defense:
            opponent.deck.cards.remove(card)


    # You should add your implementation above this.
    discarded = orig_opponent_deck_length - len(opponent.deck.cards)
    if discarded:
        print('{} cards were discarded from {}\'s deck!'.format(discarded, opponent.name))
        return