J'ai écrit un jeu de cartes simple où à l'initialisation du jeu, chaque joueur reçoit deux cartes.
En bas, j'ai créé une méthode appelée returnCards qui remet les cartes d'un joueur dans le paquet.
Je suis nouveau dans la POO en python et je suis curieux de savoir si c'est une bonne convention de créer une méthode autonome comme celle-ci?
J'ai l'impression que cette méthode devrait en fait être une méthode de classe Player mais je ne sais pas comment l'écrire en tant que telle. Plus que tout, j'essaie de comprendre les bonnes pratiques lors de l'écriture du code POO
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()
3 Réponses :
J'envisagerais d'avoir deux méthodes return_cards , sur Player et Deck
game.deck.cards.append(card for card in player.cards)
L'idée est que les objets assument leurs propres responsabilités - ils font des choses à eux-mêmes - plutôt que de gérer les responsabilités des autres objets. Ainsi, Player est responsable de renvoyer ses cartes à Deck , et Deck est responsable de ce qui arrive aux cartes une fois qu'elles ont été retournées.
Une autre considération est que nous ne voulons pas que les objets en sachent trop sur les éléments internes les uns des autres.
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)
signifie que Game "sait" que Deck a une liste nommée cartes . Ce type de conception associe étroitement les objets - si Deck.cards devient un ensemble ou un dict, nous devons changer le Game - il est préférable d'accéder aux cartes via des méthodes plutôt que directement . Voir aussi encapsulation et Loi de Demeter .
Game serait chargé de dire au Player qu'il est temps de rendre la main.
Cela est parfaitement logique et va certainement m'aider à écrire un code plus clair, merci!
Oui, utiliser des fonctions définies séparément est correct, mais seulement lorsque la fonction n'est pas liée et fortement liée aux objets (formateurs, etc.).
Dans votre cas, il devrait en fait être une méthode de classe Game en ce sens fondamentalement un nouveau début de jeu. Jetez un œil à son apparence avec quelques astuces dans les commentaires:
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)
Si pour une raison quelconque non indiquée ici, vous souhaitez conserver les objets de la carte d'origine (c'est-à-dire compter combien fois que la carte a été tirée, etc.), vous pouvez suivre la réponse @snakecharmerb (méthodes dans le deck et le player) et je gérerais la logique derrière cela dans la classe Game (de cette façon, vous n'avez pas besoin de référence entre le joueur et le deck, et c'est plus logique du point de vue commercial)
Je ne connaissais pas deque, c'est donc quelque chose d'utile à savoir. Vraisemblablement, si je voulais créer une pile en python, alors deque serait le moyen le plus efficace de le faire?
@PIEAGE Oui, deque est optimisé pour ajouter et faire sauter les éléments des deux côtés. Je recommande de s'habituer aux collections intégrées, itertools et functools car il contient beaucoup de code utile
Il n'y a pas de vrai ou de faux absolu lors de l'exécution de la POO. C'est juste une philosophie conceptuelle qui vous aide à organiser votre code pour atteindre la maintenabilité et la durabilité.
Vous ferez beaucoup de travaux de refactoring à l'avenir lorsque le cas d'utilisation changera (cela changera toujours). Le changement de cas d'utilisation peut également affecter la manière dont vous organisez votre méthode return_cards .
Donc, tout ce dont vous avez à vous soucier est de savoir à quoi ressemble le code ACTUELLEMENT, est-il suffisamment compréhensible pour vous et aux personnes avec lesquelles vous travaillez? Encore une fois, il n'y a pas de réponse absolue.
Voici mon avis.
Avoir une fonction globale return_cards ne semble pas raisonnable. Vous mélangez deux types de philosophie, la programmation procédurale et la programmation orientée objet, tout en fournissant des indices et des limites très limités pour expliquer pourquoi cela se passe de cette façon.
ACTUELLEMENT, je pense que return_cards () ne pouvait localiser que dans la classe Game . Étant donné que Deck et Player peuvent être considérés comme l'état du Game . Alors laissez la méthode du Jeu changer le statut du Jeu, c'est parfaitement compréhensible.
Bien sûr, vous pouvez faire quelque chose comme ceci:
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]
mais je ne pense pas que ce soit actuellement nécessaire. Quand sera-t-il? Par exemple, lorsque certains autres modules, tels que l'interface utilisateur ou une procédure d'impression de texte, dépendent de votre objet Player , vous devez les avertir lorsque les joueurs retournent leur carte. Ensuite, il est raisonnable de créer quelque chose comme Player.give_all_cards_back () et d'encapsuler toutes les procédures associées ensemble.
Voici un exemple de code de Fluent Python de Ramalho (O'Reilly), montrant comment créer une classe Deck , pour votre référence.
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)
Les classes Card, Deck et Player semblent raisonnables, même s'il manque peut-être certains attributs / méthodes dont vous aurez besoin. Le jeu semble éteint. Un jeu a-t-il besoin à la fois d'un deck et de cartes? Devrait-il y avoir un concept de «virage»? Si oui, comment pouvez-vous modéliser cela? Chaque joueur doit-il recevoir les mêmes cartes à chaque fois que le jeu commence? J'espère que quelques questions pour vous mettre sur la bonne voie.
Je suppose que vous utilisez 1 pour un Ace, 11 pour Jack, etc.?