[Edu-sig] more fun with cards

kirby urner kirby.urner at gmail.com
Sat Oct 17 20:01:45 CEST 2009


On Wed, Oct 7, 2009 at 4:46 PM, kirby urner <kirby.urner at gmail.com> wrote:
> The code below isn't stellar, but has some pedagogical value nevertheless.
>

Indeed the code was far from stellar, however it gave some direction.

The version below is in Python 3.1 and demonstrates primitive
conditionals and exception handling, too often postponed because of
that "phylogeny problem" (older languages just crashed or tried to
catch everything at compile time, so we didn't see try/except syntax
until relatively newer languages).

In the Game of War (a silly game), each player gets a deck.  Our Deck
class is able to hand back a smaller sample from an already-shuffled
deck of 52 (no jokers).  They each then turn over the top card and see
which has the higher value, scoring accordingly.  Our Deck class spits
a random card from anywhere in the deck, which would be another way to
play (as if fanning the cards and picking any at will).

>>>
A's King of Diamonds beats B's Ace of Hearts
B's 9 of Spades beats A's 5 of Clubs
A's 10 of Clubs beats B's 2 of Hearts
B's Ace of Diamonds matches A's Ace of Hearts
B's 7 of Clubs beats A's 5 of Hearts
B's 10 of Diamonds beats A's 9 of Hearts
B's King of Hearts beats A's 6 of Spades
A's King of Hearts beats B's Jack of Hearts
A's 9 of Diamonds beats B's 4 of Clubs
B's King of Spades beats A's 2 of Diamonds
Game Over:  B wins
>>>

What's demonstrated in the code, besides exception handling and flow
(in a somewhat pedantic and repetitive style) is (a) use of shuffle
and randint from random module and (b) __rib__ syntax for > < ==.  The
Card type gets the logic for comparing any two card instances.  The
Deck is innocent of such logic, just needs to manage "how many" and
"which cards".

from random import shuffle, randint

thesuits = ['Hearts','Diamonds','Clubs','Spades']
theranks = ['Ace'] + [str(v) for v in range(2,11)] + ['Jack','Queen','King']
rank_values = list(zip(theranks, range(1,14)))

class Card:

    def __init__(self, suit, rank_value ):
        self.suit = suit
        self.rank = rank_value[0]
        self.value = rank_value[1]

    def __lt__(self, other):
        if self.value < other.value:
            return True
        else:
            return False

    def __gt__(self, other):
        if self.value > other.value:
            return True
        else:
            return False

    def __eq__(self, other):
        if self.value == other.value:
            return True
        else:
            return False

    def __repr__(self):
        return "Card(%s, %s)" % (self.suit, (self.rank, self.value))

    def __str__(self):
        return "%s of %s" % (self.rank, self.suit)

class Deck:

    def __init__(self, numcards = 52):
        # build a complete deck then slice
        try:
            assert 0 < numcards <= 52
        except:
            raise ValueError()

        self.numcards = numcards
        self.cards = [Card(suit, rank_value)
                      for rank_value in rank_values
                      for suit in thesuits ]
        shuffle(self.cards)
        self.cards = self.cards[ : self.numcards]

    def spit_card(self):
        try:
            assert self.numcards > 0
        except:
            raise AssertionError()

        some_card = self.cards.pop( randint( 0, self.numcards - 1 ))
        self.numcards = len(self.cards)
        return some_card

    def __repr__(self):
        return "Deck(%s)" % self.numcards

    def __str__(self):
        return str([str(card) for card in self.cards])


def test():
    thedeck = Deck()
    print (str(thedeck))

def game_of_war():
    deckA = Deck(10)
    deckB = Deck(10)
    PlayerA_score = 0
    PlayerB_score = 0

    try:
        assert deckA.numcards == deckB.numcards
    except:
        raise AssertionError()

    for i in range(deckA.numcards):
        playerA_card = deckA.spit_card()
        playerB_card = deckB.spit_card()

        if playerA_card > playerB_card:
            PlayerA_score += 1
            print("A's %s beats B's %s" % (playerA_card,
playerB_card))
        if playerA_card < playerB_card:
            PlayerB_score += 1
            print("B's %s beats A's %s" % (playerB_card, playerA_card))
        if playerA_card == playerB_card:
            print("B's %s matches A's %s" % (playerB_card, playerA_card))

    if PlayerA_score > PlayerB_score:
        print("Game Over:  A wins")
    if PlayerA_score < PlayerB_score:
        print("Game Over:  B wins")
    if PlayerA_score == PlayerB_score:
        print("Game Over:  it's a draw!")

if __name__ == '__main__':
    # test()
    game_of_war()

A subtle error I was having, which took me back to Steve Holden's
workshop at Pycon 2009 (Chicago) was this line below:

rank_values = list(zip(theranks, range(1,14)))

At first I just returned the zip without making it a list.  But in 3.1
that's a "zip object" and gets exhausted through iteration.  In
creating a first deck object from this global variable, it'd exhaust
the zip object, leaving nothing for the next deck to iterate against
upon instantiation.  Coercing to a list forces a static object on the
heap, suitable for continued reiteration.

I should give more explicit error messages and have more interesting
tests.  This is still in need of some spit and polish, but is already
suitable as a lesson plan for gnu math teachers.

Kirby


More information about the Edu-sig mailing list