I am trying to make a text based object oriented card game. Two players draw a card each from a deck of cards, and the player with the strongest card wins. I have four classes for this game: Card, Deck, Player, Game. My question is: How can i compare each players card to each other and determine the strongest one. All other suggestions about the code are welcome. Best regards HWG.
Here is my code:
Card
class Card(): values = [None, None, 2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King", "Ace"] suits = ["hearts", "spades", "diamond", "clubs"] def __init__(self, value, suit): self.value = value self.suit = suit def __repr__(self): return str(self.values[self.value]) + " of " + str(self.suits[self.suit])
Deck
from random import shuffle from card import Card class Deck(): def __init__(self): self.cards = [] for v in range(2, 15): for s in range(4): self.cards.append(Card(v, s)) shuffle(self.cards)
Player
from deck import Deck class Player(): def __init__(self, name): self.name = name self.card = None self.wins = 0
Game
from player import Player from deck import Deck import getch class Game(): def __init__(self): player1_name = input("Player One Name: ") player2_name = input("Player Two Name: ") self.deck = Deck() self.player1 = Player(player1_name) self.player2 = Player(player2_name) self.cards = self.deck.cards def game_loop(self): while len(self.cards) >= 2: print("\nPress enter to draw") getch.getch() player1_card = self.cards.pop() player2_card = self.cards.pop()
Here is a sketch of an approach. You can easily combine this with your own approach, the biggest change being for the Card
class. Here, I've used namedtuple
to make a Card
class, but your current class can simply wrap a tuple
value:
import enum from functools import total_ordering from collections import namedtuple @total_ordering class OrderedEnum(enum.Enum): def __lt__(self, other): if isinstance(other, type(self)): return self.value < other.value return NotImplemented Rank = OrderedEnum('Rank', ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'jack', 'queen','king', 'ace']) Suit = OrderedEnum('Suit', ['clubs', 'diamonds', 'hearts', 'spades']) Card = namedtuple('Card', ['rank', 'suit']) c1 = Card(Rank.four, Suit.clubs) c2 = Card(Rank.four, Suit.spades) c3 = Card(Rank.ace, Suit.diamonds)
Now, in action:
>>> c1 Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>) >>> c2 Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>) >>> c1 < c2 True >>> c1 > c3 False
Tuple sorting is lexicographic! Nice!
>>> hand = [c2, c1, c3] >>> hand [Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>), Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>), Card(rank=<Rank.ace: 13>, suit=<Suit.diamonds: 2>)] >>> sorted(hand) [Card(rank=<Rank.four: 4>, suit=<Suit.clubs: 1>), Card(rank=<Rank.four: 4>, suit=<Suit.spades: 4>), Card(rank=<Rank.ace: 13>, suit=<Suit.diamonds: 2>)] >>>
Note, I've used the total_ordering
decorator, which is simply a shortcut, and indeed, I think it might be better to simply do the whole class by hand. Here's a recipe.
EDIT So, to elaborate, here is how I would implement your Card
and Deck
classes. Notice how much more readable your code becomes when you use the enum
and namedtuple
.
import enum from functools import total_ordering from collections import namedtuple from random import shuffle @total_ordering class OrderedEnum(enum.Enum): def __lt__(self, other): if isinstance(other, type(self)): return self.value < other.value return NotImplemented Rank = OrderedEnum('Rank', ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'jack', 'queen','king', 'ace']) Suit = OrderedEnum('Suit', ['clubs', 'diamonds', 'hearts', 'spades']) CardValue = namedtuple('CardValue', ['rank', 'suit']) @total_ordering class Card(object): def __init__(self, rank, suit): self.value = CardValue(rank, suit) def __repr__(self): return "Card({:s}, {:s})".format(self.value.rank, self.value.suit) def __lt__(self, other): if isinstance(other, type(self)): return self.value < other.value return NotImplemented def __eq__(self, other): if isinstance(other, type(self)): return self.value == other.value return NotImplemented class Deck(object): def __init__(self): self.cards = [] for rank in Rank: for suit in Suit: self.cards.append(Card(rank, suit)) shuffle(self.cards)
Now, in action:
>>> deck = Deck() >>> c1 = deck.cards.pop() >>> c2 = deck.cards.pop() >>> c1 Card(Rank.queen, Suit.hearts) >>> c2 Card(Rank.king, Suit.clubs) >>> c1 == c2 False >>> c1 > c2 False >>> c1 < c2 True >>> c1.value CardValue(rank=<Rank.queen: 11>, suit=<Suit.hearts: 3>) >>> c2.value CardValue(rank=<Rank.king: 12>, suit=<Suit.clubs: 1>)
Also, notice that __repr__
should try to represent the object, if you want a pretty message, use __str__
. See this question
You could implement operators for your Card class __gt__(), __lt__(),
etc ...
than you can use a number of standard library functions like max() to determine the higher value card or deck and could even use sort() to simply sort a 'hand' for example a list [Card, Card, ...].
I would recommend you store the value of each card as an int, so you can compare them, and not to use strings such as "King" or "Ace". You can do this and change repr() to print a human readable version using those strings.
The Card class could look like this:
class Card(object): suits = ["Clubs", "Diamonds", "Hearts", "Spades"] #list of suits values = [None, "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"] #list of card values def __init__(self, suit= 0, value= 2): """ Initializes card :param suit: Suit of card int value 0-3 :param value: Value of card int value 0-13 """ self.suit = suit self.value = value def __str__(self): """ Returns a readable format of the card """ return "%s of %s" %(Card.values[self.value], Card.suits[self.suit])
Notice how the value of the card is stored as an int all the time.
In the Game class you could have a function that compares two cards, I'm not sure how you want to do this but it could look something like this:
def compare(card1, card2): """ Compares the value of two cards and returns the greater of the two :param card1: A card object :param card2: A second card object :return: The higher value card, if tie returns 0 """ if card1.value > card2.value: return card1 elif card2.value == card1.value: return 0 else: return card2