bridge 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -3
- data/README.md +56 -0
- data/Rakefile +3 -4
- data/bridge.gemspec +19 -18
- data/lib/bridge.rb +3 -1
- data/lib/bridge/auction.rb +77 -0
- data/lib/bridge/bid.rb +1 -1
- data/lib/bridge/card.rb +2 -2
- data/lib/bridge/constants.rb +42 -24
- data/lib/bridge/deal.rb +5 -10
- data/lib/bridge/play.rb +80 -0
- data/lib/bridge/score.rb +53 -47
- data/lib/bridge/trick.rb +3 -3
- data/lib/bridge/version.rb +1 -1
- data/test/auction_test.rb +133 -0
- data/test/bid_test.rb +135 -0
- data/test/bridge_test.rb +118 -0
- data/test/{test_card.rb → card_test.rb} +12 -12
- data/test/{test_chicago.rb → chicago_test.rb} +11 -11
- data/test/deal_test.rb +225 -0
- data/test/{test_duplicate.rb → duplicate_test.rb} +9 -9
- data/test/helper.rb +3 -6
- data/test/play_test.rb +102 -0
- data/test/score_test.rb +215 -0
- data/test/{test_trick.rb → trick_test.rb} +10 -10
- metadata +80 -73
- data/Gemfile.lock +0 -12
- data/README.rdoc +0 -62
- data/test/test_bid.rb +0 -135
- data/test/test_bridge.rb +0 -62
- data/test/test_deal.rb +0 -225
- data/test/test_score.rb +0 -330
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ca2211c5c71ea895f0b08a0935c1cde3bd1484f
|
4
|
+
data.tar.gz: 5b26a6b5e274921f8998f6a4a53534552fe7d339
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e362c05b670256d1775688b1ddef92cdce322d9f5f1fd16d1fb4be30739570ac4845ce8ade56fccfee0876eaedf65a3e6d72b3f34a09ac265a37b8bb047df2eb
|
7
|
+
data.tar.gz: a892d881741108bc8ec4e71b729c36fec846b826bb5121a7718df76bcdb469b7cb63f00b92817d32ee0788d5d1c27e011e2267ccd553d00e5afe06440a6ba2fa
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# bridge
|
2
|
+
|
3
|
+
Contract bridge utils.
|
4
|
+
|
5
|
+
## Bid
|
6
|
+
|
7
|
+
## Card
|
8
|
+
|
9
|
+
## Deal
|
10
|
+
|
11
|
+
## Trick
|
12
|
+
|
13
|
+
## Score
|
14
|
+
|
15
|
+
You can create Score object passing 3 arguments:
|
16
|
+
```
|
17
|
+
Bridge::Score.new("6NTXN", "BOTH", "=")
|
18
|
+
```
|
19
|
+
|
20
|
+
Arguments:
|
21
|
+
|
22
|
+
* contract -- String, where first sign is level, second suit (C D H S NT), optional double or redouble (X or XX) and declarer at the end
|
23
|
+
* vulnerable -- String, ["NONE", "EW", "NS", "BOTH"]
|
24
|
+
* tricks -- Integer or String, when Integer is passed it's number of tricks taken by declarer side, String can be relative to contract level i.e. "+1", "-2", "="
|
25
|
+
|
26
|
+
Methods:
|
27
|
+
|
28
|
+
* Score#made? -- Boolean
|
29
|
+
* Score#result -- Integer, relative to contract level i.e. -1 (one down), 1 (overtrick), 0 (contract made)
|
30
|
+
* Score#result_string -- String, relative to contract level i.e "+1", "=", "-3"
|
31
|
+
* Score#points -- Integer, calculated full value
|
32
|
+
|
33
|
+
You can also ask for all possible contracts finished with given points:
|
34
|
+
```
|
35
|
+
Bridge::Score.with_points(980)
|
36
|
+
#=> ["1NTX+4v", "2C/DX+4v", "6H/S="]
|
37
|
+
```
|
38
|
+
|
39
|
+
## Points
|
40
|
+
|
41
|
+
### Chicago
|
42
|
+
|
43
|
+
You can calculate how many IMP points you won by:
|
44
|
+
```
|
45
|
+
Bridge::Points::Chicago.new(:hcp => 25, :points => 420, :vulnerable => false).imps
|
46
|
+
```
|
47
|
+
|
48
|
+
Arguments:
|
49
|
+
|
50
|
+
* :hcp -- Integer (range between 20 and 40), which means how many honour card points has side
|
51
|
+
* :vulnerable -- Boolean, side with given hcp is vulnerable?
|
52
|
+
* :points -- Integer, the result at the end of board (can be calculated by Bridge::Score)
|
53
|
+
|
54
|
+
## Copyright
|
55
|
+
|
56
|
+
Copyright (c) 2010 Jakub Kuźma. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Bundler.setup
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
4
3
|
|
5
4
|
require "rake/testtask"
|
6
5
|
Rake::TestTask.new(:test) do |test|
|
7
6
|
test.libs << "lib" << "test"
|
8
|
-
test.pattern = "test
|
7
|
+
test.pattern = "test/**/*_test.rb"
|
9
8
|
test.verbose = true
|
10
9
|
end
|
11
10
|
|
data/bridge.gemspec
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
4
|
require "bridge/version"
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bridge"
|
8
|
+
spec.version = Bridge::VERSION
|
9
|
+
spec.summary = "Contract bridge utilities"
|
10
|
+
spec.description = "Useful contract bridge utilities - deal generator, id to deal and deal to id conversion"
|
11
|
+
spec.email = "qoobaa+github@gmail.com"
|
12
|
+
spec.homepage = "https://github.com/qoobaa/bridge"
|
13
|
+
spec.authors = ["Jakub Kuźma", "Wojciech Wnętrzak"]
|
14
|
+
spec.license = "MIT"
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = ">= 1.9.3"
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
s.files = `git ls-files`.split("\n")
|
21
|
-
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
22
|
-
s.require_path = 'lib'
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
23
24
|
end
|
data/lib/bridge.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
require "bridge/constants"
|
1
2
|
require "bridge/bid"
|
2
3
|
require "bridge/card"
|
3
|
-
require "bridge/
|
4
|
+
require "bridge/auction"
|
4
5
|
require "bridge/deal"
|
6
|
+
require "bridge/play"
|
5
7
|
require "bridge/points"
|
6
8
|
require "bridge/points/chicago"
|
7
9
|
require "bridge/points/duplicate"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Bridge
|
2
|
+
class Auction
|
3
|
+
attr_reader :dealer, :bids
|
4
|
+
|
5
|
+
def initialize(dealer, bids)
|
6
|
+
raise ArgumentError, "invalid direction: #{dealer}" unless Bridge.direction?(dealer)
|
7
|
+
@dealer = dealer
|
8
|
+
@bids = bids.map { |bid| Bid.new(bid.to_s) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def finished?
|
12
|
+
bids.length > 3 && bids[-3..-1].all?(&:pass?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def contract
|
16
|
+
if last_contract_index
|
17
|
+
modifier = bids[last_contract_index..-1].select(&:modifier?).last
|
18
|
+
bids[last_contract_index].to_s + modifier.to_s + declarer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def bid_allowed?(bid)
|
23
|
+
bid = Bid.new(bid.to_s)
|
24
|
+
return false if finished?
|
25
|
+
case
|
26
|
+
when bid.pass? then true
|
27
|
+
when bid.contract? then contract_allowed?(bid)
|
28
|
+
when bid.double? then double_allowed?
|
29
|
+
when bid.redouble? then redouble_allowed?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def declarer
|
34
|
+
direction = dealer
|
35
|
+
last_contract_index.times { direction = Bridge.next_direction(direction) }
|
36
|
+
direction
|
37
|
+
end
|
38
|
+
|
39
|
+
def directions
|
40
|
+
@directions ||= begin
|
41
|
+
return [] if bids.none?
|
42
|
+
directions = [dealer]
|
43
|
+
(bids.count - 1).times { directions << Bridge.next_direction(directions.last) }
|
44
|
+
directions
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def next_direction
|
49
|
+
@next_direction ||= directions.none? ? dealer : Bridge.next_direction(directions.last)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def last_contract_index
|
55
|
+
return @last_contract_index if defined?(@last_contract_index)
|
56
|
+
@last_contract_index = bids.rindex(&:contract?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def contract_allowed?(bid)
|
60
|
+
last_contract_index ? bids[last_contract_index] < bid : true
|
61
|
+
end
|
62
|
+
|
63
|
+
def double_allowed?
|
64
|
+
return false unless last_contract_index
|
65
|
+
after_contract_bids = bids[(last_contract_index + 1)..-1]
|
66
|
+
after_contract_bids.all?(&:pass?) && !after_contract_bids.one?
|
67
|
+
end
|
68
|
+
|
69
|
+
def redouble_allowed?
|
70
|
+
return false unless last_contract_index
|
71
|
+
return false unless last_double_index = bids.rindex(&:double?)
|
72
|
+
return false if last_double_index < last_contract_index
|
73
|
+
after_double_bids = bids[(last_double_index + 1)..-1]
|
74
|
+
after_double_bids.all?(&:pass?) && !after_double_bids.one?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/bridge/bid.rb
CHANGED
data/lib/bridge/card.rb
CHANGED
data/lib/bridge/constants.rb
CHANGED
@@ -50,33 +50,51 @@ module Bridge
|
|
50
50
|
# All possible vullnerabilites
|
51
51
|
VULNERABILITIES = ["NONE", SIDES, "BOTH"].flatten
|
52
52
|
|
53
|
+
# Matches 2S, 7NT, 1C
|
54
|
+
BID_REGEXP = Regexp.new %q{(?<bid>([1-7])([CDHS]|NT))}
|
55
|
+
|
56
|
+
# Matches X, XX
|
57
|
+
MODIFIER_REGEXP = Regexp.new %q{(?<modifier>(X{1,2}))}
|
58
|
+
|
59
|
+
# Matches X, XX
|
60
|
+
DIRECTION_REGEXP = Regexp.new %q{(?<direction>([NESW]))}
|
61
|
+
|
62
|
+
# Matches =, -12, +4
|
63
|
+
RESULT_REGEXP = Regexp.new %q{(?<result>(=|\+[1-6]|-\d{1,2}))}
|
64
|
+
|
65
|
+
# Matches 7NTXE, 1SXXS
|
66
|
+
CONTRACT_REGEXP = Regexp.new %Q{(?<contract>#{BID_REGEXP}#{MODIFIER_REGEXP}?#{DIRECTION_REGEXP})}
|
67
|
+
|
68
|
+
# Matches 1SE=, 7NTXXS-2
|
69
|
+
SCORE_REGEXP = Regexp.new %Q{(?<score>#{CONTRACT_REGEXP}#{RESULT_REGEXP})}
|
70
|
+
|
53
71
|
module Points
|
54
72
|
IMPS =
|
55
73
|
{
|
56
|
-
0..10
|
57
|
-
20..40
|
58
|
-
50..80
|
59
|
-
90..120
|
60
|
-
130..160
|
61
|
-
170..210
|
62
|
-
220..260
|
63
|
-
270..310
|
64
|
-
320..360
|
65
|
-
370..420
|
66
|
-
430..490
|
67
|
-
500..590
|
68
|
-
600..740
|
69
|
-
750..890
|
70
|
-
900..1090
|
71
|
-
1100..1290
|
72
|
-
1300..1490
|
73
|
-
1500..1740
|
74
|
-
1750..1990
|
75
|
-
2000..2240
|
76
|
-
2250..2490
|
77
|
-
2500..2990
|
78
|
-
3000..3490
|
79
|
-
3500..3990
|
74
|
+
0..10 => 0,
|
75
|
+
20..40 => 1,
|
76
|
+
50..80 => 2,
|
77
|
+
90..120 => 3,
|
78
|
+
130..160 => 4,
|
79
|
+
170..210 => 5,
|
80
|
+
220..260 => 6,
|
81
|
+
270..310 => 7,
|
82
|
+
320..360 => 8,
|
83
|
+
370..420 => 9,
|
84
|
+
430..490 => 10,
|
85
|
+
500..590 => 11,
|
86
|
+
600..740 => 12,
|
87
|
+
750..890 => 13,
|
88
|
+
900..1090 => 14,
|
89
|
+
1100..1290 => 15,
|
90
|
+
1300..1490 => 16,
|
91
|
+
1500..1740 => 17,
|
92
|
+
1750..1990 => 18,
|
93
|
+
2000..2240 => 19,
|
94
|
+
2250..2490 => 20,
|
95
|
+
2500..2990 => 21,
|
96
|
+
3000..3490 => 22,
|
97
|
+
3500..3990 => 23,
|
80
98
|
4000..15200 => 24
|
81
99
|
}
|
82
100
|
class Chicago
|
data/lib/bridge/deal.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Bridge
|
2
|
-
|
3
2
|
# Class representing bridge deal
|
4
3
|
class Deal
|
5
4
|
include Comparable
|
@@ -112,8 +111,7 @@ module Bridge
|
|
112
111
|
|
113
112
|
# Returns hash with hands
|
114
113
|
def to_hash
|
115
|
-
|
116
|
-
{ "N" => n.map{ |c| c.to_s }, "E" => e.map{ |c| c.to_s }, "S" => s.map{ |c| c.to_s }, "W" => w.map{ |c| c.to_s } }
|
114
|
+
{"N" => n.map(&:to_s), "E" => e.map(&:to_s), "S" => s.map(&:to_s), "W" => w.map(&:to_s)}
|
117
115
|
end
|
118
116
|
|
119
117
|
def inspect
|
@@ -121,9 +119,8 @@ module Bridge
|
|
121
119
|
end
|
122
120
|
|
123
121
|
def honour_card_points(side = nil)
|
124
|
-
hash = DIRECTIONS.
|
122
|
+
hash = DIRECTIONS.each_with_object({}) do |direction, h|
|
125
123
|
h[direction] = self[direction].inject(0) { |sum, card| sum += card.honour_card_points }
|
126
|
-
h
|
127
124
|
end
|
128
125
|
if side
|
129
126
|
side.to_s.upcase.split("").inject(0) { |sum, direction| sum += hash[direction] }
|
@@ -141,20 +138,18 @@ module Bridge
|
|
141
138
|
end
|
142
139
|
|
143
140
|
def sort_by_color(trump = nil)
|
144
|
-
DIRECTIONS.
|
141
|
+
DIRECTIONS.each_with_object({}) do |direction, sorted|
|
145
142
|
splitted_colors = cards_for(direction)
|
146
|
-
splitted_colors.reject! { |
|
143
|
+
splitted_colors.reject! { |color, cards| cards.empty? }
|
147
144
|
sorted_colors = sort_colors(splitted_colors.keys, trump)
|
148
145
|
sorted[direction] = sorted_colors.map { |color| splitted_colors.delete(color) }.flatten
|
149
|
-
sorted
|
150
146
|
end
|
151
147
|
end
|
152
148
|
|
153
149
|
def cards_for(direction)
|
154
|
-
TRUMPS.
|
150
|
+
TRUMPS.each_with_object({}) do |trump, colors|
|
155
151
|
cards = self[direction].select { |card| card.suit == trump }
|
156
152
|
colors[trump] = cards
|
157
|
-
colors
|
158
153
|
end
|
159
154
|
end
|
160
155
|
|
data/lib/bridge/play.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Bridge
|
2
|
+
class Play
|
3
|
+
attr_reader :deal_id, :contract, :cards
|
4
|
+
|
5
|
+
def initialize(deal_id, contract, cards)
|
6
|
+
@deal_id = deal_id
|
7
|
+
@contract = contract
|
8
|
+
@cards = cards.map { |card| Card.new(card.to_s) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def finished?
|
12
|
+
cards.length == 52
|
13
|
+
end
|
14
|
+
|
15
|
+
def declarer
|
16
|
+
contract[-1] if contract
|
17
|
+
end
|
18
|
+
|
19
|
+
def dummy
|
20
|
+
Bridge.partner_of(declarer) if contract
|
21
|
+
end
|
22
|
+
|
23
|
+
def left_hand_opponent
|
24
|
+
Bridge.next_direction(declarer) if contract
|
25
|
+
end
|
26
|
+
alias :lho :left_hand_opponent
|
27
|
+
|
28
|
+
def right_hand_opponent
|
29
|
+
Bridge.partner_of(left_hand_opponent) if contract
|
30
|
+
end
|
31
|
+
alias :rho :right_hand_opponent
|
32
|
+
|
33
|
+
def trump
|
34
|
+
contract[1] if Bridge.trump?(contract[1])
|
35
|
+
end
|
36
|
+
|
37
|
+
def card_allowed?(card)
|
38
|
+
card = Card.new(card.to_s)
|
39
|
+
case
|
40
|
+
when !contract, cards.include?(card), !deal[next_direction].include?(card) then false
|
41
|
+
when tricks.none?, tricks.last.complete? then true
|
42
|
+
else (deal[next_direction] - cards).map(&:suit).uniq.include?(last_lead.suit) ? card.suit == last_lead.suit : true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def directions
|
47
|
+
@directions ||= tricks.map { |trick| trick.cards }.flatten.map { |card| deal.owner(card) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def next_direction
|
51
|
+
case
|
52
|
+
when tricks.none? then Bridge.next_direction(declarer)
|
53
|
+
when tricks.last.complete? then deal.owner(tricks.last.winner(trump))
|
54
|
+
else Bridge.next_direction(directions.last)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def declarer_tricks_number
|
59
|
+
tricks.map { |trick| deal.owner(trick.winner(trump)) }.count { |direction| [declarer, dummy].include?(direction) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def deal
|
63
|
+
@deal ||= Deal.from_id(deal_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
def tricks
|
67
|
+
@tricks ||= begin
|
68
|
+
tricks = []
|
69
|
+
cards.each_slice(4) { |trick| tricks << Trick.new(*trick) }
|
70
|
+
tricks
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def last_lead
|
77
|
+
@last_lead ||= tricks.last.cards.first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|