Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

PYTHON 3.4 WHIST PLAYER CLASS HELP. Below is the information that is givin to us

ID: 3768703 • Letter: P

Question

PYTHON 3.4 WHIST PLAYER CLASS HELP.

Below is the information that is givin to us about the game of whist itself. The assignment itself is not code for a program that plays whist, but for a class Player, that contains a PlayCard function. The program itself is a program that is suppose to make the best move possible out of the cards they are dealt. I posted this before and got code for a whole game of whist, but I am looking for help on how to go about creating this Player class with a PlayCard fuction that will play the best card in the cards in its hand. Thank you for any and all help. Below are the details:

Whist is a card game for 4 players. It is a forerunner of Bridge, but unlike Bridge, it has no bidding phase. It is played with a standard 52-card deck (see appendix at the bottom of the assignment if you’re unfamiliar with the standard deck). There is no ranking among the suits. Play proceeds as follows:

The game requires 4 players, who play in 2-person partnerships. Partners sit across the table from each other. Customarily, the players are identified by compass directions (North, South, East, West). North and South are partners, as are East and West.

A dealer is chosen by lot. The dealer deals the entire deck, one card at a time, starting with the player to the dealer’s left and proceeding clockwise, until the deck is exhausted and each player has a hand of 13 cards.

The last card dealt to the dealer is turned face up. The suit of this card is trump for this hand.

(After the first trick, the dealer puts this card back in her hand.)

Each hand consists of 13 tricks, in which each player plays one card. The first trick is led by the player to the dealer’s left and play proceeds to the left.

Each player plays one card. Players must follow suit (play the same suit that was led) if they have any cards of that suit. If they have no cards of that suit, they may play a card of the trump suit, or may discard (play any other card in their hand).

When each player has played, the highest card played of the trump suit takes the trick. If no trumps were played (which is more often the case), the highest card played of the suit led takes the trick. A card that is not the suit led or trump cannot take the trick.

Suppose Spades are trump, and East leads the 6 of Clubs. Other players play the Ace of Diamonds, Ace of Hearts, and King of Diamonds. Since no trump were played, and the other players were apparently unable to follow suit, the 6 of Clubs takes the trick. On the other hand, if the 2 of Spades were played instead of the King of Diamonds, it would take the trick, as it was trump.

The winner of one trick leads the next. The player leading the trick may lead with any card they choose.

When all cards have been played, each partnership counts the tricks they have taken. The winning partnership scores 1 point for each trick over 6 that they took. (If they took 7 tricks, they score 1 point; 8 tricks scores 2 points; and so on).

The game is 5 points. As only one partnership can score each hand, ties are impossible.

If neither partnership has yet won the game, the deal passes to the left and another hand is played.

You will write a Player object (class). This class will have a method called PlayCard(). The parameter list for this method is rather long:

A tuple of cards. These are the cards in your hand.

A (possibly empty) list of cards that have been played so far this trick. (If the list is empty, you need to lead for this trick.) Cards are listed in the order played.

A string (one of ‘S’, ‘H’, ‘D’, ‘C’) specifying trump for this hand.

A string (one of ‘N’, ‘S’, ‘E’, ‘W’) specifying which position you are playing.

A (possibly empty) list of tricks that have been played so far this hand. Each is a tuple consisting of: 4 cards (the cards that were played), a string “N”, “S”, “E”, “W” specifying who led that trick, and another string (same format) specifying who won that trick.

The current score of the game; NS score, then EW score.

The PlayCard() method should not have defaults for any of these parameters.

Your PlayCard method must select and return a card from your hand to play. If it is not leading, it must follow suit if possible. It must return a Card, not a string, integer, or other representation of the card.

That’s good for a B.

For an A, you must implement some kind of strategy in your class. Document this strategy in your code. How do you select which card to play? There are several possibilities.

You may choose to always play the highest card available.

You may decide to always play trump if you can (to maximize the number of tricks taken).

On the other hand, if a card’s already been played that’s higher than anything you have in that suit, then you can’t take the trick (you have to follow suit if you can), and should probably play your lowest card in that suit.

On the other hand, if your partner led an Ace in a suit you don’t have, that will probably take the trick and you should save your trump for when it would be more needed.

BUT, if it’s late in the hand and there are only a few cards of that suit left, an opponent may play a trump.

One strategy is to “draw trumps” by leading with your high trumps to force opponents to play their trumps early, so they can’t use them to take tricks later in the game. This assumes that you have the high trumps. If you only have low trumps, or only have a few, try to look for better opportunities.

If you only have a few cards of a suit, you may decide to discard them as soon as possible, so you can play trump the next time that suit is led.

With careful tracking of what cards have been played, it’s possible to work out how many cards of a particular suit are unplayed, and where some of the cards may lie. (If the last time an opponent led trump, your partner discarded, then your partner is out of trumps. If 12 trumps have already been played and you have 1 trump in your hand, then you have the last trump.)

You must use some method beyond playing the first (or last) legal card found or randomly choosing from legal cards. You can base it on the rank of the cards in your hand, on what cards have been played, etc. (The actual game has strict rules against going through previous tricks to see what’s been played, but your object will have that information available, since it won’t be ‘watching’ as the hand is played. You don’t have to use that information for your strategy, but it’s available if you want to use it.)

Support code:

Your program will use the Card class attached. DO NOT MODIFY THIS CLASS. The class used in the final program will have the same interface—the same public methods will be available and will have the same parameters. Do not assume the private/internal data elements of the class will be the same. Read the docstrings of the classes carefully; these are not quite the same as versions developed in class. Put the Card.py file in the same folder as your code. Your program will import Card early in the program.  

Your file should be named Player.py. Your class name should be Player.

Your Player should have an __init__(self) method. You may add whatever other methods or data you please, but must have a PlayCard() method with the interface specified above.

A test script and file will be provided. Put the test script and data file into the same folder as your Player file. The test script will use the unittest module to run various tests on your class. It will start with the basics (Is there a Player module? Does it define a Player class? Does the Player class have a PlayCard method? Does the PlayCard method have the right number of parameters? And so on.) If all of these tests are passed, more advanced tests will be done, verifying that your object returns a Card, that it only returns a Card from the hand, that it follows suit if possible, etc. Submissions that pass ALL testing without errors will be entered into a tournament. Details will be announced as the deadline approaches (and the instructors get the various test scripts written).

Note that this gives you a lot of control over your grade. If your class returns a Card from the hand, always following suit if possible…then you’ve met the requirements for a B. So you can check for yourself how you’re progressing toward meeting the requirements of the assignment.

APPENDIX: The standard deck of cards (US/UK)

The standard deck consists of 52 cards, comprising 4 suits of 13 cards each. The suits are: Spades,

Hearts, Diamonds, Clubs. The suits are printed in contrasting colors: Spades and Clubs are Black suits, Hearts and Diamonds are Red suits. Color is not significant in Whist. There is no ranking among the suits in Whist.

The ranks of the cards are: Ace (high), King, Queen, Jack (often called the Knave in UK), 10, 9, 8, 7, 6, 5, 4, 3, 2 (low). In Whist the Ace is always high; this varies in some games.

Some games also have one or two Jokers, which are outside the normal ranking. They have no rank and no suit (or constitute their own). Whist does not use Jokers.

Comment

Explanation / Answer

from math import *
import random, sys
from copy import deepcopy

class GameState:
   """ A state of the game, i.e. the game board. These are the only functions which are
       absolutely necessary to implement ISMCTS in any imperfect information game,
       although they could be enhanced and made quicker, for example by using a
       GetRandomMove() function to generate a random move during rollout.
       By convention the players are numbered 1, 2, ..., self.numberOfPlayers.
   """
   def __init__(self):
       self.numberOfPlayers = 2
       self.playerToMove = 1
  
   def GetNextPlayer(self, p):
       """ Return the player to the left of the specified player
       """
       return (p % self.numberOfPlayers) + 1
  
   def Clone(self):
       """ Create a deep clone of this game state.
       """
       st = GameState()
       st.playerToMove = self.playerToMove
       return st
  
   def CloneAndRandomize(self, observer):
       """ Create a deep clone of this game state, randomizing any information not visible to the specified observer player.
       """
       return self.Clone()
  
   def DoMove(self, move):
       """ Update a state by carrying out the given move.
           Must update playerToMove.
       """
       self.playerToMove = self.GetNextPlayer(self.playerToMove)
      
   def GetMoves(self):
       """ Get all possible moves from this state.
       """
       raise NotImplementedException()
  
   def GetResult(self, player):
       """ Get the game result from the viewpoint of player.
       """
       raise NotImplementedException()

   def __repr__(self):
       """ Don't need this - but good style.
       """
       pass

class Card:
   """ A playing card, with rank and suit.
       rank must be an integer between 2 and 14 inclusive (Jack=11, Queen=12, King=13, Ace=14)
       suit must be a string of length 1, one of 'C' (Clubs), 'D' (Diamonds), 'H' (Hearts) or 'S' (Spades)
   """
   def __init__(self, rank, suit):
       if rank not in range(2, 14+1):
           raise Exception("Invalid rank")
       if suit not in ['C', 'D', 'H', 'S']:
           raise Exception("Invalid suit")
       self.rank = rank
       self.suit = suit
  
   def __repr__(self):
       return "??23456789TJQKA"[self.rank] + self.suit
  
   def __eq__(self, other):
       return self.rank == other.rank and self.suit == other.suit

   def __ne__(self, other):
       return self.rank != other.rank or self.suit != other.suit

class KnockoutWhistState(GameState):
   """ A state of the game Knockout Whist.
       See http://www.pagat.com/whist/kowhist.html for a full description of the rules.
       For simplicity of implementation, this version of the game does not include the "dog's life" rule
       and the trump suit for each round is picked randomly rather than being chosen by one of the players.
   """
   def __init__(self, n):
       """ Initialise the game state. n is the number of players (from 2 to 7).
       """
       self.numberOfPlayers = n
       self.playerToMove = 1
       self.tricksInRound = 7
       self.playerHands = {p:[] for p in xrange(1, self.numberOfPlayers+1)}
       self.discards = [] # Stores the cards that have been played already in this round
       self.currentTrick = []
       self.trumpSuit = None
       self.tricksTaken = {} # Number of tricks taken by each player this round
       self.knockedOut = {p:False for p in xrange(1, self.numberOfPlayers+1)}
       self.Deal()
  
   def Clone(self):
       """ Create a deep clone of this game state.
       """
       st = KnockoutWhistState(self.numberOfPlayers)
       st.playerToMove = self.playerToMove
       st.tricksInRound = self.tricksInRound
       st.playerHands = deepcopy(self.playerHands)
       st.discards = deepcopy(self.discards)
       st.currentTrick = deepcopy(self.currentTrick)
       st.trumpSuit = self.trumpSuit
       st.tricksTaken = deepcopy(self.tricksTaken)
       st.knockedOut = deepcopy(self.knockedOut)
       return st
  
   def CloneAndRandomize(self, observer):
       """ Create a deep clone of this game state, randomizing any information not visible to the specified observer player.
       """
       st = self.Clone()
      
       # The observer can see his own hand and the cards in the current trick, and can remember the cards played in previous tricks
       seenCards = st.playerHands[observer] + st.discards + [card for (player,card) in st.currentTrick]
       # The observer can't see the rest of the deck
       unseenCards = [card for card in st.GetCardDeck() if card not in seenCards]
      
       # Deal the unseen cards to the other players
       random.shuffle(unseenCards)
       for p in xrange(1, st.numberOfPlayers+1):
           if p != observer:
               # Deal cards to player p
               # Store the size of player p's hand
               numCards = len(self.playerHands[p])
               # Give player p the first numCards unseen cards
               st.playerHands[p] = unseenCards[ : numCards]
               # Remove those cards from unseenCards
               unseenCards = unseenCards[numCards : ]
      
       return st
  
   def GetCardDeck(self):
       """ Construct a standard deck of 52 cards.
       """
       return [Card(rank, suit) for rank in xrange(2, 14+1) for suit in ['C', 'D', 'H', 'S']]
  
   def Deal(self):
       """ Reset the game state for the beginning of a new round, and deal the cards.
       """
       self.discards = []
       self.currentTrick = []
       self.tricksTaken = {p:0 for p in xrange(1, self.numberOfPlayers+1)}
      
       # Construct a deck, shuffle it, and deal it to the players
       deck = self.GetCardDeck()
       random.shuffle(deck)
       for p in xrange(1, self.numberOfPlayers+1):
           self.playerHands[p] = deck[ : self.tricksInRound]
           deck = deck[self.tricksInRound : ]
      
       # Choose the trump suit for this round
       self.trumpSuit = random.choice(['C', 'D', 'H', 'S'])
  
   def GetNextPlayer(self, p):
       """ Return the player to the left of the specified player, skipping players who have been knocked out
       """
       next = (p % self.numberOfPlayers) + 1
       # Skip any knocked-out players
       while next != p and self.knockedOut[next]:
           next = (next % self.numberOfPlayers) + 1
       return next
  
   def DoMove(self, move):
       """ Update a state by carrying out the given move.
           Must update playerToMove.
       """
       # Store the played card in the current trick
       self.currentTrick.append((self.playerToMove, move))
      
       # Remove the card from the player's hand
       self.playerHands[self.playerToMove].remove(move)
      
       # Find the next player
       self.playerToMove = self.GetNextPlayer(self.playerToMove)
      
       # If the next player has already played in this trick, then the trick is over
       if any(True for (player, card) in self.currentTrick if player == self.playerToMove):
           # Sort the plays in the trick: those that followed suit (in ascending rank order), then any trump plays (in ascending rank order)
           (leader, leadCard) = self.currentTrick[0]
           suitedPlays = [(player, card.rank) for (player, card) in self.currentTrick if card.suit == leadCard.suit]
           trumpPlays = [(player, card.rank) for (player, card) in self.currentTrick if card.suit == self.trumpSuit]
           sortedPlays = sorted(suitedPlays, key = lambda (player, rank) : rank) + sorted(trumpPlays, key = lambda (player, rank) : rank)
           # The winning play is the last element in sortedPlays
           trickWinner = sortedPlays[-1][0]
          
           # Update the game state
           self.tricksTaken[trickWinner] += 1
           self.discards += [card for (player, card) in self.currentTrick]
           self.currentTrick = []
           self.playerToMove = trickWinner
          
           # If the next player's hand is empty, this round is over
           if self.playerHands[self.playerToMove] == []:
               self.tricksInRound -= 1
               self.knockedOut = {p:(self.knockedOut[p] or self.tricksTaken[p] == 0) for p in xrange(1, self.numberOfPlayers+1)}
               # If all but one players are now knocked out, the game is over
               if len([x for x in self.knockedOut.itervalues() if x == False]) <= 1:
                   self.tricksInRound = 0
              
               self.Deal()
  
   def GetMoves(self):
       """ Get all possible moves from this state.
       """
       hand = self.playerHands[self.playerToMove]
       if self.currentTrick == []:
           # May lead a trick with any card
           return hand
       else:
           (leader, leadCard) = self.currentTrick[0]
           # Must follow suit if it is possible to do so
           cardsInSuit = [card for card in hand if card.suit == leadCard.suit]
           if cardsInSuit != []:
               return cardsInSuit
           else:
               # Can't follow suit, so can play any card
               return hand
  
   def GetResult(self, player):
       """ Get the game result from the viewpoint of player.
       """
       return 0 if (self.knockedOut[player]) else 1
  
   def __repr__(self):
       """ Return a human-readable representation of the state
       """
       result = "Round %i" % self.tricksInRound
       result += " | P%i: " % self.playerToMove
       result += ",".join(str(card) for card in self.playerHands[self.playerToMove])
       result += " | Tricks: %i" % self.tricksTaken[self.playerToMove]
       result += " | Trump: %s" % self.trumpSuit
       result += " | Trick: ["
       result += ",".join(("%i:%s" % (player, card)) for (player, card) in self.currentTrick)
       result += "]"
       return result

class Node:
   """ A node in the game tree. Note wins is always from the viewpoint of playerJustMoved.
   """
   def __init__(self, move = None, parent = None, playerJustMoved = None):
       self.move = move # the move that got us to this node - "None" for the root node
       self.parentNode = parent # "None" for the root node
       self.childNodes = []
       self.wins = 0
       self.visits = 0
       self.avails = 1
       self.playerJustMoved = playerJustMoved # the only part of the state that the Node needs later
  
   def GetUntriedMoves(self, legalMoves):
       """ Return the elements of legalMoves for which this node does not have children.
       """
      
       # Find all moves for which this node *does* have children
       triedMoves = [child.move for child in self.childNodes]
      
       # Return all moves that are legal but have not been tried yet
       return [move for move in legalMoves if move not in triedMoves]
      
   def UCBSelectChild(self, legalMoves, exploration = 0.7):
       """ Use the UCB1 formula to select a child node, filtered by the given list of legal moves.
           exploration is a constant balancing between exploitation and exploration, with default value 0.7 (approximately sqrt(2) / 2)
       """
      
       # Filter the list of children by the list of legal moves
       legalChildren = [child for child in self.childNodes if child.move in legalMoves]
      
       # Get the child with the highest UCB score
       s = max(legalChildren, key = lambda c: float(c.wins)/float(c.visits) + exploration * sqrt(log(c.avails)/float(c.visits)))
      
       # Update availability counts -- it is easier to do this now than during backpropagation
       for child in legalChildren:
           child.avails += 1
      
       # Return the child selected above
       return s
  
   def AddChild(self, m, p):
       """ Add a new child node for the move m.
           Return the added child node
       """
       n = Node(move = m, parent = self, playerJustMoved = p)
       self.childNodes.append(n)
       return n
  
   def Update(self, terminalState):
       """ Update this node - increment the visit count by one, and increase the win count by the result of terminalState for self.playerJustMoved.
       """
       self.visits += 1
       if self.playerJustMoved is not None:
           self.wins += terminalState.GetResult(self.playerJustMoved)

   def __repr__(self):
       return "[M:%s W/V/A: %4i/%4i/%4i]" % (self.move, self.wins, self.visits, self.avails)

   def TreeToString(self, indent):
       """ Represent the tree as a string, for debugging purposes.
       """
       s = self.IndentString(indent) + str(self)
       for c in self.childNodes:
           s += c.TreeToString(indent+1)
       return s

   def IndentString(self,indent):
       s = " "
       for i in range (1,indent+1):
           s += "| "
       return s

   def ChildrenToString(self):
       s = ""
       for c in self.childNodes:
           s += str(c) + " "
       return s


def ISMCTS(rootstate, itermax, verbose = False):
   """ Conduct an ISMCTS search for itermax iterations starting from rootstate.
       Return the best move from the rootstate.
   """

   rootnode = Node()
  
   for i in range(itermax):
       node = rootnode
      
       # Determinize
       state = rootstate.CloneAndRandomize(rootstate.playerToMove)
      
       # Select
       while state.GetMoves() != [] and node.GetUntriedMoves(state.GetMoves()) == []: # node is fully expanded and non-terminal
           node = node.UCBSelectChild(state.GetMoves())
           state.DoMove(node.move)

       # Expand
       untriedMoves = node.GetUntriedMoves(state.GetMoves())
       if untriedMoves != []: # if we can expand (i.e. state/node is non-terminal)
           m = random.choice(untriedMoves)
           player = state.playerToMove
           state.DoMove(m)
           node = node.AddChild(m, player) # add child and descend tree

       # Simulate
       while state.GetMoves() != []: # while state is non-terminal
           state.DoMove(random.choice(state.GetMoves()))

       # Backpropagate
       while node != None: # backpropagate from the expanded node and work back to the root node
           node.Update(state)
           node = node.parentNode

   # Output some information about the tree - can be omitted
   if (verbose): print rootnode.TreeToString(0)
   else: print rootnode.ChildrenToString()

   return max(rootnode.childNodes, key = lambda c: c.visits).move # return the move that was most visited

def PlayGame():
   """ Play a sample game between two ISMCTS players.
   """
   state = KnockoutWhistState(4)
  
   while (state.GetMoves() != []):
       print str(state)
       # Use different numbers of iterations (simulations, tree nodes) for different players
       if state.playerToMove == 1:
           m = ISMCTS(rootstate = state, itermax = 1000, verbose = False)
       else:
           m = ISMCTS(rootstate = state, itermax = 100, verbose = False)
       print "Best Move: " + str(m) + " "
       state.DoMove(m)
  
   someoneWon = False
   for p in xrange(1, state.numberOfPlayers + 1):
       if state.GetResult(p) > 0:
           print "Player " + str(p) + " wins!"
           someoneWon = True
   if not someoneWon:
       print "Nobody wins!"

if __name__ == "__main__":
   PlayGame()