Saturday, August 9, 2014

War (Python)

Realized this week I knew some tricks to make a quick version of the card game War in Python. For the rules, I looked to Hoyle's Rules of Games (Second Revised Edition). I thought I knew the rules, but the ones I used led to unending games. Summarized:
  • A randomized deck is split between two players
  • The players draw the top card from their decks and add them to the pot
  • If the drawn cards are equal, the game goes into War
    • Players each draw three more cards and add them to the pot
    • Fourth cards are drawn
    • If these are equal, the war continues; these and three other cards go into the pot
  • When the cards aren't equal, the player with the highest wins and adds the pot to his deck
  • Hoyle also allows for "The first to win 3 wars wins the game"
I didn't see anything about if Ace is the highest or lowest, so I left that to war().

This is an exercise in using __x__() methods. I heard C++ let you rewrite the meanings of operators and recently found out Python does too. I use it in __gt__(self, other), which allows me to compare one card's rank to another with >. I'm pleased with how Pythonic the code looks for Card, Deck, and Player. They could all be improved, but war() needs the most work.

import random as r

war = False 

class Card:
    '''Cards have numbers as rank and strings as suits'''
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
    
    def __gt__(self, other):
        return self.rank > other.rank

    def __str__(self):
        face = ["J","Q","K"]
        if self.rank == 1 or self.rank == 14:
            rank = "A"    # If highA, then ace==14; else, ace==1
        elif self.rank < 10:
            rank = self.rank
        else:
            rank = face[self.rank-11] 
        return str(tuple( (rank, self.suit) ))
    
class Deck:
    '''A deck is any set of cards, owned by players or games''''
    def __init__(self, n=0, suits=None, highA=True):
       span = range(2,n+2) if highA else range(1,n+1)
       self.deck = [Card(i,j) for i in span for j in suits]

    # reveal deck, good for debugging but not used in game
    def __str__(self):
        return ','.join([str(card) for card in self.deck])

    # redefine += for easy appending
    def __iadd__(self, card):
        self.deck.append(card)
        return self

    def __next__(self):
        return self.deck.pop(0)
    
    def __len__(self):
        return len(self.deck)

    def shuffle(self):        
        r.shuffle(self.deck)

    def deal(self, hands):
        self.shuffle()
        while self.deck:
            for hand in hands:
                hand += self.__next__()

    # append cardlist to this deck
    def gets(self, cardlist):
        while cardlist:
            self += cardlist.pop(0)

class Player:
    '''Player #ID plays cards and wins wars'''    
    def __init__(self, ID):
        self.ID = ID
        self.hand = Deck()
        self.wars_won = 1

    def __len__(self):
        return self.wars_won

    def wins(self, potlist):
        global war
        print self.ID, "gets pot"
        self.hand.gets(potlist)
        # "continued" wars count as one win
        if war:
            self.wars_won += 1
            war = False

    def draw(self):
        return self.hand.__next__()

def war(war_ante=3, wins=3, highA=True):
    global war
    pot = Deck()
    p1,p2 = Player(1),Player(2)
    main = Deck(n=13, suits=['C','S','H','D'], highA=highA)
    main.deal([p1.hand,p2.hand])
    
    while len(p1.hand) and len(p2.hand) and len(p1)<wins and len(p2)<wins:
        p1card, p2card = p1.draw(), p2.draw()
        pot.gets([p1card,p2card])
        print "p1 plays", p1card, "\np2 plays", p2card
        if p1card > p2card:
            p1.wins(pot.deck)
        elif p2card > p1card:
            p2.wins(pot.deck)
        else:
            print "WAR"
            war = True
            for i in range(war_ante):                
                pot.gets([p1.draw(),p2.draw()])
        print "p1=",len(p1.hand),"\np2=",len(p2.hand),"\n"
    if len(p1.hand) and len(p2.hand):
        print "p1" if len(p1) > len(p2) else "p2", "wins"
    else:
        print "p1" if len(p1.hand) > 0 else "p2", "wins"

war()

No comments:

Post a Comment