class Bridge::TrickPlay

This class models the trick-taking phase of a game of bridge. This code is generalised, and could easily be adapted to support a variety of trick-taking card games.

Attributes

declarer[RW]
dummy[RW]
history[RW]
lho[RW]
played[RW]
rho[RW]
trumps[RW]
winners[RW]

Public Class Methods

new(declarer, trump_suit) click to toggle source

@param declarer: the declarer from the auction. @type declarer: Direction @param trump_suit: the trump suit from the auction. @type trump_suit: Suit or None

# File lib/bridge/trick_play.rb, line 14
def initialize(declarer, trump_suit)
  raise TypeError, "Expected Direction, got #{declarer.inspect}" unless Direction[declarer]
  raise TypeError, "Expected Suit, got #{trump_suit.inspect}" if !trump_suit.nil? and Suit[trump_suit].nil?
  
  self.trumps = trump_suit
  self.declarer = declarer
  self.dummy = Direction[(declarer + 2) % 4]
  self.lho = Direction[(declarer + 1) % 4]
  self.rho = Direction[(declarer + 3) % 4]
  # Each trick corresponds to a cross-section of lists.
  self.played = {}
  self.history = []
  Direction.each do |position|
    self.played[position] = []
  end
  self.winners = []  # Winning player of each trick.
end

Public Instance Methods

complete?() click to toggle source

Playing is complete if there are 13 complete tricks. @return: True if playing is complete, False if not.

# File lib/bridge/trick_play.rb, line 34
def complete?
  self.winners.size == 13
end
get_current_trick() click to toggle source

Returns the getTrick() tuple of the current trick. @return: a (leader, cards) trick tuple.

# File lib/bridge/trick_play.rb, line 64
def get_current_trick
  # Index of current trick is length of longest played list minus 1.
  index = [0, (self.played.map { |dir,cards| cards.size }.max - 1)].max
  self.get_trick(index)
end
get_trick(index) click to toggle source

A trick is a set of cards, one from each player's hand. The leader plays the first card, the others play in clockwise order. @param: trick index, in range 0 to 12. @return: a trick object.

# File lib/bridge/trick_play.rb, line 42
def get_trick(index)
  raise ArgumentError unless 0 <= index and index < 13
  if index == 0  # First trick.
    leader = self.lho  # Leader is declarer's left-hand opponent.
  else  # Leader is winner of previous trick.
    leader = self.winners[index - 1]
  end
  
  cards = []
  
  Direction.each do |position|
    # If length of list exceeds index value, player's card in trick.
    if self.played[position].size > index
      cards[position] = self.played[position][index]
    end
  end
  
  Trick.new(:leader => leader, :cards => cards)
end
get_trick_count() click to toggle source

Returns the number of tricks won by declarer/dummy and by defenders. @return: the declarer trick count, the defender trick count. @rtype: tuple

# File lib/bridge/trick_play.rb, line 73
def get_trick_count
  declarer_count, defender_count = 0, 0
  
  (0..self.winners.size-1).each do |i|
    trick = self.get_trick(i)
    winner = self.who_played?(self.winning_card(trick))
    if [self.declarer, self.dummy].include?(winner)
      declarer_count += 1
    else
      defender_count += 1
    end
  end
  
  [declarer_count, defender_count]
end
get_tricks() click to toggle source
# File lib/bridge/trick_play.rb, line 89
def get_tricks
  (0..self.winners.size-1).map do |i|
    self.get_trick(i)
  end
end
play_card(card, player=nil, hand=nil) click to toggle source

Plays card to current trick. Card validity should be checked with isValidPlay() beforehand. @param card: the Card object to be played from player's hand. @param player: the player of card, or None. @param hand: the hand of player, or [].

# File lib/bridge/trick_play.rb, line 100
def play_card(card, player=nil, hand=nil)
  Bridge.assert_card(card)
  
  player = player || self.whose_turn
  hand = hand || [card]  # Skip hand check.
  
  raise ArgumentError, 'Not valid play' unless self.valid_play?(card, player, hand)
  self.played[player] << card
  self.history << card
  
  # If trick is complete, determine winner.
  trick = self.get_current_trick
  if trick.cards.compact.size == 4
    winner = self.who_played?(self.winning_card(trick))
    self.winners << winner
  end
  return true
end
to_a() click to toggle source
# File lib/bridge/trick_play.rb, line 202
def to_a
  trick_counts = self.get_trick_count
  {
    trumps: self.trumps,
    declarer: self.declarer,
    dummy: self.dummy,
    lho: self.lho,
    rho: self.rho,
    played: self.played,
    winners: self.winners,
    declarer_trick_count: trick_counts.first,
    defender_trick_count: trick_counts.last,
    tricks: self.get_tricks
  }
end
valid_play?(card, player=nil, hand=[]) click to toggle source

Card is playable if and only if:

  • Play session is not complete.

  • Direction is on turn to play.

  • Card exists in hand.

  • Card has not been previously played.

In addition, if the current trick has an established lead, then card must follow lead suit OR hand must be void in lead suit. Specification of player and hand are required for verification.

# File lib/bridge/trick_play.rb, line 127
def valid_play?(card, player=nil, hand=[])
  Bridge.assert_card(card)
  
  if self.complete?
    return false
  elsif hand and !hand.include?(card)
    return false  # Playing a card not in hand.
  elsif player and self.whose_turn != self.dummy and player != self.whose_turn
    return false  # Playing out of turn.
  elsif self.who_played?(card)
    return false  # Card played previously.
  end
  trick = self.get_current_trick
  # 0 if start of playing, 4 if complete trick.
  if [0, 4].include?(trick.cards.compact.size)
    return true # Card will be first in next trick.
  else # Current trick has an established lead: check for revoke.
    leadcard = trick.leader_card
    # Cards in hand that match suit of leadcard.
    followers = hand.select { |c| c.suit == leadcard.suit and !self.who_played?(c) }
    # Hand void in lead suit or card follows lead suit.
    return (followers.size == 0 or followers.include?(card))
  end
end
who_played?(card) click to toggle source

Returns the player who played the specified card. @param card: a Card. @return: the player who played card.

# File lib/bridge/trick_play.rb, line 155
def who_played?(card)
  Bridge.assert_card(card) unless card.nil?
  self.played.each do |player,cards|
    return player if cards.include?(card)
  end
  false
end
whose_turn() click to toggle source

If playing is not complete, returns the player who is next to play. @return: the player next to play.

# File lib/bridge/trick_play.rb, line 165
def whose_turn
  unless self.complete?
    trick = self.get_current_trick
    if trick.cards.compact.size == 4  # If trick is complete, trick winner's turn.
      return self.who_played?(self.winning_card(trick))
    else  # Otherwise, turn is next (clockwise) player in trick.
      return Direction[(trick.leader + trick.cards.compact.size) % 4]
    end
  end
  return false
end
winning_card(trick) click to toggle source

Determine which card wins the specified trick:

  • In a trump contract, the highest ranked trump card wins.

  • Otherwise, the highest ranked card of the lead suit wins.

@param: a complete (leader, cards) trick tuple. @return: the Card object which wins the trick.

# File lib/bridge/trick_play.rb, line 182
def winning_card(trick)
  if trick.cards.compact.size == 4  # Trick is complete.
    if self.trumps  # Suit contract.
      trumpcards = trick.cards.compact.select { |c| c.suit_i == self.trumps }
      if trumpcards.size > 0 # Highest ranked trump.
        return trumpcards.max 
      else # we re in trump contract but play didn't have a trump.
        followers = trick.cards.compact.select { |c| c.suit == trick.leader_card.suit }
        return followers.max # Highest ranked card in lead suit.
      end
    else
      # No Trump contract, or no trump cards played.
      followers = trick.cards.compact.select { |c| c.suit == trick.leader_card.suit }
      return followers.max # Highest ranked card in lead suit.
    end
  else
    return false
  end
end