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