bridge 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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