leonardo-bridge 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +46 -0
  6. data/Rakefile +1 -0
  7. data/bin/leo-console +7 -0
  8. data/bin/leo-play +143 -0
  9. data/bridge.gemspec +29 -0
  10. data/bridge.rc.rb +11 -0
  11. data/lib/bridge.rb +35 -0
  12. data/lib/bridge/auction.rb +182 -0
  13. data/lib/bridge/board.rb +84 -0
  14. data/lib/bridge/call.rb +98 -0
  15. data/lib/bridge/card.rb +54 -0
  16. data/lib/bridge/contract.rb +51 -0
  17. data/lib/bridge/db.rb +27 -0
  18. data/lib/bridge/deal.rb +33 -0
  19. data/lib/bridge/deck.rb +22 -0
  20. data/lib/bridge/game.rb +372 -0
  21. data/lib/bridge/hand.rb +25 -0
  22. data/lib/bridge/leonardo_result.rb +7 -0
  23. data/lib/bridge/player.rb +49 -0
  24. data/lib/bridge/result.rb +290 -0
  25. data/lib/bridge/trick.rb +28 -0
  26. data/lib/bridge/trick_play.rb +219 -0
  27. data/lib/bridge/version.rb +3 -0
  28. data/lib/enum.rb +32 -0
  29. data/lib/redis_model.rb +137 -0
  30. data/lib/uuid.rb +280 -0
  31. data/spec/auction_spec.rb +100 -0
  32. data/spec/board_spec.rb +19 -0
  33. data/spec/bridge_spec.rb +25 -0
  34. data/spec/call_spec.rb +44 -0
  35. data/spec/card_spec.rb +95 -0
  36. data/spec/db_spec.rb +19 -0
  37. data/spec/deck_spec.rb +14 -0
  38. data/spec/enum_spec.rb +14 -0
  39. data/spec/game_spec.rb +291 -0
  40. data/spec/hand_spec.rb +21 -0
  41. data/spec/player_spec.rb +22 -0
  42. data/spec/redis_model_spec.rb +50 -0
  43. data/spec/result_spec.rb +64 -0
  44. data/spec/spec_helper.rb +21 -0
  45. data/spec/support/auction_helper.rb +9 -0
  46. data/spec/support/test_enum.rb +5 -0
  47. data/spec/support/test_model.rb +3 -0
  48. data/spec/trick_play_spec.rb +90 -0
  49. data/spec/trick_spec.rb +15 -0
  50. metadata +240 -0
@@ -0,0 +1,25 @@
1
+ module Bridge
2
+ class Hand
3
+ attr_accessor :cards, :played, :current
4
+
5
+ def initialize
6
+ self.cards = []
7
+ end
8
+
9
+ def method_missing(m, *args, &block)
10
+ if cards.respond_to?(m)
11
+ cards.send(m, *args, &block)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ cards.to_s
19
+ end
20
+
21
+ def to_json opts = {}
22
+ cards.to_json
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ require Pathname.new(__FILE__).dirname.join('result')
2
+
3
+ module Bridge
4
+ # custom leonardo bridge result logic
5
+ class LeonardoResult < DuplicateResult
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ module Bridge
2
+ # Actor representing a player's view of a BridgeGame object.
3
+ # ref: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/game.py
4
+ class Player < RedisModel
5
+ def initialize(game)
6
+ @game = game # Access to game is private to this object.
7
+ end
8
+
9
+ def get_hand
10
+ position = game.players[self]
11
+ return game.get_hand(position)
12
+ end
13
+ alias :hand :get_hand
14
+
15
+ def make_call(call)
16
+ begin
17
+ return game.make_call(call, player = self)
18
+ rescue Exception => e
19
+ if Bridge::DEBUG
20
+ puts e.backtrace.first(8).join("\n").red
21
+ puts "\n"
22
+ end
23
+ raise GameError, e.message
24
+ end
25
+ end
26
+
27
+ def play_card(card)
28
+ begin
29
+ return self.game.play_card(card, self)
30
+ rescue Exception => e
31
+ if Bridge::DEBUG
32
+ puts e.backtrace.first(8).join("\n").red
33
+ puts "\n"
34
+ end
35
+ raise GameError, e.message
36
+ end
37
+ end
38
+
39
+ def start_next_game
40
+ raise GameError, "Not ready to start game" unless game.next_game_ready?
41
+ game.start!
42
+ end
43
+
44
+ protected
45
+ def game
46
+ @game
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,290 @@
1
+
2
+ module Bridge
3
+ # Represents the result of a completed round of bridge.
4
+ # Swiped from: https://svn.code.sf.net/p/pybridge/code/trunk/pybridge/pybridge/games/bridge/result.py
5
+ class Result
6
+ VULN_MAP = {
7
+ Vulnerability.none => [],
8
+ Vulnerability.north_south => [Direction.north, Direction.south],
9
+ Vulnerability.east_west => [Direction.east, Direction.west],
10
+ Vulnerability.all => [
11
+ Direction.north, Direction.east,
12
+ Direction.west, Direction.south
13
+ ]
14
+ }
15
+
16
+ def _get_score
17
+ raise NoMethodError # Expected to be implemented by subclasses.
18
+ end
19
+
20
+ # @type board: Board
21
+ # @type contract: Contract
22
+ # @type tricks_made: int or None
23
+ attr_accessor :board, :contract, :tricks_made, :is_vulnerable, :score
24
+ attr_accessor :is_doubled, :is_redoubled, :is_vulnerable, :is_major,
25
+ :contract_level, :tricks_made, :tricks_required, :trump_suit, :claimed, :claimed_by
26
+
27
+ def initialize(board, contract, tricks_made = nil, opts = {})
28
+ self.board = board
29
+ self.contract = contract
30
+ self.tricks_made = tricks_made
31
+
32
+ # a claim has been made. Let's modify the trick count accordingly
33
+ if opts[:claim].is_a?(Array)
34
+ self.claimed_by = opts[:claim][0]
35
+ self.claimed = opts[:claim][1]
36
+ defender_tricks = opts[:claim][2]
37
+ if [self.contract[:declarer],(self.contract[:declarer] + 2) % 4].include?(claimed_by)
38
+ self.tricks_made += claimed # if declarer claimed, add claim to tally
39
+ else # if defender claimed, add what remains to tally
40
+ self.tricks_made = 13 - (defender_tricks + claimed)
41
+ end
42
+ self.tricks_made
43
+ end
44
+
45
+ self.is_vulnerable = nil
46
+ if self.contract
47
+ vuln = self.board.vulnerability || Vulnerability.none
48
+ self.is_vulnerable = VULN_MAP[vuln].include?(self.contract[:declarer])
49
+ end
50
+
51
+ self.score = self._get_score
52
+ end
53
+
54
+ # Compute the component values which contribute to the score.
55
+ # Note that particular scoring schemes may ignore some of the components.
56
+ # Scoring values: http://en.wikipedia.org/wiki/Bridge_scoring
57
+ # @return: a dict of component values.
58
+ # @rtype: dict
59
+ def _get_score_components
60
+ components = {}
61
+
62
+ self.is_doubled = self.contract[:double_by] ? true : false
63
+ self.is_redoubled = self.contract[:redouble_by] ? true : false
64
+ self.contract_level = self.contract[:bid].level + 1
65
+ self.tricks_required = contract_level + 6
66
+ self.trump_suit = self.contract[:bid].strain
67
+ self.is_major = [Strain.spade, Strain.heart].include?(self.contract[:bid].strain)
68
+
69
+ if tricks_made >= tricks_required # Contract successful.
70
+ #### Contract tricks (bid and made) ####
71
+ if is_major || self.contract[:bid].strain == Strain.no_trump # Hearts, Spades and NT score 30 for each odd trick.
72
+ components['odd'] = contract_level * 30
73
+ if trump_suit == Strain.no_trump
74
+ components['odd'] += 10 # For NT, add a 10 point bonus.
75
+ end
76
+ else
77
+ components['odd'] = contract_level * 20
78
+ end
79
+
80
+ if is_redoubled
81
+ components['odd'] *= 4 # Double the doubled score.
82
+ elsif is_doubled
83
+ components['odd'] *= 2 # Double score.
84
+ end
85
+
86
+
87
+ #### over_tricks ####
88
+ over_tricks = tricks_made - tricks_required
89
+
90
+ if is_redoubled
91
+ # 400 for each overtrick if vulnerable, 200 if not.
92
+ if is_vulnerable
93
+ components['over'] = over_tricks * 400
94
+ else
95
+ components['over'] = over_tricks * 200
96
+ end
97
+ elsif is_doubled
98
+ # 200 for each overtrick if vulnerable, 100 if not.
99
+ if is_vulnerable
100
+ components['over'] = over_tricks * 200
101
+ else
102
+ components['over'] = over_tricks * 100
103
+ end
104
+ else # Undoubled contract.
105
+ if is_major || self.contract[:bid].strain == Strain.no_trump
106
+ # Hearts, Spades and NT score 30 for each overtrick.
107
+ components['over'] = over_tricks * 30
108
+ else
109
+ # Clubs and Diamonds score 20 for each overtrick.
110
+ components['over'] = over_tricks * 20
111
+ end
112
+ end
113
+
114
+ #### Premium bonuses ####
115
+
116
+ if tricks_required == 13
117
+ # 1500 for grand slam if vulnerable, 1000 if not.
118
+ if is_vulnerable
119
+ components['slambonus'] = 1500
120
+ else
121
+ components['slambonus'] = 1000
122
+ end
123
+ elsif tricks_required == 12
124
+ # 750 for small slam if vulnerable, 500 if not.
125
+ if is_vulnerable
126
+ components['slambonus'] = 750
127
+ else
128
+ components['slambonus'] = 500
129
+ end
130
+ elsif components['odd'] >= 100 # Game contract (non-slam).
131
+ # 500 for game if vulnerable, 300 if not.
132
+ if is_vulnerable
133
+ components['gamebonus'] = 500
134
+ else
135
+ components['gamebonus'] = 300
136
+ end
137
+ else # Non-game contract.
138
+ components['partscore'] = 50
139
+ end
140
+
141
+ #### Insult bonus ####
142
+ if is_redoubled
143
+ components['insultbonus'] = 100
144
+ elsif is_doubled
145
+ components['insultbonus'] = 50
146
+ end
147
+ else # Contract not successful.
148
+ under_tricks = tricks_required - tricks_made
149
+ if is_redoubled
150
+ if is_vulnerable
151
+ # -400 for first, then -600 each.
152
+ components['under'] = -400 + (under_tricks - 1) * -600
153
+ else
154
+ # -200 for first, -400 for second and third, then -600 each.
155
+ components['under'] = -200 + (under_tricks - 1) * -400
156
+ if under_tricks > 3
157
+ components['under'] += (under_tricks - 3) * -200
158
+ end
159
+ end
160
+ elsif is_doubled
161
+ if is_vulnerable
162
+ # -200 for first, then -300 each.
163
+ components['under'] = -200 + (under_tricks - 1) * -300
164
+ else
165
+ # -100 for first, -200 for second and third, then -300 each.
166
+ components['under'] = -100 + (under_tricks - 1) * -200
167
+ if under_tricks > 3
168
+ components['under'] += (under_tricks - 3) * -100
169
+ end
170
+ end
171
+ else
172
+ if is_vulnerable
173
+ # -100 each.
174
+ components['under'] = under_tricks * -100
175
+ else
176
+ # -50 each.
177
+ components['under'] = under_tricks * -50
178
+ end
179
+ end
180
+ end
181
+ components
182
+ end
183
+
184
+ def to_a
185
+ {
186
+ score: _get_score,
187
+ tricks_made: tricks_made,
188
+ tricks_required: tricks_required,
189
+ contract_level: contract_level,
190
+ trump_suit: trump_suit,
191
+ vulnerable: is_vulnerable,
192
+ major: is_major,
193
+ doubled: is_doubled,
194
+ redoubled: is_redoubled,
195
+ claimed: claimed,
196
+ claimed_by: claimed_by
197
+ }
198
+ end
199
+ end
200
+
201
+
202
+ #Represents the result of a completed round of duplicate bridge.
203
+ class DuplicateResult < Result
204
+ # Duplicate bridge scoring scheme.
205
+ # @return: score value: positive for declarer, negative for defenders.
206
+ def _get_score
207
+ score = 0
208
+ if self.contract and self.tricks_made
209
+ self._get_score_components.each do |key, value|
210
+ if ['odd', 'over', 'under', 'slambonus', 'gamebonus', 'partscore', 'insultbonus'].include?(key)
211
+ score += value
212
+ end
213
+ end
214
+ end
215
+ score
216
+ end
217
+ end
218
+
219
+ # Represents the result of a completed round of rubber bridge.
220
+ class RubberResult < Result
221
+ # Rubber bridge scoring scheme.
222
+ # @return: 2-tuple of numeric scores (above the line, below the line): positive for
223
+ # declarer, negative for defenders.
224
+ def _get_score
225
+ above, below = 0, 0
226
+ if self.contract and self.tricks_made
227
+ self._get_score_components.items.each do |key, value|
228
+ # Note: gamebonus/partscore are not assigned in rubber bridge.
229
+ if ['over', 'under', 'slambonus', 'insultbonus'].include?(key)
230
+ above += value
231
+ elsif key == 'odd'
232
+ below += value
233
+ end
234
+ end
235
+ end
236
+ return [above, below]
237
+ end
238
+ end
239
+
240
+
241
+
242
+ # A rubber set, in which pairs compete to make two consecutive games.
243
+ # A game is made by accumulation of 100+ points from below-the-line scores
244
+ # without interruption from an opponent's game.
245
+ class Rubber < Array
246
+ attr_accessor :games, :winner
247
+
248
+ # Returns a list of completed (ie. won) 'games' in this rubber, in the
249
+ # order of their completion.
250
+
251
+ # A game is represented as a list of consecutive results from this rubber,
252
+ # coupled with the identifier of the scoring pair.
253
+ def _get_games
254
+ games = []
255
+
256
+ thisgame = []
257
+ belowNS, belowEW = 0, 0 # Cumulative totals for results in this game.
258
+
259
+ self.each do |result|
260
+ thisgame << result
261
+ if [Direction.north, Direction.south].include?(result.contract.declarer)
262
+ belowNS += result.score[1]
263
+ if belowNS >= 100
264
+ games << [thisgame, [Direction.north, Direction.south]]
265
+ else
266
+ belowEW += result.score[1]
267
+ if belowEW >= 100
268
+ games << [thisgame, [Direction.east, Direction.west]]
269
+ end
270
+ end
271
+ # If either total for this game exceeds 100, proceed to next game.
272
+ if belowNS >= 100 or belowEW >= 100
273
+ thisgame = []
274
+ belowNS, belowEW = 0, 0 # Reset accumulators.
275
+ end
276
+ end
277
+ end
278
+ return games
279
+ end
280
+
281
+ # The rubber is won by the pair which have completed two games.
282
+ def _get_winner
283
+ pairs = self.games.map { |game, pair| pair }
284
+ [[Direction.north, Direction.south], [Direction.east, Direction.west]].each do |pair|
285
+ pair if pairs.count(pair) >= 2
286
+ end
287
+ end
288
+ end
289
+ end
290
+
@@ -0,0 +1,28 @@
1
+ module Bridge
2
+ class Trick
3
+ REQUIRED_CARDS = 4
4
+ attr_accessor :cards
5
+ attr_accessor :leader
6
+ def initialize(params = {})
7
+ params.map { |k,v| self.send(:"#{k}=",v) }
8
+ self.cards = [] if self.cards.nil?
9
+ end
10
+
11
+ def done?
12
+ self.cards.compact.size >= REQUIRED_CARDS
13
+ end
14
+
15
+ def leader_card
16
+ self.cards[self.leader]
17
+ end
18
+
19
+ def method_missing(method, *args, &block)
20
+ begin
21
+ self.cards = self.cards.to_s.split(' ').map { |c| Card.from_string(c) } unless self.cards.class == Array
22
+ self.cards.send(method, *args, &block)
23
+ rescue Exception => e
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,219 @@
1
+ module Bridge
2
+ # This class models the trick-taking phase of a game of bridge.
3
+ # This code is generalised, and could easily be adapted to support a
4
+ # variety of trick-taking card games.
5
+ # Swiped from: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/auction.py
6
+ class TrickPlay
7
+ attr_accessor :trumps, :declarer, :dummy, :lho, :rho, :played, :winners, :history
8
+
9
+ # TODO: tricks, leader, winner properties?
10
+
11
+ # @param declarer: the declarer from the auction.
12
+ # @type declarer: Direction
13
+ # @param trump_suit: the trump suit from the auction.
14
+ # @type trump_suit: Suit or None
15
+ def initialize(declarer, trump_suit)
16
+ raise TypeError, "Expected Direction, got #{declarer.inspect}" unless Direction[declarer]
17
+ raise TypeError, "Expected Suit, got #{trump_suit.inspect}" if !trump_suit.nil? and Suit[trump_suit].nil?
18
+
19
+ self.trumps = trump_suit
20
+ self.declarer = declarer
21
+ self.dummy = Direction[(declarer + 2) % 4]
22
+ self.lho = Direction[(declarer + 1) % 4]
23
+ self.rho = Direction[(declarer + 3) % 4]
24
+ # Each trick corresponds to a cross-section of lists.
25
+ self.played = {}
26
+ self.history = []
27
+ Direction.each do |position|
28
+ self.played[position] = []
29
+ end
30
+ self.winners = [] # Winning player of each trick.
31
+ end
32
+
33
+ # Playing is complete if there are 13 complete tricks.
34
+ # @return: True if playing is complete, False if not.
35
+ def complete?
36
+ self.winners.size == 13
37
+ end
38
+
39
+ # A trick is a set of cards, one from each player's hand.
40
+ # The leader plays the first card, the others play in clockwise order.
41
+ # @param: trick index, in range 0 to 12.
42
+ # @return: a trick object.
43
+ def get_trick(index)
44
+ raise ArgumentError unless 0 <= index and index < 13
45
+ if index == 0 # First trick.
46
+ leader = self.lho # Leader is declarer's left-hand opponent.
47
+ else # Leader is winner of previous trick.
48
+ leader = self.winners[index - 1]
49
+ end
50
+
51
+ cards = []
52
+
53
+ Direction.each do |position|
54
+ # If length of list exceeds index value, player's card in trick.
55
+ if self.played[position].size > index
56
+ cards[position] = self.played[position][index]
57
+ end
58
+ end
59
+
60
+ Trick.new(:leader => leader, :cards => cards)
61
+ end
62
+
63
+ # Returns the getTrick() tuple of the current trick.
64
+ # @return: a (leader, cards) trick tuple.
65
+ def get_current_trick
66
+ # Index of current trick is length of longest played list minus 1.
67
+ index = [0, (self.played.map { |dir,cards| cards.size }.max - 1)].max
68
+ self.get_trick(index)
69
+ end
70
+
71
+ # Returns the number of tricks won by declarer/dummy and by defenders.
72
+ # @return: the declarer trick count, the defender trick count.
73
+ # @rtype: tuple
74
+ def get_trick_count
75
+ declarer_count, defender_count = 0, 0
76
+
77
+ (0..self.winners.size-1).each do |i|
78
+ trick = self.get_trick(i)
79
+ winner = self.who_played?(self.winning_card(trick))
80
+ if [self.declarer, self.dummy].include?(winner)
81
+ declarer_count += 1
82
+ else
83
+ defender_count += 1
84
+ end
85
+ end
86
+
87
+ [declarer_count, defender_count]
88
+ end
89
+
90
+ def get_tricks
91
+ (0..self.winners.size-1).map do |i|
92
+ self.get_trick(i)
93
+ end
94
+ end
95
+
96
+ # Plays card to current trick.
97
+ # Card validity should be checked with isValidPlay() beforehand.
98
+ # @param card: the Card object to be played from player's hand.
99
+ # @param player: the player of card, or None.
100
+ # @param hand: the hand of player, or [].
101
+ def play_card(card, player=nil, hand=nil)
102
+ Bridge.assert_card(card)
103
+
104
+ player = player || self.whose_turn
105
+ hand = hand || [card] # Skip hand check.
106
+
107
+ raise ArgumentError, 'Not valid play' unless self.valid_play?(card, player, hand)
108
+ self.played[player] << card
109
+ self.history << card
110
+
111
+ # If trick is complete, determine winner.
112
+ trick = self.get_current_trick
113
+ if trick.cards.compact.size == 4
114
+ winner = self.who_played?(self.winning_card(trick))
115
+ self.winners << winner
116
+ end
117
+ return true
118
+ end
119
+
120
+ # Card is playable if and only if:
121
+ # - Play session is not complete.
122
+ # - Direction is on turn to play.
123
+ # - Card exists in hand.
124
+ # - Card has not been previously played.
125
+ # In addition, if the current trick has an established lead, then
126
+ # card must follow lead suit OR hand must be void in lead suit.
127
+ # Specification of player and hand are required for verification.
128
+ def valid_play?(card, player=nil, hand=[])
129
+ Bridge.assert_card(card)
130
+
131
+ if self.complete?
132
+ return false
133
+ elsif hand and !hand.include?(card)
134
+ return false # Playing a card not in hand.
135
+ elsif player and self.whose_turn != self.dummy and player != self.whose_turn
136
+ return false # Playing out of turn.
137
+ elsif self.who_played?(card)
138
+ return false # Card played previously.
139
+ end
140
+ trick = self.get_current_trick
141
+ # 0 if start of playing, 4 if complete trick.
142
+ if [0, 4].include?(trick.cards.compact.size)
143
+ return true # Card will be first in next trick.
144
+ else # Current trick has an established lead: check for revoke.
145
+ leadcard = trick.leader_card
146
+ # Cards in hand that match suit of leadcard.
147
+ followers = hand.select { |c| c.suit == leadcard.suit and !self.who_played?(c) }
148
+ # Hand void in lead suit or card follows lead suit.
149
+ return (followers.size == 0 or followers.include?(card))
150
+ end
151
+ end
152
+
153
+ # Returns the player who played the specified card.
154
+ # @param card: a Card.
155
+ # @return: the player who played card.
156
+ def who_played?(card)
157
+ Bridge.assert_card(card) unless card.nil?
158
+ self.played.each do |player,cards|
159
+ return player if cards.include?(card)
160
+ end
161
+ false
162
+ end
163
+
164
+ # If playing is not complete, returns the player who is next to play.
165
+ # @return: the player next to play.
166
+ def whose_turn
167
+ unless self.complete?
168
+ trick = self.get_current_trick
169
+ if trick.cards.compact.size == 4 # If trick is complete, trick winner's turn.
170
+ return self.who_played?(self.winning_card(trick))
171
+ else # Otherwise, turn is next (clockwise) player in trick.
172
+ return Direction[(trick.leader + trick.cards.compact.size) % 4]
173
+ end
174
+ end
175
+ return false
176
+ end
177
+
178
+ # Determine which card wins the specified trick:
179
+ # - In a trump contract, the highest ranked trump card wins.
180
+ # - Otherwise, the highest ranked card of the lead suit wins.
181
+ # @param: a complete (leader, cards) trick tuple.
182
+ # @return: the Card object which wins the trick.
183
+ def winning_card(trick)
184
+ if trick.cards.compact.size == 4 # Trick is complete.
185
+ if self.trumps # Suit contract.
186
+ trumpcards = trick.cards.compact.select { |c| c.suit_i == self.trumps }
187
+ if trumpcards.size > 0 # Highest ranked trump.
188
+ return trumpcards.max
189
+ else # we re in trump contract but play didn't have a trump.
190
+ followers = trick.cards.compact.select { |c| c.suit == trick.leader_card.suit }
191
+ return followers.max # Highest ranked card in lead suit.
192
+ end
193
+ else
194
+ # No Trump contract, or no trump cards played.
195
+ followers = trick.cards.compact.select { |c| c.suit == trick.leader_card.suit }
196
+ return followers.max # Highest ranked card in lead suit.
197
+ end
198
+ else
199
+ return false
200
+ end
201
+ end
202
+
203
+ def to_a
204
+ trick_counts = self.get_trick_count
205
+ {
206
+ trumps: self.trumps,
207
+ declarer: self.declarer,
208
+ dummy: self.dummy,
209
+ lho: self.lho,
210
+ rho: self.rho,
211
+ played: self.played,
212
+ winners: self.winners,
213
+ declarer_trick_count: trick_counts.first,
214
+ defender_trick_count: trick_counts.last,
215
+ tricks: self.get_tricks
216
+ }
217
+ end
218
+ end
219
+ end