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,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Auction do
4
+ include AuctionHelper
5
+
6
+ let(:dealer) { Direction.north }
7
+ let(:calls) {
8
+ [Pass.new(), Pass.new(), Bid.new(Level.one, Strain.club), Double.new(),
9
+ Redouble.new(), Pass.new(), Pass.new(), Bid.new(Level.one, Strain.no_trump),
10
+ Pass.new(), Bid.new(Level.three, Strain.no_trump), Pass.new(), Pass.new(),
11
+ Pass.new()]
12
+ }
13
+
14
+ subject(:instance) { Auction.new(dealer) }
15
+
16
+ it { subject.contract.should be_a(NilClass) }
17
+ it { subject.get_contract.should be_a(NilClass) }
18
+ it { subject.calls.size.should eq(0) }
19
+ it { subject.dealer.should eq(dealer) }
20
+
21
+ it 'should pass out on 4 passes only' do
22
+ subject.passed_out?.should eq(false)
23
+ 3.times do
24
+ subject.make_call(Pass.new).should eq(true)
25
+ subject.passed_out?.should eq(false)
26
+ end
27
+
28
+ subject.make_call(Pass.new).should eq(true)
29
+ subject.passed_out?.should eq(true)
30
+ end
31
+
32
+ describe 'when finished' do
33
+ before { calls.each { |call| subject.make_call(call) } }
34
+
35
+ it { subject.calls.should eq(calls) }
36
+ it { subject.contract.should_not be_a(NilClass) }
37
+ it { subject.contract.should be_a(Contract) }
38
+ it { subject.get_contract.should be_a(Hash) }
39
+ it { subject.get_contract.should eq(subject.contract.to_hash) }
40
+ end
41
+
42
+ describe 'current call' do
43
+ it { assert_current_calls([nil,nil,nil]) }
44
+
45
+ it 'should set current bid' do
46
+ assert_current_calls [Bid.new(Level.one, Strain.diamond)]
47
+ end
48
+
49
+ it 'should set current double' do
50
+ assert_current_calls [ Bid.new(Level.one, Strain.diamond), Double.new ]
51
+ end
52
+
53
+ it 'should set current redouble' do
54
+ assert_current_calls [ Bid.new(Level.one, Strain.diamond), Double.new, Redouble.new ]
55
+ end
56
+ end
57
+
58
+ it 'does not allow invalid calls' do
59
+ subject.make_call(Bid.new(Level.two, Strain.club))
60
+ expect { subject.make_call(Bid.new(Level.one, Strain.club)) }.to raise_error
61
+ expect { subject.make_call(Bid.new(Level.three, Strain.club)) }.to_not raise_error
62
+ expect { subject.make_call(Bid.new(Level.two, Strain.heart)) }.to raise_error
63
+ expect { subject.make_call(Bid.new(Level.three, Strain.heart)) }.to_not raise_error
64
+ end
65
+
66
+ it 'knows whose turn it is' do
67
+ turn = dealer
68
+ subject.whose_turn.should eq(turn)
69
+
70
+ calls.each_index do |i|
71
+ subject.make_call(calls[i])
72
+ if i == calls.size - 1
73
+ subject.whose_turn.should eq(nil)
74
+ else
75
+ turn = Direction[(turn + 1) % 4] # Turn moves clockwise.
76
+ subject.whose_turn.should eq(turn)
77
+ end
78
+ end
79
+ end
80
+
81
+ it 'only marks an auction as complete if it is' do
82
+ subject.complete?.should eq(false)
83
+ calls.each_index do |i|
84
+ subject.make_call(calls[i])
85
+ if i == calls.size - 1
86
+ subject.complete?.should eq(true)
87
+ else
88
+ subject.complete?.should eq(false)
89
+ end
90
+ end
91
+ end
92
+
93
+ it 'knows if a call is valid' do
94
+ calls.each_index do |i|
95
+ subject.make_call(calls[i])
96
+ next_call = calls[i+1]
97
+ subject.valid_call?(next_call).should eq(true) unless next_call.nil?
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Board do
4
+ subject { Board.first }
5
+
6
+ it { subject.number.should eq(1) }
7
+ it { subject.vulnerability.should eq(0) }
8
+ it { subject.dealer.should eq(0) }
9
+ it { subject.deal.hands.size.should eq(4) }
10
+ it { subject.deal.hands.each { |h| h.size.should eq(13) } }
11
+
12
+ describe '#next' do
13
+ let(:next_board) { subject.next }
14
+
15
+ it { next_board.number.should eq(2) }
16
+ it { next_board.vulnerability.should eq(1) }
17
+ it { next_board.dealer.should eq(1) }
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Direction do
4
+ it { Direction.north.should eq(0) }
5
+ it { Direction.east.should eq(1) }
6
+ it { Direction.south.should eq(2) }
7
+ it { Direction.west.should eq(3) }
8
+
9
+ it { Direction.next(Direction.north).should eq(Direction.east) }
10
+ it { Direction.next(Direction.east).should eq(Direction.south) }
11
+ it { Direction.next(Direction.south).should eq(Direction.west) }
12
+ it { Direction.next(Direction.west).should eq(Direction.north) }
13
+ end
14
+
15
+ describe Vulnerability do
16
+ it { Vulnerability.none.should eq(0) }
17
+ it { Vulnerability.north_south.should eq(1) }
18
+ it { Vulnerability.east_west.should eq(2) }
19
+ it { Vulnerability.all.should eq(3) }
20
+
21
+ it { Vulnerability.next(Vulnerability.none).should eq(Vulnerability.north_south) }
22
+ it { Vulnerability.next(Vulnerability.north_south).should eq(Vulnerability.east_west) }
23
+ it { Vulnerability.next(Vulnerability.east_west).should eq(Vulnerability.all) }
24
+ it { Vulnerability.next(Vulnerability.all).should eq(Vulnerability.none) }
25
+ end
data/spec/call_spec.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Call do
4
+ it 'can create a pass from a string' do
5
+ Call.from_string('pass').should be_a(Pass)
6
+ Call.from_string('p').should be_a(Pass)
7
+ Call.from_string('Pass').should be_a(Pass)
8
+ Call.from_string('P').should be_a(Pass)
9
+ end
10
+
11
+ it 'can create a double from a string' do
12
+ Call.from_string('double').should be_a(Double)
13
+ Call.from_string('d').should be_a(Double)
14
+ end
15
+
16
+ it 'can create a redouble from a string' do
17
+ Call.from_string('redouble').should be_a(Redouble)
18
+ Call.from_string('r').should be_a(Redouble)
19
+ end
20
+
21
+ it 'can create a bid from a string' do
22
+ c = Call.from_string('bid one heart')
23
+ c.should be_a(Bid)
24
+ c.strain.should eq(Strain.heart)
25
+ c.level.should eq(Level.one)
26
+
27
+ c = Call.from_string('b one heart')
28
+ c.strain.should eq(Strain.heart)
29
+ c.level.should eq(Level.one)
30
+
31
+ c = Call.from_string('b one no trump')
32
+ c.strain.should eq(Strain.no_trump)
33
+ c.level.should eq(Level.one)
34
+
35
+ c = Call.from_string('b one no_trump')
36
+ c.strain.should eq(Strain.no_trump)
37
+ c.level.should eq(Level.one)
38
+ end
39
+
40
+ it 'returns all available calls' do
41
+ Call.all.should be_a(Array)
42
+ Call.all.size.should eq(38)
43
+ end
44
+ end
data/spec/card_spec.rb ADDED
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Card do
4
+
5
+ let(:card){ Card.new('2', 'C') }
6
+
7
+ it "sets rank on initialize" do
8
+ card.rank.should == '2'
9
+ end
10
+
11
+ it "sets suit on initialize" do
12
+ card.suit.should == 'C'
13
+ end
14
+
15
+ it "returns a string with rank and suit" do
16
+ card.to_s.should == '2C'
17
+ end
18
+
19
+ it { card.suit_i.should eq(Suit.club) }
20
+
21
+ describe "honour" do
22
+ it "has an honour of 4 when rank is A" do
23
+ Card.new('A','H').honour.should eq(4)
24
+ end
25
+
26
+ it "has an honour of 3 when rank is K" do
27
+ Card.new('K','H').honour.should eq(3)
28
+ end
29
+
30
+ it "has an honour of 2 when rank is Q" do
31
+ Card.new('Q','H').honour.should eq(2)
32
+ end
33
+
34
+ it "has an honour of 1 when rank is J" do
35
+ Card.new('J','H').honour.should eq(1)
36
+ end
37
+
38
+ it "has an honour of 0 when rank is a number" do
39
+ Card.new('5','H').honour.should eq(0)
40
+ end
41
+ end
42
+
43
+ describe "#from_string" do
44
+ it { expect { Card.from_string('2') }.to raise_error(CardError) }
45
+ it { expect { Card.from_string('WAT') }.to raise_error(CardError) }
46
+ it { expect { Card.from_string('GT') }.to raise_error(CardError) }
47
+ it { expect { Card.from_string('GC') }.to raise_error(CardError) }
48
+ it { expect { Card.from_string('2F') }.to raise_error(CardError) }
49
+ it { Card.from_string('10C').should eq(Card.new('10','C')) }
50
+ it { Card.from_string('2C').should eq(card) }
51
+ it { Card.from_string('2C').rank.should eq('2') }
52
+ it { Card.from_string('2C').suit.should eq('C') }
53
+ end
54
+
55
+ describe "<=>" do
56
+ let(:cards) do
57
+ cards = []
58
+ for rank in Card::RANKS do
59
+ for suit in Card::SUITS do
60
+ cards << Card.new(rank, suit)
61
+ end
62
+ end
63
+
64
+ cards
65
+ end
66
+
67
+ let(:other_cards) do
68
+ cards = []
69
+ for rank in Card::RANKS do
70
+ for suit in Card::SUITS do
71
+ cards << Card.new(rank, suit)
72
+ end
73
+ end
74
+
75
+ cards
76
+ end
77
+
78
+ it "compares to other cards" do
79
+ (Card.new('2','C') < Card.new('2','D')).should eq(true)
80
+ (Card.new('2','C') > Card.new('2','D')).should eq(false)
81
+ (Card.new('2','C') == Card.new('2','D')).should eq(false)
82
+ end
83
+
84
+ it "compares to other cards of same suit" do
85
+ (Card.new('2','C') < Card.new('J','C')).should eq(true)
86
+ (Card.new('2','C') > Card.new('J','C')).should eq(false)
87
+ (Card.new('2','C') == Card.new('J','C')).should eq(false)
88
+ end
89
+
90
+ it "compares as equal to same card" do
91
+ cards.each { |c| c.should eq(c) } # same card
92
+ cards.each_index { |i| cards[i].should eq(other_cards[i]) } # same card, different object
93
+ end
94
+ end
95
+ end
data/spec/db_spec.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe DB do
4
+ # set up in spec_helper
5
+ subject { $redis }
6
+ it { subject.should be_a(Redis) }
7
+
8
+ describe '#namespace' do
9
+ before {
10
+ subject.set('server:bob','bob.bridge.com')
11
+ subject.set('server:dave','dave.bridge.com')
12
+ }
13
+
14
+ it { subject.namespace('server').should be_a(Array) }
15
+ it { subject.namespace('server').size.should eq(2) }
16
+ it { subject.namespace('server').first.should eq('bob.bridge.com') }
17
+ it { subject.namespace('server').last.should eq('dave.bridge.com') }
18
+ end
19
+ end
data/spec/deck_spec.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deck do
4
+
5
+ subject { Deck.new }
6
+
7
+ it { subject.size.should eq(52) }
8
+
9
+ it "should be shuffleable" do
10
+ old_deck = subject.clone
11
+ subject.shuffle!
12
+ subject.should_not eq(old_deck)
13
+ end
14
+ end
data/spec/enum_spec.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Enum do
4
+
5
+ subject { TestEnum }
6
+
7
+ it { subject.size.should eq(4) }
8
+ it { subject.first.should eq(0) }
9
+ it { subject.next(0).should eq(1) }
10
+ it { subject[0].should eq(0) }
11
+ it { subject.oh.should eq(0) }
12
+ it { subject.name(0).should eq('oh') }
13
+ it { subject.each.should be_a(Enumerator) }
14
+ end
data/spec/game_spec.rb ADDED
@@ -0,0 +1,291 @@
1
+ # ported from https://svn.code.sf.net/p/pybridge/code/trunk/pybridge/tests/bridge/test_game.py
2
+ require 'spec_helper'
3
+
4
+ describe Game do
5
+ subject { Game.new }
6
+ let(:board) {
7
+ Board.new(
8
+ :deal => Deal.new,
9
+ :dealer => Direction.north,
10
+ :vulnerability => Vulnerability.all
11
+ )
12
+ }
13
+
14
+ it { subject.in_progress?.should eq(false) }
15
+ it { subject.state.should eq(:new) }
16
+ it { expect { subject.get_turn }.to raise_error(GameError) }
17
+
18
+ describe 'in #rubber_mode' do
19
+ subject { Game.new(:rubber_mode => true) }
20
+
21
+ it 'sets rubber mode' do
22
+ subject.rubbers.should eq([])
23
+ end
24
+
25
+ it "should set vulnerability" do
26
+ subject.start!(board)
27
+ subject.board.vulnerability = Vulnerability.none
28
+ end
29
+ end
30
+
31
+ it 'should start with the first board by default' do
32
+ subject.start!
33
+ subject.board.number.should eq(1)
34
+ end
35
+
36
+ it 'should increment boards' do
37
+ g = Game.new(:board => Board.first)
38
+ g.start!
39
+ g.board.number.should eq(2)
40
+ end
41
+
42
+ describe '#start!' do
43
+ before { subject.start! }
44
+
45
+ it { subject.state.should eq(:auction) }
46
+ it { subject.in_progress?.should eq(true) }
47
+ it { subject.get_turn.should eq(subject.board.dealer) }
48
+ end
49
+
50
+ describe '#start with board' do
51
+ before { subject.start!(board) }
52
+
53
+ it { subject.board.should eq(board) }
54
+ it { subject.get_turn.should eq(board.dealer) }
55
+ end
56
+
57
+ describe 'player methods' do
58
+ before {
59
+ players = []
60
+ Direction.each do |position|
61
+ players[position] = subject.add_player(position)
62
+ end
63
+ }
64
+ it { subject.players.size.should eq(4) }
65
+ it { expect { subject.add_player(Direction.north) }.to raise_error }
66
+
67
+ it 'should be able to remove all players' do
68
+ Direction.each do |position|
69
+ expect { subject.remove_player(position) }.to_not raise_error
70
+ end
71
+
72
+ expect { subject.remove_player(Direction.north) }.to raise_error
73
+ end
74
+ end
75
+
76
+ describe 'running game' do
77
+ let(:players) {
78
+ players = []
79
+ Direction.each do |position|
80
+ players[position] = subject.add_player(position)
81
+ end
82
+ players
83
+ }
84
+
85
+ describe '#get_state' do
86
+ let(:calls) {
87
+ [Pass.new(), Pass.new(), Bid.new(Level.one, Strain.club), Double.new(),
88
+ Redouble.new(), Pass.new(), Pass.new(), Bid.new(Level.one, Strain.no_trump),
89
+ Pass.new(), Bid.new(Level.two, Strain.heart), Pass.new(), Pass.new(),
90
+ Pass.new()]
91
+ }
92
+
93
+ context 'in auction' do
94
+ before {
95
+ @players = []
96
+ Direction.each { |p| @players[p] = subject.add_player(p) }
97
+ subject.start!(board)
98
+ }
99
+
100
+ it 'should return all available calls at start' do
101
+ subject.get_state[:calls].size.should eq(38)
102
+ subject.get_state[:available_calls].size.should eq(36)
103
+ end
104
+
105
+ it 'should return available calls' do
106
+ @players[subject.get_turn].make_call(Call.from_string('b two heart'))
107
+ subject.get_state[:calls].size.should eq(38)
108
+ subject.get_state[:available_calls].size.should eq(29)
109
+ end
110
+ end
111
+
112
+ context "passed out" do
113
+ before {
114
+ subject.start!(board)
115
+ turn = board.dealer # Avoid calling getTurn.™
116
+ Direction.each do |i| # Iterate for each player.
117
+ players[i].make_call(Pass.new) # Each player passes.
118
+ turn = Direction[(turn+1) % Direction.size]
119
+ end
120
+ }
121
+
122
+ it 'should return game state' do
123
+ state = subject.get_state
124
+ state.should be_a(Hash)
125
+ state[:state].should eq(:finished)
126
+ state[:available_calls].should eq([])
127
+ state[:auction].size.should eq(4)
128
+ state[:turn].should eq(nil)
129
+ state[:play].should eq(nil)
130
+ end
131
+ end
132
+
133
+ context "auctioned" do
134
+ before {
135
+ subject.start!(board)
136
+ turn = board.dealer # Avoid calling getTurn.
137
+ calls.each do |c|
138
+ players[turn].make_call(c) # Each player passes.
139
+ turn = Direction[(turn+1) % Direction.size]
140
+ end
141
+ }
142
+
143
+ it 'should return game state' do
144
+ state = subject.get_state
145
+ state.should be_a(Hash)
146
+ state[:auction].size.should eq(13)
147
+ state[:available_calls].should eq([])
148
+ state[:play].should be_a(Hash)
149
+ state[:state].should eq(:playing)
150
+ state[:turn].should eq(2) # this is now game turn
151
+ Strain.name(state[:play][:trumps]).should eq('heart')
152
+ state[:contract][:bid].to_s.should eq('two heart')
153
+ Direction.name(state[:play][:declarer]).should eq('east')
154
+ Direction.name(state[:play][:dummy]).should eq('west')
155
+ state[:play][:declarer_trick_count].should eq(0)
156
+ state[:play][:defender_trick_count].should eq(0)
157
+ state[:play][:tricks].should be_a(Array)
158
+ state[:play][:tricks].size.should eq(0)
159
+ end
160
+
161
+ it 'should return well-formed json state' do
162
+ state = JSON.parse(subject.get_state.to_json)
163
+ state['state'].should eq('playing')
164
+ state['auction'].size.should eq(13)
165
+ state['auction'].first.should eq('pass')
166
+ end
167
+
168
+ context 'in play' do
169
+ before {
170
+ @turn = subject.get_turn
171
+ player = players[@turn]
172
+ @card = player.get_hand.sample
173
+ player.play_card(@card) # play a random card
174
+ }
175
+
176
+ it 'should include public cards' do
177
+ state = subject.get_state
178
+ # go to json and test that for sanity
179
+ state = JSON.parse(state.to_json)
180
+ dummy = state['play']['dummy']
181
+ dummy.should eq(3)
182
+ state['play']['played'][@turn.to_s].first.should eq(@card.to_s)
183
+ state['board']['deal'][dummy.to_s].should eq(subject.get_hand(dummy).map { |c| c.to_s })
184
+ end
185
+
186
+ it 'can perform a claim issued by a defender' do
187
+ subject.claim(Direction.north, 9)
188
+ subject.results.size.should eq(1)
189
+ subject.state.should eq(:finished)
190
+ result = subject.results.first
191
+ result.claimed_by.should eq(Direction.north)
192
+ result.claimed.should eq(9)
193
+ result.tricks_made.should eq(4)
194
+ result.score.should eq(-400)
195
+ end
196
+
197
+ it 'can perform a zero claim issued by a defender' do
198
+ subject.claim(Direction.north, 0)
199
+ subject.results.size.should eq(1)
200
+ subject.state.should eq(:finished)
201
+ result = subject.results.first
202
+ result.claimed_by.should eq(Direction.north)
203
+ result.claimed.should eq(0)
204
+ result.tricks_made.should eq(13)
205
+ result.score.should eq(260)
206
+ end
207
+
208
+ it 'can perform a claim issued by a declarer' do
209
+ subject.claim(Direction.east, 9)
210
+ subject.results.size.should eq(1)
211
+ subject.state.should eq(:finished)
212
+ state = subject.get_state
213
+ result = subject.results.first
214
+ result.claimed_by.should eq(Direction.east)
215
+ result.claimed.should eq(9)
216
+ result.tricks_made.should eq(9)
217
+ result.score.should eq(140)
218
+ end
219
+
220
+ it 'can perform a zero claim issued by a declarer' do
221
+ subject.claim(Direction.east, 0)
222
+ subject.results.size.should eq(1)
223
+ subject.state.should eq(:finished)
224
+ state = subject.get_state
225
+ result = subject.results.first
226
+ result.claimed_by.should eq(Direction.east)
227
+ result.claimed.should eq(0)
228
+ result.tricks_made.should eq(0)
229
+ result.score.should eq(-800)
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ # All players pass, game should finish without reaching play
236
+ it 'should finish without reaching play if passed out' do
237
+ subject.start!(board)
238
+ turn = board.dealer # Avoid calling getTurn.
239
+ Direction.each do |i| # Iterate for each player.
240
+ players[i].make_call(Pass.new) # Each player passes.
241
+ turn = Direction[(turn+1) % Direction.size]
242
+ end
243
+ turn.should eq(board.dealer) # Sanity check.
244
+
245
+ # Bidding is passed out - game is over.
246
+ subject.in_progress?.should eq(false)
247
+ subject.state.should eq(:finished)
248
+ expect { players[turn].make_call(Bid.new(Level.one, Strain.club)) }.to raise_error
249
+ expect { players[turn].play_card(board.deal.hands[turn].first) }.to raise_error
250
+ end
251
+
252
+ # Play through a sample game.
253
+ # This does not attempt to test the integrity of Bidding and Play.
254
+ it 'should play from start to finish' do
255
+ calls = Level.map do |l| # all available action bids
256
+ Strain.map { |s| Bid.new(l,s) }
257
+ end
258
+
259
+ calls << [Pass.new, Pass.new, Pass.new] # ...plus 3 passes
260
+
261
+ subject.start!(board)
262
+
263
+ calls.flatten.each do |call| # make em all
264
+ turn = subject.get_turn
265
+ players[turn].make_call(call)
266
+ end
267
+
268
+ subject.state.should eq(:playing)
269
+ subject.auction.complete?.should eq(true)
270
+ subject.play.should_not eq(nil)
271
+
272
+ while not subject.play.complete?
273
+ subject.state.should eq(:playing)
274
+ turn = subject.get_turn
275
+ # Find a valid card.
276
+ board.deal[turn].each do |card|
277
+ if subject.play.valid_play?(card, turn, board.deal[turn])
278
+ if turn == subject.play.dummy
279
+ self.players[subject.play.declarer].play_card(card).should eq(true)
280
+ else
281
+ self.players[turn].play_card(card).should eq(true)
282
+ end
283
+ break
284
+ end
285
+ end
286
+ end
287
+ subject.state.should eq(:finished)
288
+ subject.in_progress?.should eq(false) # Game complete.
289
+ end
290
+ end
291
+ end