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
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OWRjMmJjOGYwNTVlZmU3OGQ3ZDVkNTlhODg5NWVmOWZhMjllMGE3MA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGI1YTBkY2QwZjI0NjcwMDIyMzYxMDE1NzQzZDRkOTYzYTdmYTU5Ng==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OGRmMDkzZjMzNGVjYjMxY2E4OTFlNTBjNzMwMzY1YmRhYjU0ZDQ3YTFmZTE2
|
10
|
+
ZDNjOWMzYzA0ZWY0NjY5MDM0NWZmODllZjM0NmU5NzEwZGI3MjhjMjBkYWQy
|
11
|
+
YmZhYjdmZWQ0NzdhZmZkNjlhMGM3MTE4MzZmM2FiOTk2YWQ2MmE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGY2OGRiOGIzYTJkMzg5MGZhNzcxODdjMWRhMGQwYmU5NTYyMWVkYTU3NjIz
|
14
|
+
MjhlNDU5NjRiZTk5ZjcyNzA1YzE5ODZiZjcwN2RjMTEzYmM1MDEwM2YwZmMz
|
15
|
+
NjgzNjM3MzcyNTU2OGVmY2Y3OGI1NjQzMjRlODE2MjFjZGE5MjI=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Achilles Charmpilas
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Bridge
|
2
|
+
|
3
|
+
A lean mean bridge playing machine.
|
4
|
+
Large portions of this gem have been ported from [pybridge](http://sourceforge.net/projects/pybridge/)
|
5
|
+
|
6
|
+
Bridge is the bridge playing engine sitting behind the Leonardo Bridge API.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
#!ruby
|
13
|
+
gem 'bridge'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install bridge
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
You can have a look at `bin/play` for an example of interaction with the Bridge::Game class.
|
26
|
+
|
27
|
+
Here's a quick run-through:
|
28
|
+
|
29
|
+
#!ruby
|
30
|
+
require 'rubygems'
|
31
|
+
require 'bridge'
|
32
|
+
include Bridge
|
33
|
+
|
34
|
+
game = Game.new # start game
|
35
|
+
players = [] # keep players somewhere handy
|
36
|
+
Direction.each { |d| players[d] << game.add_player(d) }
|
37
|
+
# you're ready to start playing
|
38
|
+
|
39
|
+
|
40
|
+
## Contributing
|
41
|
+
|
42
|
+
1. Fork it
|
43
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
44
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
45
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
46
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/leo-console
ADDED
data/bin/leo-play
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require './lib/bridge'
|
3
|
+
|
4
|
+
[:INT, :TERM].each do |sig|
|
5
|
+
trap(sig) {
|
6
|
+
puts clear_line
|
7
|
+
puts 'Shutting down, bye!'.yellow
|
8
|
+
exit
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def cursor
|
13
|
+
'> '
|
14
|
+
end
|
15
|
+
|
16
|
+
def put string, prompt = '> '
|
17
|
+
puts cursor + string
|
18
|
+
end
|
19
|
+
|
20
|
+
def put_contract c
|
21
|
+
if c
|
22
|
+
#puts c.inspect.red
|
23
|
+
print "#{Direction.name(c.declarer)} won auction with ".yellow
|
24
|
+
print "#{Level.name(c.bid.level)} #{Strain.name(c.bid.strain)}".yellow
|
25
|
+
print "double by #{Direction.name(c.double_by)}".yellow unless c.double_by.nil?
|
26
|
+
print "redouble by #{Direction.name(c.redouble_by)}".yellow unless c.redouble_by.nil?
|
27
|
+
puts ""
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
include Bridge
|
32
|
+
@history = []
|
33
|
+
@game = Game.new
|
34
|
+
@players = []
|
35
|
+
Direction.each do |position|
|
36
|
+
@players[position] = @game.add_player(position)
|
37
|
+
end
|
38
|
+
@game.start!(Board.first)
|
39
|
+
|
40
|
+
puts clear_screen
|
41
|
+
puts 'Started game on first board'.green
|
42
|
+
puts "\t#{Direction.name(@game.board.dealer)} deals".white
|
43
|
+
puts "\t#{Vulnerability.name(@game.board.vulnerability)} is vulnerable".white
|
44
|
+
|
45
|
+
|
46
|
+
print "#{Direction.name(@game.get_turn).to_s.upcase}> " if @game.in_progress?
|
47
|
+
|
48
|
+
def rush_auction
|
49
|
+
@players[@game.get_turn].make_call(Call.from_string('bid two club'))
|
50
|
+
@players[@game.get_turn].make_call(Pass.new)
|
51
|
+
@players[@game.get_turn].make_call(Pass.new)
|
52
|
+
@players[@game.get_turn].make_call(Pass.new)
|
53
|
+
|
54
|
+
put_contract @game.auction.contract
|
55
|
+
end
|
56
|
+
|
57
|
+
def rush_trick
|
58
|
+
@players[@game.get_turn].play_card(@players[@game.get_turn].get_hand.sample)
|
59
|
+
|
60
|
+
3.times do
|
61
|
+
if @game.get_turn == @game.play.dummy
|
62
|
+
player = @players[@game.play.declarer]
|
63
|
+
hand = @players[@game.play.dummy].get_hand
|
64
|
+
else
|
65
|
+
player = @players[@game.get_turn]
|
66
|
+
hand = @players[@game.get_turn].get_hand
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
player.play_card(hand.sample)
|
71
|
+
rescue Bridge::GameError => e
|
72
|
+
puts e.message.red
|
73
|
+
retry
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
while cmd = gets.strip
|
80
|
+
case cmd
|
81
|
+
when 'bye','exit','leave'
|
82
|
+
break
|
83
|
+
when 'rush trick' # play an automatic trick
|
84
|
+
rush_auction unless @game.auction.complete?
|
85
|
+
rush_trick
|
86
|
+
when 'rush auction' # play a canned auction
|
87
|
+
rush_auction
|
88
|
+
when 'history'
|
89
|
+
put 'Move history:'.yellow
|
90
|
+
put @history.join("\n")
|
91
|
+
|
92
|
+
when 'hand'
|
93
|
+
put @game.get_hand(@game.get_turn).inspect.white
|
94
|
+
print cursor
|
95
|
+
|
96
|
+
else
|
97
|
+
ary = cmd.split
|
98
|
+
if @game.in_progress?
|
99
|
+
position_i = @game.get_turn
|
100
|
+
position = Direction.name(position_i)
|
101
|
+
@history << [position, ary.join(' ')]
|
102
|
+
|
103
|
+
if @game.auction.complete?
|
104
|
+
card = nil
|
105
|
+
|
106
|
+
begin
|
107
|
+
if ary.size == 2 # overriding player
|
108
|
+
position = ary.first
|
109
|
+
position_i = Direction.send(position.to_sym)
|
110
|
+
card = Card.from_string(ary.last)
|
111
|
+
else
|
112
|
+
card = Card.from_string(ary.first)
|
113
|
+
end
|
114
|
+
|
115
|
+
@players[position_i].play_card(card)
|
116
|
+
print "#{position.white} plays #{card.to_s.magenta} ~> "
|
117
|
+
puts @game.play.get_current_trick.cards.compact.inspect.white
|
118
|
+
rescue Exception => e
|
119
|
+
put e.message.red
|
120
|
+
end
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
@players[position_i].make_call(Call.from_string(ary.join(' ')))
|
124
|
+
put "#{position.white} calls #{ary.join(' ').green}"
|
125
|
+
rescue Exception => e
|
126
|
+
put e.message.red
|
127
|
+
end
|
128
|
+
|
129
|
+
if @game.auction.passed_out?
|
130
|
+
put 'Auction phase complete, @game passed out'.red
|
131
|
+
exit
|
132
|
+
elsif @game.auction.complete?
|
133
|
+
put 'Auction phase complete'.yellow
|
134
|
+
put_contract @game.auction.contract
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end # if @game.in_progress?
|
138
|
+
end
|
139
|
+
|
140
|
+
print "#{Direction.name(@game.get_turn).to_s.upcase}> " if @game.in_progress?
|
141
|
+
end
|
142
|
+
|
143
|
+
puts 'Shutting down, bye!'.yellow
|
data/bridge.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bridge/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "leonardo-bridge"
|
8
|
+
spec.version = Bridge::VERSION
|
9
|
+
spec.authors = ["Achilles Charmpilas"]
|
10
|
+
spec.email = ["ac@humbuckercode.co.uk"]
|
11
|
+
spec.description = %q{A lean mean bridge playing machine}
|
12
|
+
spec.summary = %q{Encapsulates all the necessary logic that allows 4 players to play a bridge game. Also supports rubber scoring.}
|
13
|
+
spec.homepage = "http://bridge.leonardogames.net/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
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.add_dependency "redis"
|
21
|
+
spec.add_dependency "nutrun-string"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "fuubar"
|
25
|
+
spec.add_development_dependency "geminabox"
|
26
|
+
spec.add_development_dependency "gem-release"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
end
|
data/bridge.rc.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
IRB.conf[:PROMPT][:CUSTOM] = {
|
2
|
+
:PROMPT_I => "Bridge >> ",
|
3
|
+
:PROMPT_S => "%l>> ",
|
4
|
+
:PROMPT_C => ">>",
|
5
|
+
:PROMPT_N => ">>",
|
6
|
+
:RETURN => "=> %s\n"
|
7
|
+
}
|
8
|
+
IRB.conf[:PROMPT_MODE] = :CUSTOM
|
9
|
+
IRB.conf[:AUTO_INDENT] = true
|
10
|
+
include Bridge
|
11
|
+
puts ">> I am Bridge Console".white.bg_black
|
data/lib/bridge.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'pathname'
|
4
|
+
require 'nutrun-string'
|
5
|
+
|
6
|
+
root = File.dirname(__FILE__)
|
7
|
+
|
8
|
+
%W{ uuid redis_model enum }.each { |r| require File.join(root,r) }
|
9
|
+
|
10
|
+
module Bridge
|
11
|
+
DEBUG = false
|
12
|
+
|
13
|
+
module Direction
|
14
|
+
extend Enum
|
15
|
+
set_values :north, :east, :south, :west
|
16
|
+
end
|
17
|
+
|
18
|
+
module Vulnerability
|
19
|
+
extend Enum
|
20
|
+
set_values :none, :north_south, :east_west, :all
|
21
|
+
end
|
22
|
+
|
23
|
+
def root
|
24
|
+
File.dirname(__FILE__)
|
25
|
+
end
|
26
|
+
|
27
|
+
def assert_card card
|
28
|
+
raise CardError, "Card #{card.inspect} is not valid" unless card.is_a?(Card)
|
29
|
+
end
|
30
|
+
|
31
|
+
module_function :assert_card
|
32
|
+
public :assert_card
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir[File.join(root,'bridge','*.rb')].each { |r| require r }
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Bridge
|
2
|
+
class DuplicateCallError < StandardError; end
|
3
|
+
class InvalidCallError < StandardError; end
|
4
|
+
class InvalidCallClassError < StandardError; end
|
5
|
+
|
6
|
+
# The auction (bidding phase) of a game of bridge.
|
7
|
+
# Swiped from: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/auction.py
|
8
|
+
class Auction
|
9
|
+
attr_accessor :dealer, :calls, :contract
|
10
|
+
|
11
|
+
# @param dealer: who distributes the cards and makes the first call.
|
12
|
+
# @type dealer: Direction
|
13
|
+
def initialize dealer
|
14
|
+
self.dealer = dealer
|
15
|
+
self.contract = nil
|
16
|
+
self.calls = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Auction is complete if all players have called (ie. 4 or more calls)
|
20
|
+
# and the last 3 calls are Pass calls.
|
21
|
+
# @return: True if bidding is complete, False if not.
|
22
|
+
# @rtype: bool
|
23
|
+
def complete?
|
24
|
+
passes = self.calls.last(3).select { |c| c.is_a?(Pass) }.size
|
25
|
+
self.calls.size >= 4 and passes == 3
|
26
|
+
end
|
27
|
+
|
28
|
+
# Auction is passed out if each player has passed on their first turn.
|
29
|
+
# In this case, the bidding is complete, but no contract is established.
|
30
|
+
# @return: True if bidding is passed out, False if not.
|
31
|
+
# @rtype: bool
|
32
|
+
def passed_out?
|
33
|
+
passes = calls.select { |c| c.is_a?(Pass) }.size
|
34
|
+
self.calls.size == 4 and passes == 4
|
35
|
+
end
|
36
|
+
|
37
|
+
# When the bidding is complete, the contract is the last and highest
|
38
|
+
# bid, which may be doubled or redoubled.
|
39
|
+
# Hence, the contract represents the "final state" of the bidding.
|
40
|
+
# @return: a dict containing the keywords:
|
41
|
+
# @keyword bid: the last and highest bid.
|
42
|
+
# @keyword declarer: the partner who first bid the contract strain.
|
43
|
+
# @keyword doubleBy: the opponent who doubled the contract, or None.
|
44
|
+
# @keyword redoubleBy: the partner who redoubled an opponent's double
|
45
|
+
# on the contract, or None.
|
46
|
+
def get_contract
|
47
|
+
if self.complete? and not self.passed_out?
|
48
|
+
bid = self.get_current_call(Bid)
|
49
|
+
double = self.get_current_call(Double)
|
50
|
+
redouble = self.get_current_call(Redouble)
|
51
|
+
declarer_bid = nil
|
52
|
+
# Determine partnership.
|
53
|
+
caller = self.who_called?(bid)
|
54
|
+
partnership = [caller, Direction[(caller + 2) % 4]]
|
55
|
+
# Determine declarer.
|
56
|
+
self.calls.each do |call|
|
57
|
+
if call.is_a?(Bid) and call.strain == bid.strain and partnership.include?(self.who_called?(call))
|
58
|
+
declarer_bid = call
|
59
|
+
break
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
{
|
64
|
+
:bid => bid,
|
65
|
+
:declarer => declarer_bid.nil? ? nil : self.who_called?(declarer_bid),
|
66
|
+
:double_by => double.nil? ? nil : self.who_called?(double),
|
67
|
+
:redouble_by => redouble.nil? ? nil : self.who_called?(redouble)
|
68
|
+
}
|
69
|
+
else
|
70
|
+
nil # Bidding passed out or not complete, no contract.
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Appends call from position to the calls list.
|
75
|
+
# Please note that call validity should be checked with isValidCall()
|
76
|
+
# before calling this method!
|
77
|
+
# @param call: a candidate call.
|
78
|
+
def make_call call, player = nil
|
79
|
+
assert_call(call.class)
|
80
|
+
# Calls must be distinct.
|
81
|
+
raise InvalidCallError, "#{call.inspect} is invalid" unless self.valid_call?(call)
|
82
|
+
|
83
|
+
self.calls << call
|
84
|
+
if self.complete? and not self.passed_out?
|
85
|
+
self.contract = Contract.new(self)
|
86
|
+
end
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check that call can be made, according to the rules of bidding.
|
91
|
+
# @param call: the candidate call.
|
92
|
+
# @param position: if specified, the position from which the call is made.
|
93
|
+
# @return: True if call is available, False if not.
|
94
|
+
def valid_call? call, position = nil
|
95
|
+
# The bidding must not be complete.
|
96
|
+
return false if complete?
|
97
|
+
|
98
|
+
# Position's turn to play.
|
99
|
+
return false if position and position != whose_turn
|
100
|
+
|
101
|
+
# A pass is always available.
|
102
|
+
return true if call.is_a?(Pass)
|
103
|
+
|
104
|
+
# A bid must be greater than the current bid.
|
105
|
+
if call.is_a?(Bid)
|
106
|
+
return (!current_bid or call > current_bid)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Doubles and redoubles only when a bid has been made.
|
110
|
+
if current_bid
|
111
|
+
bidder = who_called?(current_bid)
|
112
|
+
|
113
|
+
# A double must be made on the current bid from opponents,
|
114
|
+
# which has not been already doubled by partnership.
|
115
|
+
if call.is_a?(Double)
|
116
|
+
opposition = [Direction[(whose_turn + 1) % 4], Direction[(whose_turn + 3) % 4]]
|
117
|
+
return (opposition.include?(bidder) and !current_double)
|
118
|
+
|
119
|
+
# A redouble must be made on the current bid from partnership,
|
120
|
+
# which has been doubled by an opponent.
|
121
|
+
elsif call.is_a?(Redouble)
|
122
|
+
partnership = [whose_turn, Direction[(whose_turn + 2) % 4]]
|
123
|
+
return (partnership.include?(bidder) and current_double and !current_redouble)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
false # Otherwise unavailable.
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the position from which the specified call was made.
|
131
|
+
# @param call: a call made in the auction.
|
132
|
+
# @return: the position of the player who made call, or None.
|
133
|
+
def who_called? call
|
134
|
+
raise ArgumentError, "#{call.inspect} is not a call" unless call.is_a?(Call)
|
135
|
+
return nil unless calls.include?(call) # Call not made by any player.
|
136
|
+
Direction[(self.dealer + calls.find_index(call)) % 4]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the position from which the next call should be made.
|
140
|
+
# @return: the next position to make a call, or None.
|
141
|
+
def whose_turn
|
142
|
+
return nil if complete?
|
143
|
+
return Direction[(self.dealer + calls.size) % 4]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns most recent current call of specified class, or None.
|
147
|
+
# @param callclass: call class, in (Bid, Pass, Double, Redouble).
|
148
|
+
# @return: most recent call matching type, or None.
|
149
|
+
def get_current_call callclass
|
150
|
+
assert_call(callclass)
|
151
|
+
|
152
|
+
self.calls.reverse.each do |call|
|
153
|
+
if call.is_a?(callclass)
|
154
|
+
return call
|
155
|
+
elsif call.is_a?(Bid)
|
156
|
+
break # Bids cancel all preceding calls.
|
157
|
+
end
|
158
|
+
end
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def current_bid
|
163
|
+
get_current_call(Bid)
|
164
|
+
end
|
165
|
+
|
166
|
+
def current_double
|
167
|
+
get_current_call(Double)
|
168
|
+
end
|
169
|
+
|
170
|
+
def current_redouble
|
171
|
+
get_current_call(Redouble)
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_call callclass
|
175
|
+
raise InvalidCallClassError unless [Bid, Pass, Double, Redouble].include?(callclass)
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_a
|
179
|
+
self.calls
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|