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.
@@ -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
@@ -1,3 +1,4 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
+ Gemfile.lock
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ notifications:
3
+ disabled: true
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
 
3
- gem "rake"
4
- gem "test-unit", ">=2", :require => "test/unit"
3
+ gemspec
@@ -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
- require "bundler"
2
- Bundler::GemHelper.install_tasks
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/**/test_*.rb"
7
+ test.pattern = "test/**/*_test.rb"
9
8
  test.verbose = true
10
9
  end
11
10
 
@@ -1,23 +1,24 @@
1
- # -*- encoding: utf-8 -*-
2
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
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 |s|
6
- s.name = "bridge"
7
- s.version = Bridge::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.summary = "Contract bridge utilities"
10
- s.description = "Useful contract bridge utilities - deal generator, id to deal and deal to id conversion"
11
- s.email = "qoobaa+github@gmail.com"
12
- s.homepage = "http://github.com/qoobaa/bridge"
13
- s.authors = ["Jakub Kuźma", "Wojciech Wnętrzak"]
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
- s.required_rubygems_version = ">= 1.3.6"
16
- s.rubyforge_project = "bridge"
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
- s.add_development_dependency "bundler", ">= 1.0.0"
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
@@ -1,7 +1,9 @@
1
+ require "bridge/constants"
1
2
  require "bridge/bid"
2
3
  require "bridge/card"
3
- require "bridge/constants"
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
@@ -12,7 +12,7 @@ module Bridge
12
12
 
13
13
  # Returns the level of the bid
14
14
  def level
15
- bid[0..0] if contract?
15
+ bid[0] if contract?
16
16
  end
17
17
 
18
18
  # Returns the suit of the bid
@@ -12,12 +12,12 @@ module Bridge
12
12
 
13
13
  # Returns the suit of the card
14
14
  def suit
15
- card[0..0]
15
+ card[0]
16
16
  end
17
17
 
18
18
  # Returns the suit of the card
19
19
  def value
20
- card[1..1]
20
+ card[1]
21
21
  end
22
22
 
23
23
  # Returns the honour card points
@@ -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 => 0,
57
- 20..40 => 1,
58
- 50..80 => 2,
59
- 90..120 => 3,
60
- 130..160 => 4,
61
- 170..210 => 5,
62
- 220..260 => 6,
63
- 270..310 => 7,
64
- 320..360 => 8,
65
- 370..420 => 9,
66
- 430..490 => 10,
67
- 500..590 => 11,
68
- 600..740 => 12,
69
- 750..890 => 13,
70
- 900..1090 => 14,
71
- 1100..1290 => 15,
72
- 1300..1490 => 16,
73
- 1500..1740 => 17,
74
- 1750..1990 => 18,
75
- 2000..2240 => 19,
76
- 2250..2490 => 20,
77
- 2500..2990 => 21,
78
- 3000..3490 => 22,
79
- 3500..3990 => 23,
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
@@ -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
- # use map to be 1.8.6 compatible
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.inject({}) do |h, direction|
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.inject({}) do |sorted, direction|
141
+ DIRECTIONS.each_with_object({}) do |direction, sorted|
145
142
  splitted_colors = cards_for(direction)
146
- splitted_colors.reject! { |trump, cards| cards.empty? }
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.inject({}) do |colors, trump|
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
 
@@ -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