GufyGames Tutorial


This tutorial is an intro to get you started building games and adding your own ai to play against. This tutorial assumes that you have atleast some knowledge of programming in python and using the python interpreter for development. If you don't have this please see Prerequisites bellow for links to getting more info on learning python. This tutorial will take you through setting up your deck, setting up your game to be loaded through GufyGames, adding game rules and enforcing them, creating a simple ai/bot/computer player, and finally adding it to your game.As the API grows, this document should also.


TOC

  • Prerequisites
  • Creating a Custom Deck
  • Creating a Game
  • Creating a Bot

Prerequisites


Creating a Custom Deck

  • The Card
  • The Deck
  • Creating The Deck
  • Using the deck

The Card

Card API

The card is a custom class that contains two properties, suit and face. The suit is the suit/color of the card and the face is the value/action of the card. A suit is a tuple of the suits name, in the order of short form, long form

The Card will have a image property for the GUI system added to it.

 suit = ('H', 'Hearts')

The face is a set, similar to suit, but adds an integer for the value of the card, in the order of short form, long form, value

 face = ('A', 'Ace', 11)

To create a card we initiate it with the suit and face. The class provides simple methods for accessing the different locations of the card.

Card().getCardFace() and Card().getCardSuit() both take an index and return the value of tuple's at the index, so you can use a list, set or tuple and access the locations with the index. Card().getCardValue() always returns Card().face[2].

pp() is just a convenience method for returning '%s' %(card.getCardSuit(1), card.getCardFace())

 
 # import Card
 from gameLib.card import Card

 # set the suit and the face
 face = ('A', 'Ace', 11)

 suit = ('H', 'Heart')
 
 card = Card(face, suit)

 card.suit
 ('H', 'Heart')

 card.face('A', 'Ace', 11)

 card.getCardFace()
 'A'
 
 card.getCardSuit(1)
 'Hearts'

 card
 '<Card: Hearts A>'

 card.pp()
 Hearts A

 card.getCardValue()
 11

 card.getCardFace(2)
 11

The Deck

Deck API

The deck as you imagine is like a real deck a collection of cards. The Deck class contains the deck, a discard pile, a face card (the top of the discard pile players can see), the delt card, which is the current card being delt to a player and a used deck. The deck and used deck are dictionaries, the key is an int, the location in the deck that the card is in, the value is a card object.

How long deckUsed and deltCard (as a global) will be around is questionable.

Creating New Card Faces

Since we can have multiple types of cards in a deck, the default deck is a the classic french suit. This is good for most card games, but there are many games that use other cards. For example Uno. Uno use a deck with colors and different faces. So we create our suits and faces. I prefer using tuples, but this can be any iterable you prefer.

 
 from gameLib.deck import Deck

 UNO_SUITS = (
         ('B', 'Blue'),
         ('G', 'Green'),
         ('R', 'Red'),
         ('Y', 'Yellow'),
        )

 UNO_FACES = (
          ('0', 'Zero', 0),
          ('1', 'One', 1),
          ('2', 'Two', 2),
          ('3', 'Three', 3),
          ('4', 'Four', 4),
          ('5', 'Five', 5),
          ('6', 'Six', 6),
          ('7', 'Seven', 7),
          ('8', 'Eight', 8),
          ('9', 'Nine', 9),
          ('R', 'Reverse', 20),
          ('S', 'Skip', 20),
          ('DT', 'Draw Two', 20),
         )

 WILDS = (
          ('W', 'Wild', 25),
          ('WD4', 'Wild Draw 4', 50),
        )

Creating a New Deck

Now we extend the basic Deck object by overriding Deck().createDeck(). First we set our suits and faces. Iter over them to create new card objects and insert them in order into the deck.

Either before you call Deck().createDeck() you should add the SUITS and FACES to the deck. Deck().setSuits(value) and Deck().setFaces(value) sets the SUITS and FACES tuples to the deck. Now we iter over Deck().suits and Deck.faces to create cards and add them to our deck. Now our deck should be created and in order.

splitDeck() is overriden because the current version in deck.py only handles a 52 card deck correctly till it is updated

 
 class UnoDeck(Deck):
   def __init__(self):
       Deck.__init__(self)

   def __str__(self):
       return '<Deck: Uno>'

   def createDeck(self):
       # set our faces and suits.
       self.setSuits(UNO_SUITS)
       self.setFaces(UNO_FACES)

       tmpdk = {}
       
       i = 0
       # create 2 of every card in each color except 0
       for face in self.faces:
           if face[2] != 0:
              for color in self.suits:
                  tmpdk[i] = Card(face, color)
                  i += 1
                  tmpdk[i] = Card(face, color)
                  1 += 1

       # create the 4 0 cards  
       for color in suits:
            tmpdk[i] = Card(FACES[0], color)
            i += 1

       # create the wilds
       # we may want to randomly insert these 
       # into the deck.
       for c in range(4):
            tmpdk[i] = Card(WILDS[0], (None, None))
            i += 1
            tmpdk[i] = Card(WILDS[1], (None, None))
            1 += 1
        
       self.deck = tmpdk

   # this is redefined for now, till deck.py is updated with this code

   def splitDeck(self, cards):
       sp = cards/4
        
       suits1 = cards[:sp]
       suits2 = cards[sp:(sp+sp)]
       suits3 = cards[(sp+sp):(sp+sp+sp)]
       suits4 = cards[(sp+sp+sp):(sp+sp+sp+sp)]

       suits2.reverse()
       suits4.reverse()

       d1 = suit1 + suit2
       d2 = suit4 + suit3

       return (d1, d2) 

Using The Deck

First initialize the deck, then shuffle. Deck().shuffle() calls Deck().createDeck() and Deck().newShuffle() which uses Deck().splitDeck() before it shuffles the the halves of the deck returned by Deck().splitDeck().

Deck().newShuffle() is called 7 times. newShuffle uses the Fisher-Yates shuffle algorithm to shuffle the decks keys.

 
   dk = UnoDeck()
   dk.shuffle()
   for card in dk.deck.values():
       print card.pp()

   Blue 2
   Red DT
   etc....

   dk.newShuffle()  # shuffle the deck once.

Using the deck is rather simple and straight forward, just a few methods for drawing, discarding and managing the face card.

 
  dk.drawOne()
  <Card:  WD4>'

  dk.deckUsed
  {0: '<Card:WD4>'}

  for card in dk.drawMoreThanOne(4):
      card.pp()

  Blue 4
  Green R
  Green 1
  Yellow 0

 dk.setFaceCard(dk.drawOne())
 
 dk.faceCard
 '<Card: Red 9>'

 dk.discard(dk.drawOne())

 dk.discardPile
 ['<Card: Red 9>', '<Card: Blue 5>']


 dk.faceCard
 '<Card: Blue 5>'

Creating a Game

  • The Player
  • Initializing a Game
  • Starting a Game
  • Implementing Game Rules
  • Ending the Game
  • Launching the game with GufyGames()

The Player

Player API

The Player class is the players representation with in the game. Players when initiated only needs a name. Player properties include the name and 'up' (marks if the player is the current player). handValue, the value of the hand, which has Player().setHandValue() and Player().getHandValue() methods to change and retrieve the. We can check the player status with Player().isUp(), it returns True if "Player().up" is set to anything but False or 0.

Player().hand is a list of cards that are in the players hand.

Just like Card this will get an image property also for the path to a players icon/avatar/image.

     from gameLib.player import Player

     player1 = Player("Player 1")

     player1
     '<Player: Player 1>'

     player1.name
     'Player 1'
 
     player1.isUp()
     False

     player1.setIsUp(True)
     player1.isUp()
     True

     player1.setHandValue(13)
     player1.getHandValue()
     13

     type(player1.hand)
     <type 'list'>

The players hand is a list of cards. We provide functions for drawing, discarding and organizing the hand by suit. The Game class is actually mostly a wrapper for the Player and Deck interaction, while enforcing the game rules. Here is a sample of using the player class.

You should always pass the game a list of players, usually you'll want to do something

 
 from gameLib.player import Player
 from gameLib.deck import Deck

 dk = Deck()
 dk.shuffle()

 p1 = Player("Player 1")
 p2 = Player("Player 2")

 players = [p1, p2]

 # Deal our cards
 for c in range(5):
    for player in players:
        player.draw(dk.drawOne())

 p1.hand
 ['<Card: Hearts A>',
 '<Card: Hearts 4>',
 '<Card: Clubs 10>',
 '<Card: Spades 8>',
 '<Card: Hearts 6>']

 len(p1.hand)
 5

 p1.discard(p1.getCard(3))

 p1.hand
 ['<Card: Hearts A>',
 '<Card: Hearts 4>',
 '<Card: Clubs 10>',
 '<Card: Hearts 6>']

 p1.draw(dk.drawOne())
 p1.hand
 ['<Card: Hearts A>',
 '<Card: Hearts 4>',
 '<Card: Clubs 10>',
 '<Card: Hearts 6>',
 '<Card: Diamonds Q>']

 p1.updateCardSuits()

 p1.spades
 []

 p1.hearts
 ['<Card: Hearts A>', '<Card: Hearts 4>', '<Card: Hearts 6>']

 p1.clearSuits()
 p1.hearts
 []


 # sorting a hand by value
 p1.hand.sort(lambda, x, y: cmp(x.getCardValue(), y.getCardValue()))

 p1.hand
 ['<Card: Hearts 4>',
 '<Card: Hearts 6>',
 '<Card: Clubs 10>',
 '<Card: Diamonds Q>',
 '<Card: Hearts A>']

 p1.hand.reverse()
 p1.hand
 ['<Card: Hearts A>',
 '<Card: Diamonds Q>',
 '<Card: Clubs 10>',
 '<Card: Hearts 6>',
 '<Card: Hearts 4>']

 p1.hand[0].pp()
 Hearts A

Initializing a Game

Game API

The Game requires a few things when it is started, a list of Player objects, a Deck and Bot. You should take care in initializing with your game this way.

The Game().name property should be defined to your games name, this is a must to over ride.

The Game class also includes a property called Game().currentPlayer which holds the player whos turn it currently is. This property should be used in your game when interacting with the player who's turn it currently is.

The bot option on intialization is either a valid BotPlayer() or False

 
  from gameLib.game import Game

  class GameUno(Game):
       
        def __init__(self, players, playDeck, bot, debug):
            Game.__init__(self, players, playDeck, bot, debug)
            self.name = "Uno"
            # any special game properties, 
            self.color = ''  # current color player can play.
            self.colorlist = ['Blue', 'Red', 'Green', 'Yellow']   # for ease


        def faceCard(self):
            self.color = self.playDeck.faceCard.getCardSuit()  # set the color

            # super(GameUno, self).faceCard()
            
            # This should be in the parent Game() class, super() should return this.
            return self.playDeck.faceCard      

Starting a Game

For the launcher to be able to start the game, we need to override 2 methods. Game().startGame() and Game().loop(). Game().startGame() initializes the game, creates the deck and deals to the players.

Game().startGame(count) takes an integer value that will deal count cards to the players. i.e.: Game().startGame(7) deals 7 cards to each player. this is changing, you must define Game().startGame() with Game().deal(count) to deal count cards.

Game().gameOn is a boolean property that is used to control our main loop, first we it to True or 1, start our loop then when we want to end the game set it to False or 0

   
   self.gameOn = True

   while self.gameOn:  
      for i in range(50): 
          print "Starting GufyGames" 
          if i%3 == 0:
             self.gameOn = False

In Game().loop() we start our turns, first we make sure the player isn't the bot player, other wise we allow the human player to play. Starting the players turn describes the choices a player gets. In this case he can choose to either play a card from his hand or draw a card. If the player draws, then he is faced with another set of choices, either to play or pass his turn.

Before we continue, I want to point out some common knowledge shared between the classes.

ne

  • All card properties are initialized to blankCard = Card(None, None) broken pp() on wild and wd4, suit is None and will return 'Blank Card'
  • blankCard.pp() returns 'Blank Card'
  • self.currentPlayer is the player's turn it is.
  • Game().getHighestValue(val1, val2) and Game().getLowestValue(val1, val2) takes in integers and returns integers, unless the vals are equal, then it returns 'Equal'
  • Game().getHand() returns the hand in a string with this form: '1. Blue 4 , 2. Green R, 3. Red 2' with these three cards in hand. broken with wd4 and wilds
  • Game().getNextPlayer() will return the next player in order (makes a check to cycle through the list).

Game().currentPlayer has Game().setCurrentPlayer(player) that should be used instead of self.currentPlayer = player because setCurrentPlayer will update Player().up with Player().setUp for both players. Also use Game().getCurrentPlayer() It'll find the right player if self.currentPlayer isn't set.

 
         ... continue GameUno() ...

        def startGame(self):
            # deal the players 7 cards
            self.playDeck.shuffle()              # create and shuffle our cards
            self.deal(self.order, 7)             # need to add an option for seting the facecard or not in gameLib.game.Game().deal() It does by deafault.
            # self.playDeck.discard(self.playDeck.drawOne)  # set the first face card.  Deal does this but it needs to change to here.

        def loop(self):
            self.gameOn = True                  # start our game

            # check if we have a face card, if not set it.
            if self.color == '' or self.playDeck.faceCard.pp() == 'Blank Card':
                      self.playDeck.discard(self.playDeck.drawOne())

            while self.gameOn:                  # enter our main loop


                  # check if it's the ai's turn and let it play it
                  if self.bot and self.currentPlayer.name == self.bot.name:      
                      self.currentPlayer.playTurn()            
  
                  # or let human players play
                  else:
                      
                       print "%s it is your turn.\nThe face card is %s" %(self.currentPlayer.name, self.playDeck.faceCard.pp())

                       # Let the player play :)
                       self.playersStart()
                   
                   # sets the next player
                   np = self.getNextPlayer()
                   self.setCurrentPlayer(np)

        def playersStart(self):
            # show the player thier hand
            print self.getHand(self.currentPlayer)     

            # ask the player to play or draw
            choice = raw_input("Please choose: play a card or draw:"
               
            if choice == 'play':
               self.chooseCard()

            elif choice == 'draw':
               # call drawTurn to allow the player to pass or play
               self.drawTurn()

            else:
               print "Invalid choice, Please choose, play or draw Thanks."
               self.playersStart()
                                           
        def drawTurn(self):
            """
                if the player chooses to draw instead of play, this is called
            """
            # draw and show the player the card
            card = self.playDeck.drawOne()
            print "%s, you drew %s" %(self.currentPlayer.name, card.pp())
           
            # ask the player what they wanna do.
            print self.getHand(self.currentPlayer)
            c = raw_input("Please choose to play or pass:")
            
            # handle the user input      
            if c == "play":
               self.chooseCard()
            elif c == "pass":
               return
            else:
               print "Invalid choice, Please choose to play or pass."
               self.drawTurn()


        def chooseCard(self):
            """
               Make sure the player has that card to play
            """
            c = raw_input("Play a card from your hand [1-%s]" %(len(self.hand))
            try:
                card = self.currentPlayer.hand[c-1]
                self.playCard(card)  
            except:
                print "Invalid card, please choose again"
                print self.getHand(self.currentPlayer)
                self.chooseCard()

Implementing Game Rules

Now we've almost defined all the players choices, We only need to add two more methods, the method GameUno?().playCard() which enforces uno rules for what card a player can play and decides to call endGame() Which ends the game and calculates the points the player gets.

Here we are going to have the Player, Deck and Game classes interact.

 
      ... Continue GameUno() ...

      def playCard(self, card):
          """
             Start enforcing rules
          """
          fc = self.getCardFace()

          if card.getCardFace() == 'W':
             self.chooseColor()

          elif card.getFaceCard() == 'WD4':
             nextPlayer = self.getNextPlayer()
             self.drawFour(nextPlayer)
             print "%s draws four cards." % nextPlayer.name
             self.chooseColor()
             self.setCurrentPlayer(nextPlayer)

          elif card.getCardSuit() == self.color:
             self.checkCardAction(card)

          elif card.getCardFace() == fc:
             self.checkCardAction(card)

          else:
             print "Can't Play this card, choose another"
             self.chooseCard()


      def chooseColor(self):
          c = raw_input("%s please choose a color, Blue, Red, Yellow or Green:  ", % self.currentPlayer.name)
          try:
             self.colorlist.index(c)
          except:
             print "Invalid color, try again"
             self.chooseColor()
 
      def chooseAction(self, card):
          act = card.getFaceFace()
          # discard card
          self.playerDiscard(self.currentPlayer, card)
          
          # if it's a s or dt, we'll change
          # self.currentPlayer.  
          # just in case self.currentPlayer changes
          p = self.getCurrentPlayer()

          # perform action on the card, if it has one.
          if act == 'R':
             print "The order is reversed."
             self.order.reverse()
             return

          elif act == 'S':
             np = self.getNextPlayer()
             print "%s is skipped." % np.name
             self.setCurrentPlayer(np)

          elif act == 'DT':
             np = self.getNextPlayer()
             print "%s gets to draw two cards." % np.name
             self.drawTwo(np)
             self.setCurrentPlayer(np)

          # catch all
          else:
             pass
          
          # check for UNO!
          if len(p.hand) == 1:
             print "%s has UNO!" % p.name

          # or endGame()
          elif len(p.hand) <= 0:
             print "%s has won the game"
             self.endGame()

          # catch all
          else:
               pass
      # set up actions for special cards.
     
      def drawFour(self, player):
          self.playerDrawMoreThanOne(player, 4)

      def drawTwo(self, player)
          self.playerDrawMoreThanOne(player, 2)

Ending the Game

Ending the game, is simple just return the order and the players hand, while calculating up the card values

 
     ... Continue GameUno ....

    def endGame(self):
        """
           Calculate the hand total and grand total and print each players hand
        """
        tval = 0
        for player in self.displayOrder():
            val = 0
            tmpHand = ''
            if len(player.hand) >= 0:
                for card in player.hand:
                    val += card.getValue()
                    tmpHand += "%s " % card.pp()
                print "%s hand is %s for a total of %s points" %(player.name, tmpHand, val)
                tval += val
       print "Winner gets %s points" % tval
       
       # break the loop
       self.gameOn = False

Loading the Game with GufyGames

Now, to load the game with GufyGames you need to provide the launcher with a instance of your game. At the top of your GameUno? source file, add a main function that returns the game instance.

 
 from gameLib.game import Game

 def main(players, dk, bot, debug):
    # game = GameUno(*args, **kwargs)
    game = GameUno(players, dk, bot, debug)
    return game

 class GameUno(Game):
 ..... class definition ......

Now the game is ready to load, save the game to games/uno.py

     ./gufygames -1 gmike -2 gufy -g uno

Creating a Bot


The BotPlayer

BotPlayer API

The Player() object you should be familar with already from the Creating A Game. The BotPlayer extends the the Player class to implement different strategies that the bot player will play. Things the bot player should know are the rules to winning the game, rules to scoring, and how to play the turn. The bot player is initialized before the game, after the game is created the bot player will have the game added to it for access to the playDeck and any possible hand calculations the game provides.

 
     from gameLib.botplayer import BotPlayer
     bot = BotPlayer("GufyBot")

     bot.playTurn()

    # Extending a bot

    class BotPlayerUno(BotPlayer):
         def __init__(self, name, debug):
             BotPlayer.__init__(name, debug)
             self.faceCardFace = ''
        

---

The BotPlayers? Turn

The bots turn should be like any other players turn and go through a decision proccess, in Uno the first thing a player looks at is the face card and then looks at his hand and sees if he can play a card. if he can't find one the player must draw, then decide to play or pass.

 
       ...Continue BotPlayerUno()...

        def playTurn(self):
            self.getFaceCard()
            if self.faceCardFace == 'W' or self.faceCardFace == 'WD4':
               self.faceCardSuit = self.game.color
            self.checkCard()


        def clearSuits(self):
            """
                over ride for colors ;)
            """
            self.blue = []
            self.red = []
            self.green = []
            self.yellow = []

        def updateCardSuits(self, card=False):
            """
                same thing as above
            """
            if not card:
               for c in self.hand:
                   self.updateCardSuits(c)
            cardColor = card.getCardSuit()
            if cardColor == 'B':
               self.blue.append(card)
            elif cardColor == 'R':
               self.red.append(card)
            elif cardColor == 'Y':
               self.yellow.append(card)
            elif cardColor == 'G':
               self.green.append(card)
            else:
               pass

        def findLargestColor(self):
            """
               finds the color with the most cards.  Useful for color decisions
            """
            high = 0
            hv = self.game.getHighestValue(high, len(self.blue))
            if hv == 'Equal':
               hv = len(self.blue)
            hv = self.game.getHighestValue(hv, len(self.red))
            if hv == 'Equal':
               hv = len(self.red)
            hv = self.game.getHighestValue(hv, len(self.green))
            if hv == 'Equal':
               hv = len(self.green)
            hv = self.game.getHighestValue(hv, len(self.yellow))
            if hv == 'Equal':
               hv = len(self.yellow)

            if hv == len(self.blue):
               return 'Blue'
            elif hv == len(self.red):
               return 'Red'
            elif hv == len(self.yellow):
               return 'Yellow'
            elif hv == len(self.green):
               return 'Green'
            else:
               # catch all to return a random color :D
               import random
               c = random.randint(0, 3)
               return colorlist[c]
                  
        def getFaceCard(self)
            fc = self.game.faceCard()
            self.faceCardFace == fc.getCardFace()
            self.faceCardSuit == fc.getCardSuit()

        def checkCard(self, rec=0):
            """
               This is where the bot gets it's intelligence and strategy.
               This should be where you make it 'think'
            """
            # find cards it can play
            tmpHand = []
            for card in self.hand:
                if card.getCardSuit() == self.faceCardSuit:
                   tmpHand.append(card)
                elif card.getCardFace() == 'W' or card.getCardFace() == 'WD4':
                    tmpHand.append(card)
                else:
                     pass
           

            if len(tmpHand) == 0 and rec == 0:
                self.game.playerDrawOne()
                self.playCard(1)
            elif len(tmpHand) == 0 and rec == 1:
                return
            else:
                # sort by highest value, we should update this eventually to be smarter
                # this is where the bot player decides to play, we're going with the largest
                # card left of the cards he can play
                tmpHand.sort(lambda x, y: cmp(x.getCardValue(), y.getCardValue()))
                tmpHand.reverse()
                
                # play card with highest value
                c = tmpHand[0]
                self.game.playCard(c)

                # check to see if we played a wild to choose a color
                if c.getCardSuit() == 'W' or c.getCardSuit() == 'WD4':
                   self.updateCardColors()
                   
                   hc = self.findHighestColor()
                   print "%s has changed the color to %s" %(self.name, hc)
                   self.game.color = hc
                   

Launching BotPlayer with GufyGames

Just Like Game, you need to provide a main function that returns a valid BotPlayer for your game to use.

 
    from gameLib.botplayer import BotPlayer

    def main(debug):
        bot = BotPlayerUno("GufyBot")
        return bot

    class BotPlayerUno(BotPlayer):
       .......Class Definition.......