[Edu-sig] more card play

kirby urner kirby.urner at gmail.com
Mon Nov 2 04:55:42 CET 2009


I'm becoming more enamored of the idea of using playing cards as a
standard feature in my Python pedagogy and andragogy (means teaching
adults).  Not only do we have the standard deck, but also Tarot which
could get us more into text files, string substitution
(string.Template) and so forth.

Cards have all the elements

Mathematically, a Deck suggests the difference between Cardinality
(yes, a silly pun) and Ordinality.  You might imagine a deck in which
you can't decipher the cards, don't know their "face value", and so
have no clear idea of their ranking (ordinality).

On the other hand, you know which cards are the same and which are
different across multiple decks (le'ts just say), which is the meaning
of "cardinality" (difference without any implied ordering).

Midhat Gazele dwells on this difference some in his book 'Number'.
You might have a set of objects, say stones or wasp specimens, and a
way of cataloging them that doesn't implement > or < or even ==.
There is only the Python "is" and "is not" for determining of two
objects have the same identity or not.  License plates on cars, proper
names, have this purpose of distinguishing.

However, very quickly just about any set beyond a certain size needs
an ordering, perhaps simply alphabetical, so that one might flip
through a lookup table in a hurry and get to the desired object.
People used to use ledgers, thick books, for such cataloging.

This is the beginning of data structuring or data structures.  The
idea of "ordering" (ordinality) is very closely associated with that
of cardinality.

Anyway, over on Pyfora I was noticing a member suggesting reversing a
string might be best accomplished by thestring[::-1], i.e. extended
slicing with from the end to the beginning step with a step of -1.
However PEP 322 suggests this will be inefficient compared a a built
in function introduced in 2.4:  reversed.

According to the PEP, all reversed needs is for the consumed object to
support __getitem__ and __len__.  If those two are present, the
function will do the rest, and return an iterator object in which the
contents of a sequence are iterated over in reverse order.

>>> a = 'the rain in spain stays mainly in the plain'
>>> ''.join(reversed(a))
'nialp eht ni ylniam syats niaps ni niar eht'

So for our Deck to be reversible by means of this built in function,
the only methods we need to implement are these two, __getitem__ and
__len__.  I do this below, plus make shuffling the deck upon
instantiation an option, not de rigueur (not mandatory).  This makes
it easier to see what reverse order is like.

Note that the returned iterable is not itself a Deck, nor is it
subscriptable, as the whole point of an iterable is it returns its
contents "just in time" as you iterate over it.  The cards are not
"already present" in reversed order, are simply returned in reverse
order.

Of course you can force the iterable to dump all its contents by
coercing it into a list.  I do this as a part of the clone_deck
method.  Playing Game of War with clone decks, one the reverse of the
other, results in a draw if always dealing from the top (now the
default, yet still optional).

The code below is just my Game of War again, with a few wrinkles.
I've upgraded all string printing to use the
print(thestring.format(args)) approach, versus the old percent sign
string substitution codes.  Other small improvements.

>>> from sillygame import Deck
>>> d = Deck(10)
>>> newd = d.clone_deck()
>>> newd[0] is d[0]
True
>>> id(newd[0])
22884944
>>> id(d[0])
22884944
>>> d.shuffle()
>>> str(d)
"['7 of Diamonds', 'Jack of Spades', 'Queen of Clubs', 'King of
Spades', '5 of Clubs', '3 of Spades', '6 of Hearts', 'Ace of Clubs',
'2 of Hearts', '9 of Spades']"
>>> str(newd)
"['2 of Hearts', 'Queen of Clubs', '6 of Hearts', '9 of Spades', '5 of
Clubs', '7 of Diamonds', 'Ace of Clubs', '3 of Spades', 'Jack of
Spades', 'King of Spades']"
>>> d[0]
Card(Diamonds, ('7', 7))
>>> newd[5]
Card(Diamonds, ('7', 7))
>>> d[0] == newd[5]
True
>>> d[0] is newd[5]
True

Kirby

For further reading:
http://www.python.org/dev/peps/pep-0322/


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({0}, {1})".format(self.suit, (self.rank, self.value))

    def __str__(self):
        return "{0} of {1}".format(self.rank, self.suit)

class Deck:

    def __init__(self, numcards = 52, shuffle = True):
        # build a complete deck then slice
        try:
            assert 0 < numcards <= 52
        except AssertionError:
            print("Defaulting to 52 cards")
            numcards = 52

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

    def __getitem__(self, index):
        return self.cards[index]

    def shuffle(self):
        shuffle(self.cards)

    def spit_card(self, top=True):
        try:
            assert self.numcards > 0
        except AssertionError:
            raise Exception("Out of cards!")

        if top:
            some_card = self.cards.pop(0)
        else:
            some_card = self.cards.pop( randint( 0, self.numcards - 1 ))

        self.numcards = len(self.cards)
        return some_card

    def clone_deck(self, reverse=False):
        newdeck = Deck(numcards=1)
        newdeck.numcards = self.numcards
        if reverse:
            newdeck.cards = list(reversed(self.cards))
        else:
            newdeck.cards = self.cards[:]
        return newdeck

    def __repr__(self):
        return "Deck({0})".format(self.numcards)

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

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

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

def game_of_war():
    deckA = Deck(10)
    # deckB = Deck(10)
    deckB = deckA.clone_deck(reverse=True) # play a reversed clone
    PlayerA_score = 0
    PlayerB_score = 0

    try:
        assert deckA.numcards == deckB.numcards
    except AssertionError:
        raise Exception("Decks don't have same number of cards")

    for i in range(deckA.numcards):
        playerA_card = deckA.spit_card(top=False) # deal from anywhere
        playerB_card = deckB.spit_card(top=False)

        if playerA_card > playerB_card:
            PlayerA_score += 1
            print("A's {0} beats B's {1}".format(playerA_card, playerB_card))
        if playerA_card < playerB_card:
            PlayerB_score += 1
            print("B's {0} beats A's {1}".format(playerB_card, playerA_card))
        if playerA_card == playerB_card:
            print("B's {0} matches A's {1}".format(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()


More information about the Edu-sig mailing list