leonardo-bridge 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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