bridge 0.1.4 → 0.2.0
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 +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
|