leonardo-bridge 0.4.3 → 0.6.5

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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OWRjMmJjOGYwNTVlZmU3OGQ3ZDVkNTlhODg5NWVmOWZhMjllMGE3MA==
5
- data.tar.gz: !binary |-
6
- OGI1YTBkY2QwZjI0NjcwMDIyMzYxMDE1NzQzZDRkOTYzYTdmYTU5Ng==
2
+ SHA1:
3
+ metadata.gz: 9be6e3140176b3db2048e72737b9f3e2a4530b38
4
+ data.tar.gz: 7c9fe92ceab4462a2aebf9ae3c6d5937cf347f80
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OGRmMDkzZjMzNGVjYjMxY2E4OTFlNTBjNzMwMzY1YmRhYjU0ZDQ3YTFmZTE2
10
- ZDNjOWMzYzA0ZWY0NjY5MDM0NWZmODllZjM0NmU5NzEwZGI3MjhjMjBkYWQy
11
- YmZhYjdmZWQ0NzdhZmZkNjlhMGM3MTE4MzZmM2FiOTk2YWQ2MmE=
12
- data.tar.gz: !binary |-
13
- OGY2OGRiOGIzYTJkMzg5MGZhNzcxODdjMWRhMGQwYmU5NTYyMWVkYTU3NjIz
14
- MjhlNDU5NjRiZTk5ZjcyNzA1YzE5ODZiZjcwN2RjMTEzYmM1MDEwM2YwZmMz
15
- NjgzNjM3MzcyNTU2OGVmY2Y3OGI1NjQzMjRlODE2MjFjZGE5MjI=
6
+ metadata.gz: df553f217643aad19ae9253be133c5a38b308544cf3e0438284d19af649d7b4f0a681d7128340b04d26f48d05c0e6c4553d453660e03cbda433cd350bde5e771
7
+ data.tar.gz: 7422db23f08aeba5d8df54f5da27318664bacafa06bc77461f77552319ef4de518e3e67ffe5182914116f76e0dbb25865d62a3eacfa30a3018199f3ba924a0c5
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Achilles Charmpilas
1
+ Copyright (c) 2018 Achilles Charmpilas
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Bridge
2
+ Bridge is the bridge playing engine sitting behind [Leo Bridge](https://leobridge.net/) this is the first version initially built in 2013 and is shared here as a working game engine example.
2
3
 
3
- A lean mean bridge playing machine.
4
- Large portions of this gem have been ported from [pybridge](http://sourceforge.net/projects/pybridge/)
4
+ You can use it to experiment with building a card game UI, or however else you wish.
5
5
 
6
- Bridge is the bridge playing engine sitting behind the Leonardo Bridge API.
6
+ Large portions of this gem have been ported from [pybridge](http://sourceforge.net/projects/pybridge/)
7
7
 
8
8
  ## Installation
9
9
 
10
10
  Add this line to your application's Gemfile:
11
11
 
12
12
  #!ruby
13
- gem 'bridge'
13
+ gem 'leonardo-bridge', require: 'bridge'
14
14
 
15
15
  And then execute:
16
16
 
@@ -18,11 +18,14 @@ And then execute:
18
18
 
19
19
  Or install it yourself as:
20
20
 
21
- $ gem install bridge
21
+ $ gem install leonardo-bridge
22
22
 
23
23
  ## Usage
24
24
 
25
- You can have a look at `bin/play` for an example of interaction with the Bridge::Game class.
25
+ You can have a look at `bin/leo-play` for an example of interaction with the Bridge::Game class (type `help` for available commands).
26
+
27
+ IF you clone the source, you can also run `./bin/leo-play` to try out a rudimentary interactive game (meant as a demo, this is NOT a full bridge game).
28
+
26
29
 
27
30
  Here's a quick run-through:
28
31
 
@@ -43,4 +46,4 @@ Here's a quick run-through:
43
46
  2. Create your feature branch (`git checkout -b my-new-feature`)
44
47
  3. Commit your changes (`git commit -am 'Add some feature'`)
45
48
  4. Push to the branch (`git push origin my-new-feature`)
46
- 5. Create new Pull Request
49
+ 5. Create new Pull Request
@@ -4,4 +4,5 @@ libs << '-r irb/completion'
4
4
  libs << '-r pp'
5
5
  libs << '-r ./lib/bridge'
6
6
  libs << '-r ./bridge.rc.rb'
7
+
7
8
  exec "irb #{libs.join(' ')}"
@@ -78,6 +78,15 @@ end
78
78
 
79
79
  while cmd = gets.strip
80
80
  case cmd
81
+ when 'help'
82
+ puts "* Available Commands".green
83
+ puts "`hand` show current player hand"
84
+ puts "`bid [valid bid]` make an auction bid (e.g. `bid one spade`)"
85
+ puts "`[card]` play a card while in play (e.g. `AH` or `10S`)"
86
+ puts "`rush [auction|trick]` fast-forward auction or trick stage"
87
+ puts "`history` display move history"
88
+ puts "`undo` undo last auction or trick stage move"
89
+ print cursor
81
90
  when 'bye','exit','leave'
82
91
  break
83
92
  when 'rush trick' # play an automatic trick
@@ -85,12 +94,18 @@ while cmd = gets.strip
85
94
  rush_trick
86
95
  when 'rush auction' # play a canned auction
87
96
  rush_auction
97
+ when 'undo'
98
+ if @game.undo!
99
+ put 'OK'.green
100
+ else
101
+ put 'Undo unavailable'.yellow
102
+ end
88
103
  when 'history'
89
104
  put 'Move history:'.yellow
90
105
  put @history.join("\n")
91
106
 
92
107
  when 'hand'
93
- put @game.get_hand(@game.get_turn).inspect.white
108
+ put @game.get_hand(@game.get_turn).to_s
94
109
  print cursor
95
110
 
96
111
  else
@@ -111,10 +126,14 @@ while cmd = gets.strip
111
126
  else
112
127
  card = Card.from_string(ary.first)
113
128
  end
114
-
115
- @players[position_i].play_card(card)
129
+ player = if @game.get_turn == @game.play.dummy
130
+ @players[@game.play.declarer]
131
+ else
132
+ @players[position_i]
133
+ end
134
+ player.play_card(card)
116
135
  print "#{position.white} plays #{card.to_s.magenta} ~> "
117
- puts @game.play.get_current_trick.cards.compact.inspect.white
136
+ puts @game.play.get_current_trick.cards.map(&:to_s).compact.join(' ').white
118
137
  rescue Exception => e
119
138
  put e.message.red
120
139
  end
@@ -7,23 +7,19 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "leonardo-bridge"
8
8
  spec.version = Bridge::VERSION
9
9
  spec.authors = ["Achilles Charmpilas"]
10
- spec.email = ["ac@humbuckercode.co.uk"]
10
+ spec.email = ["achilles@clickitmedia.eu"]
11
11
  spec.description = %q{A lean mean bridge playing machine}
12
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/"
13
+ spec.homepage = "https://leobridge.net"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
- spec.add_dependency "redis"
21
20
  spec.add_dependency "nutrun-string"
22
21
  spec.add_development_dependency "bundler", "~> 1.3"
23
22
  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
23
  spec.add_development_dependency "rake"
28
24
  spec.add_development_dependency "simplecov"
29
25
  end
@@ -1,11 +1,12 @@
1
1
  require 'rubygems'
2
2
  require 'ostruct'
3
3
  require 'pathname'
4
+ require 'json'
4
5
  require 'nutrun-string'
5
6
 
6
7
  root = File.dirname(__FILE__)
7
8
 
8
- %W{ uuid redis_model enum }.each { |r| require File.join(root,r) }
9
+ %W{ enum }.each { |r| require File.join(root,r) }
9
10
 
10
11
  module Bridge
11
12
  DEBUG = false
@@ -4,7 +4,6 @@ module Bridge
4
4
  class InvalidCallClassError < StandardError; end
5
5
 
6
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
7
  class Auction
9
8
  attr_accessor :dealer, :calls, :contract
10
9
 
@@ -1,5 +1,4 @@
1
1
  module Bridge
2
- # Swiped from https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/board.py
3
2
  # An encapsulation of board information.
4
3
  # @keyword deal: the cards in each hand.
5
4
  # @type deal: Deal
@@ -1,4 +1,3 @@
1
- # ref: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/call.py
2
1
  module Bridge
3
2
  module Level
4
3
  extend Enum
@@ -1,8 +1,5 @@
1
1
  module Bridge
2
2
  class InvalidAuctionError < StandardError; end
3
-
4
- # Represents the result of an auction.
5
- # Swiped from: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/auction.py
6
3
  class Contract
7
4
  attr_accessor :redouble_by, :double_by, :bid, :declarer
8
5
 
@@ -1,9 +1,4 @@
1
1
  module Bridge
2
- # deal, not game
3
- # bidding create the contract
4
- # bidding is also persistent
5
- # bidding finishes after 3 passes
6
-
7
2
  class Deal
8
3
  attr_accessor :hands, :deck
9
4
 
@@ -16,12 +11,20 @@ module Bridge
16
11
  deal!
17
12
  self
18
13
  end
19
-
14
+
20
15
  # deals one card per hand on a cycle until we run out of cards
21
16
  def deal!
22
17
  hands.cycle(deck.size/hands.size) { |hand| hand << deck.shift }
23
18
  end
24
19
 
20
+ def to_page
21
+ # returns HEX bridge book page value
22
+ end
23
+
24
+ def self.from_page
25
+ # create deal and hands based on HEX bridge book page number
26
+ end
27
+
25
28
  def method_missing(m, *args, &block)
26
29
  if hands.respond_to?(m)
27
30
  hands.send(m, *args, &block)
@@ -1,12 +1,23 @@
1
+ require 'securerandom'
2
+
1
3
  module Bridge
2
4
  class Deck
3
5
  attr_accessor :cards
4
6
 
5
7
  def initialize
6
8
  @cards = Card::RANKS.product(Card::SUITS).map { |a| Card.new(a[0],a[1]) }
7
- @cards.shuffle!
9
+ @cards.shuffle!(random: SecureRandom)
8
10
  end
9
-
11
+
12
+ # make sure shuffling is as random as possible
13
+ def shuffle!
14
+ @cards.shuffle!(random: SecureRandom)
15
+ end
16
+
17
+ def shuffle
18
+ @cards.shuffle(random: SecureRandom)
19
+ end
20
+
10
21
  def inspect
11
22
  cards.inspect
12
23
  end
@@ -1,8 +1,6 @@
1
1
  #require File.join(File.dirname(__FILE__),'result')
2
2
 
3
3
  module Bridge
4
- # Raised by game in response to an unsatisfiable or erroneous request.
5
- # ref: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/network/error.py
6
4
  class GameError < StandardError
7
5
  end
8
6
 
@@ -12,9 +10,7 @@ module Bridge
12
10
  # Modifications to the state are typically made through BridgePlayer objects.
13
11
  # Methods which change the game state (make_call, playCard) require a player
14
12
  # argument as "authentication".
15
- class Game < RedisModel
16
- use_timestamps
17
-
13
+ class Game
18
14
  attr_accessor :auction, :play, :players, :options, :number
19
15
  attr_accessor :board, :board_queue, :results, :visible_hands
20
16
  attr_accessor :trump_suit, :result, :contract, :state
@@ -105,7 +101,22 @@ module Bridge
105
101
  self.state = :auction
106
102
  true
107
103
  end
108
-
104
+
105
+ def in_play?
106
+ if !self.play.nil?
107
+ !self.play.complete?
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ def in_auction?
114
+ if !self.auction.nil?
115
+ !self.auction.passed_out?
116
+ else
117
+ false
118
+ end
119
+ end
109
120
 
110
121
  def in_progress?
111
122
  if !self.play.nil?
@@ -168,7 +179,44 @@ module Bridge
168
179
 
169
180
  self.players.reject! { |player,pos| pos == position }
170
181
  end
171
-
182
+
183
+ # Send undo message to either auction or trick play
184
+ # this is only available while there is an auction or trick_play
185
+ def undo!
186
+ case self.state
187
+ when :auction
188
+ if self.auction.complete?
189
+ false # can't undo if auction is complete yo.
190
+ else
191
+ card = self.auction.calls.pop # remove the last undo
192
+ if card
193
+ true
194
+ else
195
+ false
196
+ end
197
+ end
198
+ when :playing
199
+ # remove the last card from everywhere
200
+ card = self.play.history.pop
201
+ if card
202
+ trick = self.play.get_current_trick
203
+ player = self.play.who_played?(card)
204
+ # this was a completed trick, we need to remove it from the winner queue
205
+ if trick.cards.compact.size == 4
206
+ winner = self.play.who_played?(self.play.winning_card(trick))
207
+ self.play.winners.pop if self.play.winners.last == winner
208
+ end
209
+ self.play.get_current_trick.cards.delete(card)
210
+ self.play.played.each { |k,h| h.delete(card) }
211
+ self.board.deal.hands[player] << card
212
+ true
213
+ else
214
+ false
215
+ end
216
+ else
217
+ false
218
+ end
219
+ end
172
220
 
173
221
  # Bridge-specific methods.
174
222
 
@@ -15,7 +15,7 @@ module Bridge
15
15
  end
16
16
 
17
17
  def to_s
18
- cards.to_s
18
+ cards.map(&:to_s).join(' ')
19
19
  end
20
20
 
21
21
  def to_json opts = {}
@@ -1,7 +1,5 @@
1
1
  module Bridge
2
- # Actor representing a player's view of a BridgeGame object.
3
- # ref: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/game.py
4
- class Player < RedisModel
2
+ class Player
5
3
  def initialize(game)
6
4
  @game = game # Access to game is private to this object.
7
5
  end
@@ -1,7 +1,5 @@
1
1
 
2
2
  module Bridge
3
- # Represents the result of a completed round of bridge.
4
- # Swiped from: https://svn.code.sf.net/p/pybridge/code/trunk/pybridge/pybridge/games/bridge/result.py
5
3
  class Result
6
4
  VULN_MAP = {
7
5
  Vulnerability.none => [],
@@ -127,17 +125,19 @@ module Bridge
127
125
  else
128
126
  components['slambonus'] = 500
129
127
  end
130
- elsif components['odd'] >= 100 # Game contract (non-slam).
128
+ end
129
+
130
+ if components['odd'] >= 100 # Game contract (non-slam).
131
131
  # 500 for game if vulnerable, 300 if not.
132
132
  if is_vulnerable
133
133
  components['gamebonus'] = 500
134
134
  else
135
135
  components['gamebonus'] = 300
136
136
  end
137
- else # Non-game contract.
137
+ else # Non-game contract.
138
138
  components['partscore'] = 50
139
139
  end
140
-
140
+
141
141
  #### Insult bonus ####
142
142
  if is_redoubled
143
143
  components['insultbonus'] = 100
@@ -2,7 +2,6 @@ module Bridge
2
2
  # This class models the trick-taking phase of a game of bridge.
3
3
  # This code is generalised, and could easily be adapted to support a
4
4
  # variety of trick-taking card games.
5
- # Swiped from: https://pybridge.svn.sourceforge.net/svnroot/pybridge/trunk/pybridge/pybridge/games/bridge/auction.py
6
5
  class TrickPlay
7
6
  attr_accessor :trumps, :declarer, :dummy, :lho, :rho, :played, :winners, :history
8
7
 
@@ -1,3 +1,3 @@
1
1
  module Bridge
2
- VERSION = "0.4.3"
2
+ VERSION = "0.6.5"
3
3
  end
@@ -13,30 +13,30 @@ describe Auction do
13
13
 
14
14
  subject(:instance) { Auction.new(dealer) }
15
15
 
16
- it { subject.contract.should be_a(NilClass) }
17
- it { subject.get_contract.should be_a(NilClass) }
18
- it { subject.calls.size.should eq(0) }
19
- it { subject.dealer.should eq(dealer) }
16
+ it { expect(subject.contract).to be_a(NilClass) }
17
+ it { expect(subject.get_contract).to be_a(NilClass) }
18
+ it { expect(subject.calls.size).to eq(0) }
19
+ it { expect(subject.dealer).to eq(dealer) }
20
20
 
21
21
  it 'should pass out on 4 passes only' do
22
- subject.passed_out?.should eq(false)
22
+ expect(subject.passed_out?).to eq(false)
23
23
  3.times do
24
- subject.make_call(Pass.new).should eq(true)
25
- subject.passed_out?.should eq(false)
24
+ expect(subject.make_call(Pass.new)).to eq(true)
25
+ expect(subject.passed_out?).to eq(false)
26
26
  end
27
27
 
28
- subject.make_call(Pass.new).should eq(true)
29
- subject.passed_out?.should eq(true)
28
+ expect(subject.make_call(Pass.new)).to eq(true)
29
+ expect(subject.passed_out?).to eq(true)
30
30
  end
31
31
 
32
32
  describe 'when finished' do
33
33
  before { calls.each { |call| subject.make_call(call) } }
34
34
 
35
- it { subject.calls.should eq(calls) }
36
- it { subject.contract.should_not be_a(NilClass) }
37
- it { subject.contract.should be_a(Contract) }
38
- it { subject.get_contract.should be_a(Hash) }
39
- it { subject.get_contract.should eq(subject.contract.to_hash) }
35
+ it { expect(subject.calls).to eq(calls) }
36
+ it { expect(subject.contract).to_not be_a(NilClass) }
37
+ it { expect(subject.contract).to be_a(Contract) }
38
+ it { expect(subject.get_contract).to be_a(Hash) }
39
+ it { expect(subject.get_contract).to eq(subject.contract.to_hash) }
40
40
  end
41
41
 
42
42
  describe 'current call' do
@@ -57,35 +57,35 @@ describe Auction do
57
57
 
58
58
  it 'does not allow invalid calls' do
59
59
  subject.make_call(Bid.new(Level.two, Strain.club))
60
- expect { subject.make_call(Bid.new(Level.one, Strain.club)) }.to raise_error
60
+ expect { subject.make_call(Bid.new(Level.one, Strain.club)) }.to raise_error(Bridge::InvalidCallError)
61
61
  expect { subject.make_call(Bid.new(Level.three, Strain.club)) }.to_not raise_error
62
- expect { subject.make_call(Bid.new(Level.two, Strain.heart)) }.to raise_error
62
+ expect { subject.make_call(Bid.new(Level.two, Strain.heart)) }.to raise_error(Bridge::InvalidCallError)
63
63
  expect { subject.make_call(Bid.new(Level.three, Strain.heart)) }.to_not raise_error
64
64
  end
65
65
 
66
66
  it 'knows whose turn it is' do
67
67
  turn = dealer
68
- subject.whose_turn.should eq(turn)
68
+ expect(subject.whose_turn).to eq(turn)
69
69
 
70
70
  calls.each_index do |i|
71
71
  subject.make_call(calls[i])
72
72
  if i == calls.size - 1
73
- subject.whose_turn.should eq(nil)
73
+ expect(subject.whose_turn).to eq(nil)
74
74
  else
75
75
  turn = Direction[(turn + 1) % 4] # Turn moves clockwise.
76
- subject.whose_turn.should eq(turn)
76
+ expect(subject.whose_turn).to eq(turn)
77
77
  end
78
78
  end
79
79
  end
80
80
 
81
81
  it 'only marks an auction as complete if it is' do
82
- subject.complete?.should eq(false)
82
+ expect(subject.complete?).to eq(false)
83
83
  calls.each_index do |i|
84
84
  subject.make_call(calls[i])
85
85
  if i == calls.size - 1
86
- subject.complete?.should eq(true)
86
+ expect(subject.complete?).to eq(true)
87
87
  else
88
- subject.complete?.should eq(false)
88
+ expect(subject.complete?).to eq(false)
89
89
  end
90
90
  end
91
91
  end
@@ -94,7 +94,7 @@ describe Auction do
94
94
  calls.each_index do |i|
95
95
  subject.make_call(calls[i])
96
96
  next_call = calls[i+1]
97
- subject.valid_call?(next_call).should eq(true) unless next_call.nil?
97
+ expect(subject.valid_call?(next_call)).to eq(true) unless next_call.nil?
98
98
  end
99
99
  end
100
100
  end