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.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +1 -0
- data/bin/leo-console +7 -0
- data/bin/leo-play +143 -0
- data/bridge.gemspec +29 -0
- data/bridge.rc.rb +11 -0
- data/lib/bridge.rb +35 -0
- data/lib/bridge/auction.rb +182 -0
- data/lib/bridge/board.rb +84 -0
- data/lib/bridge/call.rb +98 -0
- data/lib/bridge/card.rb +54 -0
- data/lib/bridge/contract.rb +51 -0
- data/lib/bridge/db.rb +27 -0
- data/lib/bridge/deal.rb +33 -0
- data/lib/bridge/deck.rb +22 -0
- data/lib/bridge/game.rb +372 -0
- data/lib/bridge/hand.rb +25 -0
- data/lib/bridge/leonardo_result.rb +7 -0
- data/lib/bridge/player.rb +49 -0
- data/lib/bridge/result.rb +290 -0
- data/lib/bridge/trick.rb +28 -0
- data/lib/bridge/trick_play.rb +219 -0
- data/lib/bridge/version.rb +3 -0
- data/lib/enum.rb +32 -0
- data/lib/redis_model.rb +137 -0
- data/lib/uuid.rb +280 -0
- data/spec/auction_spec.rb +100 -0
- data/spec/board_spec.rb +19 -0
- data/spec/bridge_spec.rb +25 -0
- data/spec/call_spec.rb +44 -0
- data/spec/card_spec.rb +95 -0
- data/spec/db_spec.rb +19 -0
- data/spec/deck_spec.rb +14 -0
- data/spec/enum_spec.rb +14 -0
- data/spec/game_spec.rb +291 -0
- data/spec/hand_spec.rb +21 -0
- data/spec/player_spec.rb +22 -0
- data/spec/redis_model_spec.rb +50 -0
- data/spec/result_spec.rb +64 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/auction_helper.rb +9 -0
- data/spec/support/test_enum.rb +5 -0
- data/spec/support/test_model.rb +3 -0
- data/spec/trick_play_spec.rb +90 -0
- data/spec/trick_spec.rb +15 -0
- 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
|
data/spec/board_spec.rb
ADDED
@@ -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
|
data/spec/bridge_spec.rb
ADDED
@@ -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
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
|