【问题标题】:OOP Python card game class methodsOOP Python纸牌游戏类方法
【发布时间】:2019-09-08 13:33:04
【问题描述】:

我编写了一个简单的纸牌游戏,在初始化游戏时,每个玩家都会得到两张牌。

在底部,我创建了一个名为 returnCards 的方法,它将玩家的牌放回牌组中。

我是 python 中的 OOP 新手,我很好奇创建这样的独立方法是否是一个好的约定?

我觉得这个方法实际上应该是一个 Player 类方法,但我不确定如何编写它。最重要的是,我在编写 OOP 代码时试图了解良好实践

import random

class Card:
    def __init__(self, suit, val):
        self.suit = suit
        self.value = val

class Deck:
    def __init__(self):
        self.cards = []
        self.build()

    def build(self):
        for i in ["Hearts", "Diamonds", "Spades", "Clubs"]:
            for j in range(1,14):
                self.cards.append(Card(i, j))

    def length(self):
        return len(self.cards)

class Game:
    def __init__(self, players, deck):
        self.players = players
        self.deck = deck
        self.cards = deck.cards

    def deal(self):
        for player in self.players:
            for i in range(2):
                player.cards.append(self.drawCard())

    def drawCard(self):
        drawnCard = self.deck.cards[0]
        self.deck.cards = self.deck.cards[1:]
        return drawnCard

class Player:
    def __init__(self, name):
        self.name = name
        self.cards = []


def returnCards(player, game):
    game.deck.cards.append(card for card in player.cards)
    player.cards = []


deckOfCards = Deck()
playerOne = Player("John")
playerTwo = Player("Harrry")
newGame = Game([playerOne, playerTwo], deckOfCards)
newGame.deal()
returnCards()

【问题讨论】:

  • Card、Deck 和 Player 类似乎是合理的,尽管可能缺少一些您需要的属性/方法。游戏似乎关闭了。游戏需要套牌和卡片吗?应该有一些“转弯”的概念吗?如果是,您如何建模?每次游戏开始时每个玩家都应该得到相同的牌吗?希望有一些问题能让您走上正轨。
  • 我假设您将 1 用于 Ace,11 用于 Jack,等等?

标签: python oop


【解决方案1】:

我会考虑在PlayerDeck 上使用两个return_cards 方法

class Player:

    ...

    def return_cards(self, deck):
        deck.return_cards(self.cards)
        self.cards = []


class Deck:

    ...

    def return_cards(self, cards):
        self.cards.extend(cards)

这个想法是对象处理它们自己的职责——它们为自己做事——而不是管理其他对象的职责。因此,Player 负责将其卡片归还给 Deck,而 Deck 负责将卡片归还后发生的情况。

另一个考虑因素是我们不希望对象过多地了解彼此的内部结构。

game.deck.cards.append(card for card in player.cards)

表示Game“知道”Deck 有一个名为cards 的列表。这种设计将对象紧密结合在一起——如果Deck.cards 变成一个集合或一个字典,我们必须更改Game——最好通过方法而不是直接访问卡片。另见encapsulationLaw of Demeter

Game 将负责告诉Player 是时候还手了。

【讨论】:

  • 这很有意义,肯定会帮助我编写更清晰的代码,谢谢!
【解决方案2】:

是的,使用单独定义的函数是可以的,但只有当函数不相关并且与对象(格式化程序等)紧密相关时。

在您的情况下,它实际上应该是 Game 类的一个方法,它基本上是一个新游戏的开始。通过 cmets 中的一些提示来看看它的外观:

from collections import deque
import random


class Card:
    def __init__(self, suit, val):
        self.suit = suit
        self.value = val

    # good practice is to include repr and str magic methods - that way you can print meaningful info
    def __repr__(self):
        return '<{} {}>'.format(self.value, self.suit)


class Deck:
    def __init__(self):
        # use deque - it's more efficient to pop from left than from a list -> good advice -> learn build in types :)
        self.cards = deque()
        self.build()

    def build(self):
        self.cards.clear()
        for i in ["Hearts", "Diamonds", "Spades", "Clubs"]:
            for j in range(1, 14):
                self.cards.append(Card(i, j))
        # need to shuffle in order to have random order of cards
        random.shuffle(self.cards)

    def length(self):
        return len(self.cards)

    def get_card(self):
        return self.cards.popleft()

    def __repr__(self):
        return 'Deck: [{} Cards]'.format(self.length())


class Game:
    def __init__(self, players, deck):
        self.players = players
        self.deck = deck

    def deal(self):
        for player in self.players:
            for i in range(2):
                player.give_card(self.draw_card())

    # in python use underscore not camelCase for methods
    def draw_card(self):
        # avoid mutating other object properties in other objects
        return self.deck.get_card()

    def restart_game(self):
        # simply rebuild the deck - it will reshuffle all cards
        self.deck.build()
        for player in self.players:
            player.return_cards()


class Player:
    def __init__(self, name):
        self.name = name
        self.cards = []

    def give_card(self, card):
        self.cards.append(card)

    def return_cards(self):
        self.cards = []

    def __repr__(self):
        return 'Player: {} - [{}]'.format(self.name, ','.join([str(x) for x in self.cards]))


deck_of_cards = Deck()
player_one = Player("John")
player_two = Player("Harrry")
new_game = Game([player_one, player_two], deck_of_cards)
new_game.deal()
print(player_one, player_two)
print(new_game.deck)
new_game.restart_game()
print(player_one, player_two)
print(new_game.deck)

如果出于某种原因未在此处说明您想要保留原始卡片对象(即计算卡片被抽了多少次等),您可以按照@snakecharmerb 的回答(套牌和播放器中的方法),我会处理Game 类背后的逻辑(这样你就不需要玩家和套牌相互引用,从业务角度来看更合乎逻辑)

【讨论】:

  • 我不知道 deque 所以这绝对是有用的知识。据推测,如果我想在 python 中创建一个堆栈,那么 deque 将是最有效的方法?
  • @PIEAGE 是的,双端队列针对从两侧追加和弹出元素进行了优化。我建议习惯内置集合、itertools 和 functools,因为它包含很多有用的代码
【解决方案3】:

做OOP没有绝对的对与错。这只是一种概念理念,可帮助您组织代码以实现可维护性和可持续性。

当用例改变时,你会做很多重构工作(它总是会改变)。不断变化的用例也可能会影响您组织 return_cards 方法的方式。

所以您只需要担心代码目前的样子,您和与您一起工作的人是否足够理解?同样,没有绝对的答案。

这是我的看法。

  • 拥有一个全局的return_cards 函数似乎不合理。您将两种类型的哲学混合在一起,过程编程和面向对象编程,同时提供非常有限的线索和边界来解释为什么会这样。

  • 目前,我认为 return_cards() 只能位于 Game 类中。由于DeckPlayer中的任何一个都可以看作Game的状态。所以让Game的方法改变Game的状态,完全可以理解。

  • 当然,你可以这样做:

class Game:
    ......
    def return_cards(self):
        for player in self.players:
            player_cards = self.players.give_all_cards_back()
            self.decks.collect_all_cards(player_cards)

但我认为目前没有必要。什么时候?例如,当其他一些模块,如 UI 或一些文本打印程序,依赖于您的 Player 对象时,您必须在玩家归还卡片时通知他们。那么创建Player.give_all_cards_back()之类的东西并将所有相关过程封装在一起是合理的。

这里是来自 Ramalho 的 Fluent Python (O'Reilly) 的一些示例代码,演示了如何构建 Deck 类,供您参考。

import collections

Card = collections.namedtuple("Card", ("rank", "suit"))

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list("JQKA")
    suits = "spades diamonds clubs hearts".split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]

【讨论】:

    猜你喜欢
    • 2023-03-14
    • 1970-01-01
    • 2013-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多