leonardo-bridge 0.4.3 → 0.6.5

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