0

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()
HWG
  • 13
  • 1
  • 4
  • I think you should consider carefully your representation - values are a mixture of `int` and `str` and suits by just `str`... And actually, the actual instances themselves have attributes which are `ints` that correspond to indices in the `Card` class-level variables. Do you see any problems? This is sort of a canonical use case for `enum` – juanpa.arrivillaga Feb 22 '17 at 17:57
  • Can I in some way use the value and suit indexes in the lists (values and suits) to compare two cards? – HWG Feb 22 '17 at 18:01
  • Yeah, sure. Seems messy to me. I would definitely use `enum`s. Your code will be much more readable and nice and tidy. But sure, you can use indices. – juanpa.arrivillaga Feb 22 '17 at 18:03

4 Answers4

1

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, __class__):
            return self.value < other.value
        return NotImplemented
    def __eq__(self, other):
        if isinstance(other, __class__):
            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

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

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, ...].

0

An enumeration (https://docs.python.org/3.5/library/enum.html) would be appropriate. For rich comparisons (and ordering) you should also consider implementing some or all of the __eq__, __ne__, __lt__, __le__, __gt__, __ge__ methods (from https://docs.python.org/3/reference/datamodel.html) on the Card class.

FluxIX
  • 325
  • 1
  • 2
  • 13
0

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
  • Yeah, but now `card1.value > 48` will actually be evaluated. Not good. This is a prototypical case for `enum`. And your `compare` function is very much not what you would do if you already have a class, instead, you would implement the rich comparison operators. – juanpa.arrivillaga Feb 22 '17 at 21:49