cribbage_game 0.1.0
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 +7 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/cribbage_game.gemspec +38 -0
- data/lib/cribbage_game/card.rb +47 -0
- data/lib/cribbage_game/fsm.rb +42 -0
- data/lib/cribbage_game/game.rb +192 -0
- data/lib/cribbage_game/player.rb +26 -0
- data/lib/cribbage_game/score.rb +78 -0
- data/lib/cribbage_game/score_utils.rb +126 -0
- data/lib/cribbage_game/version.rb +3 -0
- data/lib/cribbage_game.rb +2 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f082325ee2fa9bad9eb816d32295d2c7efd3e2fee83698184d48f2e966308263
|
4
|
+
data.tar.gz: 8781e57a759011c7ecbc55e76b0965f2de5e076d0d2ffb7e571afe48364a107f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4f0aabf20f150a33c422a5670209fedda21a51a275f1a7114eaa07eaac7a786fd6d206f31a44ebdb6b11827fe4f98950cdcde18ede6c3e0086f381cac1f15593
|
7
|
+
data.tar.gz: c501dd8c015ab55f95ca9bf536042616a52b18c0718f7f135c6920c5917fbcf05df5afee1f619bc140e98271d4ae4817cfeb240b4583df97b7c94107ce438e50
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cribbage_game (0.1.0)
|
5
|
+
aasm (~> 5.0, >= 5.0.6)
|
6
|
+
card_deck
|
7
|
+
sum_all_number_combinations (~> 0.1.2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
aasm (5.4.0)
|
13
|
+
concurrent-ruby (~> 1.0)
|
14
|
+
ast (2.4.2)
|
15
|
+
card_deck (4.02)
|
16
|
+
concurrent-ruby (1.1.10)
|
17
|
+
diff-lcs (1.3)
|
18
|
+
json (2.6.2)
|
19
|
+
parallel (1.22.1)
|
20
|
+
parser (3.1.2.1)
|
21
|
+
ast (~> 2.4.1)
|
22
|
+
rainbow (3.1.1)
|
23
|
+
rake (13.0.6)
|
24
|
+
regexp_parser (2.6.1)
|
25
|
+
rexml (3.2.5)
|
26
|
+
rspec (3.9.0)
|
27
|
+
rspec-core (~> 3.9.0)
|
28
|
+
rspec-expectations (~> 3.9.0)
|
29
|
+
rspec-mocks (~> 3.9.0)
|
30
|
+
rspec-core (3.9.0)
|
31
|
+
rspec-support (~> 3.9.0)
|
32
|
+
rspec-expectations (3.9.0)
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
+
rspec-support (~> 3.9.0)
|
35
|
+
rspec-mocks (3.9.0)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.9.0)
|
38
|
+
rspec-support (3.9.0)
|
39
|
+
rubocop (1.39.0)
|
40
|
+
json (~> 2.3)
|
41
|
+
parallel (~> 1.10)
|
42
|
+
parser (>= 3.1.2.1)
|
43
|
+
rainbow (>= 2.2.2, < 4.0)
|
44
|
+
regexp_parser (>= 1.8, < 3.0)
|
45
|
+
rexml (>= 3.2.5, < 4.0)
|
46
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
47
|
+
ruby-progressbar (~> 1.7)
|
48
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
49
|
+
rubocop-ast (1.23.0)
|
50
|
+
parser (>= 3.1.1.0)
|
51
|
+
rubocop-performance (1.15.0)
|
52
|
+
rubocop (>= 1.7.0, < 2.0)
|
53
|
+
rubocop-ast (>= 0.4.0)
|
54
|
+
ruby-progressbar (1.11.0)
|
55
|
+
standard (1.18.0)
|
56
|
+
rubocop (= 1.39.0)
|
57
|
+
rubocop-performance (= 1.15.0)
|
58
|
+
sum_all_number_combinations (0.1.2)
|
59
|
+
unicode-display_width (2.3.0)
|
60
|
+
|
61
|
+
PLATFORMS
|
62
|
+
ruby
|
63
|
+
x64-mingw32
|
64
|
+
x86-mingw32
|
65
|
+
|
66
|
+
DEPENDENCIES
|
67
|
+
cribbage_game!
|
68
|
+
rake (~> 13.0)
|
69
|
+
rspec (~> 3.0)
|
70
|
+
standard (~> 1.3)
|
71
|
+
|
72
|
+
BUNDLED WITH
|
73
|
+
2.3.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 buchowski
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# CribbageGame
|
2
|
+
|
3
|
+
Two player cribbage game. https://bicyclecards.com/how-to-play/cribbage/
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'cribbage_game'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install cribbage_game
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
game = Game.new
|
25
|
+
game.cut_for_deal # randomly determines dealer
|
26
|
+
game.deal # deals 6 cards to game.dealer.hand & game.opponent.hand
|
27
|
+
game.dealer.discard ["7d", "qc"] # takes cards from hand and puts in crib
|
28
|
+
game.opponent.discard ["6h", "9s"]
|
29
|
+
game.flip_top_card # two for his heels if Jack
|
30
|
+
game.opponent.play_card 'ah' # game.opponent.total_score is updated automatically
|
31
|
+
game.dealer.play_card '8h'
|
32
|
+
... # continue until all cards are played
|
33
|
+
game.submit_hand_scores(game.opponent) # adds the hand's score to game.opponent.total_score
|
34
|
+
game.submit_hand_scores(game.dealer)
|
35
|
+
game.submit_crib_scores # adds the crib's score to game.dealer.total_score
|
36
|
+
game.deal # begin next round
|
37
|
+
```
|
38
|
+
### Initialization options
|
39
|
+
```ruby
|
40
|
+
game = Game.new ({
|
41
|
+
points_to_win: 35, # overrides default of 121
|
42
|
+
game_over_cb: lambda { puts "game over" } # function called on game_over
|
43
|
+
})
|
44
|
+
```
|
45
|
+
|
46
|
+
## Development
|
47
|
+
|
48
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
49
|
+
|
50
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/buchowski/ruby-cribbage.
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/cribbage_game/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "cribbage_game"
|
7
|
+
spec.version = CribbageGame::VERSION
|
8
|
+
spec.authors = ["buchowski"]
|
9
|
+
spec.email = ["bucholz.adam@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Two player cribbage card game"
|
12
|
+
spec.homepage = "https://github.com/buchowski/ruby-cribbage"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/buchowski/ruby-cribbage"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/buchowski/ruby-cribbage/blob/master/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "card_deck"
|
32
|
+
spec.add_dependency "aasm", "~> 5.0", ">= 5.0.6"
|
33
|
+
spec.add_dependency "sum_all_number_combinations", "~> 0.1.2"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
|
36
|
+
# For more information and examples about making a new gem, check out our
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CribbageGame
|
2
|
+
require "card_deck"
|
3
|
+
|
4
|
+
class CardDeck::Card # Represents a card in the deck
|
5
|
+
# Value of the card
|
6
|
+
def value
|
7
|
+
case @num
|
8
|
+
when "Ace" then 1
|
9
|
+
when 2..10 then @num
|
10
|
+
when "Jack" then 10
|
11
|
+
when "Queen" then 10
|
12
|
+
when "King" then 10
|
13
|
+
when "Joker" then 10
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def sort_value
|
18
|
+
case @num
|
19
|
+
when "Ace" then 1
|
20
|
+
when 2..10 then @num
|
21
|
+
when "Jack" then 11
|
22
|
+
when "Queen" then 12
|
23
|
+
when "King" then 13
|
24
|
+
when "Joker" then 14
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
suit = case @suit
|
30
|
+
when Hearts then "h"
|
31
|
+
when Spades then "s"
|
32
|
+
when Diamonds then "d"
|
33
|
+
when Clubs then "c"
|
34
|
+
end
|
35
|
+
num = case @num
|
36
|
+
when "Ace" then "a"
|
37
|
+
when "Jack" then "j"
|
38
|
+
when "Queen" then "q"
|
39
|
+
when "King" then "k"
|
40
|
+
when "Joker" then "r"
|
41
|
+
else @num.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
num + suit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "aasm"
|
2
|
+
|
3
|
+
module CribbageGame
|
4
|
+
class Fsm
|
5
|
+
include AASM
|
6
|
+
|
7
|
+
aasm do
|
8
|
+
state :cutting_for_deal, initial: true
|
9
|
+
state :flipping_top_card
|
10
|
+
state :discarding, :dealing, :playing
|
11
|
+
state :scoring_opponent_hand, :scoring_dealer_hand, :scoring_dealer_crib
|
12
|
+
state :game_over
|
13
|
+
|
14
|
+
event :deal do
|
15
|
+
transitions from: :cutting_for_deal, to: :dealing
|
16
|
+
transitions from: :scoring_dealer_crib, to: :dealing
|
17
|
+
end
|
18
|
+
|
19
|
+
event :discard do
|
20
|
+
transitions from: :dealing, to: :discarding
|
21
|
+
end
|
22
|
+
|
23
|
+
event :flip_top_card do
|
24
|
+
transitions from: :discarding, to: :flipping_top_card
|
25
|
+
end
|
26
|
+
|
27
|
+
event :play do
|
28
|
+
transitions from: :flipping_top_card, to: :playing
|
29
|
+
end
|
30
|
+
|
31
|
+
event :score do
|
32
|
+
transitions from: :playing, to: :scoring_opponent_hand
|
33
|
+
transitions from: :scoring_opponent_hand, to: :scoring_dealer_hand
|
34
|
+
transitions from: :scoring_dealer_hand, to: :scoring_dealer_crib
|
35
|
+
end
|
36
|
+
|
37
|
+
event :declare_winner do
|
38
|
+
transitions to: :game_over
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require "cribbage_game/card"
|
2
|
+
require "cribbage_game/score"
|
3
|
+
require "cribbage_game/player"
|
4
|
+
require "cribbage_game/fsm"
|
5
|
+
|
6
|
+
module CribbageGame
|
7
|
+
class NotYourTurnError < RuntimeError; end
|
8
|
+
|
9
|
+
class NotYourCardError < RuntimeError; end
|
10
|
+
|
11
|
+
class CardTooLargeError < RuntimeError; end
|
12
|
+
|
13
|
+
class WrongStateError < RuntimeError; end
|
14
|
+
|
15
|
+
class Game
|
16
|
+
attr_accessor :players, :deck, :crib, :pile, :cut_card, :dealer, :whose_turn, :fsm, :points_to_win, :round, :winner
|
17
|
+
attr_reader :auto_score
|
18
|
+
|
19
|
+
def initialize args = {}
|
20
|
+
@points_to_win = args[:points_to_win] || 121
|
21
|
+
@auto_score = args[:is_auto_score] || true
|
22
|
+
@game_over_cb = args[:game_over_cb] || lambda {}
|
23
|
+
@players = 2.times.map { |id| Player.new self, id.to_s }
|
24
|
+
@score_client = Score.new self
|
25
|
+
@fsm = Fsm.new
|
26
|
+
@deck = self.class.get_cards_hash CardDeck::Deck.new.cards
|
27
|
+
@round = 0
|
28
|
+
@winner = nil
|
29
|
+
|
30
|
+
reset_cards
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get_cards_hash cards
|
34
|
+
cards.each_with_object({}) do |card, card_map|
|
35
|
+
card_map[card.id] = card
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.get_hand_hash card_ids
|
40
|
+
card_ids.to_h { |id| [id, true] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def undealt_card_ids
|
44
|
+
@deck.keys.difference(@dealer.hand.keys, opponent.hand.keys)
|
45
|
+
end
|
46
|
+
|
47
|
+
def opponent
|
48
|
+
@players.difference([@dealer]).first
|
49
|
+
end
|
50
|
+
|
51
|
+
def not_whose_turn
|
52
|
+
@players.difference([@whose_turn]).first
|
53
|
+
end
|
54
|
+
|
55
|
+
def can_play_card? card_id
|
56
|
+
pile_score + @deck[card_id].value <= 31
|
57
|
+
end
|
58
|
+
|
59
|
+
def player_hands_empty?
|
60
|
+
@players.none? { |player| player.hand.values.any? }
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_playable_card? hand
|
64
|
+
card_ids = hand.keys.filter { |card_id| hand[card_id] } # false has been played
|
65
|
+
card_ids.map { |card_id| can_play_card?(card_id) }.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
def can_whose_turn_play?
|
69
|
+
has_playable_card? @whose_turn.hand
|
70
|
+
end
|
71
|
+
|
72
|
+
def can_not_whose_turn_play?
|
73
|
+
has_playable_card? not_whose_turn.hand
|
74
|
+
end
|
75
|
+
|
76
|
+
def can_either_player_play?
|
77
|
+
can_whose_turn_play? || can_not_whose_turn_play?
|
78
|
+
end
|
79
|
+
|
80
|
+
def pile_score
|
81
|
+
@pile.map { |card_id| @deck[card_id].value }.sum
|
82
|
+
end
|
83
|
+
|
84
|
+
def all_cards_discarded?
|
85
|
+
@players.all? { |player| player.hand.keys.size == 4 }
|
86
|
+
end
|
87
|
+
|
88
|
+
def is_valid_play? player, card_id
|
89
|
+
raise WrongStateError if !@fsm.playing?
|
90
|
+
raise NotYourTurnError if player != @whose_turn
|
91
|
+
raise NotYourCardError if !player.hand[card_id]
|
92
|
+
raise CardTooLargeError if !can_play_card? card_id
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset_cards
|
97
|
+
@round += 1
|
98
|
+
@cut_card = nil
|
99
|
+
@crib = []
|
100
|
+
@pile = []
|
101
|
+
@players.each { |player| player.hand = {} }
|
102
|
+
end
|
103
|
+
|
104
|
+
def cut_for_deal
|
105
|
+
raise WrongStateError if !@fsm.cutting_for_deal?
|
106
|
+
|
107
|
+
@dealer = @players.sample
|
108
|
+
@whose_turn = opponent
|
109
|
+
@fsm.deal
|
110
|
+
end
|
111
|
+
|
112
|
+
def deal
|
113
|
+
raise WrongStateError if !@fsm.dealing?
|
114
|
+
|
115
|
+
random_card_ids = @deck.keys.sample 12
|
116
|
+
@dealer.hand = self.class.get_hand_hash random_card_ids.slice!(0, 6)
|
117
|
+
opponent.hand = self.class.get_hand_hash random_card_ids.slice!(0, 6)
|
118
|
+
|
119
|
+
@fsm.discard
|
120
|
+
end
|
121
|
+
|
122
|
+
def play_card player, card_id
|
123
|
+
is_valid_play?(player, card_id)
|
124
|
+
|
125
|
+
player.hand[card_id] = false
|
126
|
+
@pile << card_id
|
127
|
+
is_last_card = !can_either_player_play?
|
128
|
+
@score_client.score_play(@pile, is_last_card, player)
|
129
|
+
@score_client.submit_play_score player
|
130
|
+
return if we_have_a_winner?
|
131
|
+
|
132
|
+
@pile = [] if is_last_card
|
133
|
+
@whose_turn = not_whose_turn if can_not_whose_turn_play?
|
134
|
+
@fsm.score if player_hands_empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
def flip_top_card top_card = nil
|
138
|
+
raise WrongStateError if !@fsm.flipping_top_card?
|
139
|
+
|
140
|
+
@cut_card = top_card || undealt_card_ids.sample
|
141
|
+
@whose_turn = opponent
|
142
|
+
@score_client.score_crib
|
143
|
+
@score_client.score_hands
|
144
|
+
@fsm.play
|
145
|
+
end
|
146
|
+
|
147
|
+
def discard player, card_id
|
148
|
+
raise WrongStateError if !@fsm.discarding?
|
149
|
+
raise NotYourCardError if !player.hand[card_id]
|
150
|
+
|
151
|
+
player.hand.delete(card_id)
|
152
|
+
@crib << card_id
|
153
|
+
|
154
|
+
@fsm.flip_top_card if all_cards_discarded?
|
155
|
+
end
|
156
|
+
|
157
|
+
def submit_hand_scores player
|
158
|
+
raise NotYourTurnError if player == @dealer && !@fsm.scoring_dealer_hand?
|
159
|
+
raise NotYourTurnError if player == opponent && !@fsm.scoring_opponent_hand?
|
160
|
+
|
161
|
+
@score_client.submit_scores player, :hand
|
162
|
+
return if we_have_a_winner?
|
163
|
+
@fsm.score
|
164
|
+
end
|
165
|
+
|
166
|
+
def submit_crib_scores
|
167
|
+
raise WrongStateError if !@fsm.scoring_dealer_crib?
|
168
|
+
|
169
|
+
@score_client.submit_scores @dealer, :crib
|
170
|
+
return if we_have_a_winner?
|
171
|
+
reset_cards
|
172
|
+
@dealer = opponent
|
173
|
+
@fsm.deal
|
174
|
+
end
|
175
|
+
|
176
|
+
def auto_score_hands_and_crib
|
177
|
+
@score_client.score_hands
|
178
|
+
@score_client.score_crib
|
179
|
+
end
|
180
|
+
|
181
|
+
def we_have_a_winner?
|
182
|
+
winner = @players.select { |player| player.total_score >= @points_to_win }
|
183
|
+
return false if winner.empty?
|
184
|
+
raise "You can only have one winner" if winner.size > 1
|
185
|
+
|
186
|
+
@winner = winner.first
|
187
|
+
@fsm.declare_winner
|
188
|
+
@game_over_cb.call
|
189
|
+
true
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CribbageGame
|
2
|
+
class Player
|
3
|
+
attr_accessor :id, :name, :hand, :total_score
|
4
|
+
|
5
|
+
def initialize game, id
|
6
|
+
@id = id
|
7
|
+
@game = game
|
8
|
+
@hand = {}
|
9
|
+
@total_score = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def discard cards
|
13
|
+
[cards].flatten.each do |card|
|
14
|
+
@game.discard self, card
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def play_card card
|
19
|
+
@game.play_card self, card
|
20
|
+
end
|
21
|
+
|
22
|
+
def score_hand
|
23
|
+
@game.score_hand self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "cribbage_game/score_utils"
|
2
|
+
|
3
|
+
module CribbageGame
|
4
|
+
class Score
|
5
|
+
include ScoreUtils
|
6
|
+
|
7
|
+
def initialize game
|
8
|
+
@game = game
|
9
|
+
@scorecards = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_cards card_ids
|
13
|
+
card_ids.map { |id| @game.deck[id] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty_scorecard
|
17
|
+
{crib: {total_score: 0}, hand: {total_score: 0}}
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_scorecard player
|
21
|
+
@scorecards[@game.round] = @scorecards[@game.round] || {play: []}
|
22
|
+
@scorecards[@game.round][player.id] = @scorecards[@game.round][player.id] || empty_scorecard
|
23
|
+
@scorecards[@game.round][player.id]
|
24
|
+
end
|
25
|
+
|
26
|
+
def score_crib
|
27
|
+
cards = get_cards(@game.crib + [@game.cut_card])
|
28
|
+
get_scorecard(@game.dealer)[:crib][:total_score] = score_hand(cards, true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def score_hands
|
32
|
+
@game.players.each do |player|
|
33
|
+
cards = get_cards(player.hand.keys + [@game.cut_card])
|
34
|
+
get_scorecard(player)[:hand][:total_score] = score_hand cards
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def score_hand cards, is_crib = false
|
39
|
+
n_of_kind_score = score_n_of_a_kind cards
|
40
|
+
sum_score = score_fifteens cards
|
41
|
+
run_score = score_hand_runs cards
|
42
|
+
flush_score = score_flush(cards, is_crib)
|
43
|
+
nobs_score = score_nobs(cards)
|
44
|
+
n_of_kind_score + sum_score + run_score + flush_score + nobs_score
|
45
|
+
end
|
46
|
+
|
47
|
+
def score_play pile_ids, is_last_card, player
|
48
|
+
@scorecards[@game.round] = @scorecards[@game.round] || {play: []}
|
49
|
+
pile_cards = get_cards pile_ids
|
50
|
+
points = get_pile_points(pile_cards, is_last_card) # pass pile cards not ids
|
51
|
+
@scorecards[@game.round][:play] << {
|
52
|
+
points: points,
|
53
|
+
player_id: player.id,
|
54
|
+
pile: pile_ids,
|
55
|
+
card_id: pile_ids.last
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def submit_play_score player, submitted_scores = nil
|
60
|
+
raise "You must submit scores if auto_score=false" if submitted_scores.nil? && !@game.auto_score
|
61
|
+
|
62
|
+
if @game.auto_score
|
63
|
+
player.total_score += @scorecards[@game.round][:play].last[:points]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def submit_scores player, card_group, submitted_scores = nil
|
68
|
+
raise "You must submit scores if auto_score=false" if submitted_scores.nil? && !@game.auto_score
|
69
|
+
raise "card_group must be :hand or :crib" if ![:hand, :crib].include? card_group
|
70
|
+
|
71
|
+
possible_scores = get_scorecard(player)[card_group]
|
72
|
+
|
73
|
+
if @game.auto_score
|
74
|
+
player.total_score += possible_scores[:total_score]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "sum_all_number_combinations"
|
2
|
+
|
3
|
+
module CribbageGame
|
4
|
+
module ScoreUtils
|
5
|
+
def is_run sort_vals, is_allow_double_runs = false
|
6
|
+
sort_vals.each_index do |i|
|
7
|
+
return true if i == sort_vals.size - 1
|
8
|
+
card_val = sort_vals[i]
|
9
|
+
next_card_val = sort_vals[i + 1]
|
10
|
+
is_same_card = next_card_val == card_val
|
11
|
+
is_adj_card = next_card_val - card_val == 1
|
12
|
+
is_run_possible = is_adj_card || (is_same_card && is_allow_double_runs)
|
13
|
+
return false if !is_run_possible
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def score_hand_runs cards
|
18
|
+
return 0 if cards.size < 3
|
19
|
+
|
20
|
+
sort_vals = cards.map(&:sort_value).sort
|
21
|
+
sort_vals.each_index do |i|
|
22
|
+
(3..(sort_vals.size)).to_a.reverse_each do |n|
|
23
|
+
subset = sort_vals[i...n]
|
24
|
+
uniq_count = subset.uniq.size
|
25
|
+
break if uniq_count < 3
|
26
|
+
|
27
|
+
if is_run(subset, true)
|
28
|
+
points = uniq_count
|
29
|
+
subset.uniq.each do |sort_val|
|
30
|
+
count = subset.count sort_val
|
31
|
+
points *= count if count > 1
|
32
|
+
end
|
33
|
+
return points
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
0
|
39
|
+
end
|
40
|
+
|
41
|
+
def score_pile_runs cards
|
42
|
+
return 0 if cards.size < 3
|
43
|
+
|
44
|
+
(3..cards.size).to_a.reverse_each do |n|
|
45
|
+
# get the last n cards played and sort_by sort_value
|
46
|
+
sort_vals = cards.map(&:sort_value)
|
47
|
+
subset = sort_vals.reverse[0...n].sort
|
48
|
+
return n if is_run subset
|
49
|
+
end
|
50
|
+
|
51
|
+
0
|
52
|
+
end
|
53
|
+
|
54
|
+
def score_consecutive cards
|
55
|
+
count = 0
|
56
|
+
|
57
|
+
cards.each_index do |i|
|
58
|
+
break if i == cards.size - 1
|
59
|
+
count = (cards[i].num == cards[i + 1].num) ? count + 1 : 0
|
60
|
+
end
|
61
|
+
# returns 0, 2, 6 or 12
|
62
|
+
count**2 + count
|
63
|
+
end
|
64
|
+
|
65
|
+
def score_n_of_a_kind cards
|
66
|
+
# 2, 3, 4 of a kind
|
67
|
+
counts = cards.each_with_object({}) do |card, counts|
|
68
|
+
if counts[card.num].nil?
|
69
|
+
counts[card.num] = 1
|
70
|
+
else
|
71
|
+
counts[card.num] += 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
counts.values.reduce(0) do |points, count|
|
76
|
+
points + count * (count - 1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def score_fifteens cards
|
81
|
+
card_values = cards.map(&:value)
|
82
|
+
sum_of_all = SumAllCombinations.new card_values
|
83
|
+
sum_of_all.sum
|
84
|
+
fifteen_count = sum_of_all.calculated_values.count 15.0
|
85
|
+
|
86
|
+
fifteen_count * 2
|
87
|
+
end
|
88
|
+
|
89
|
+
def are_same_suit cards
|
90
|
+
cards.map(&:suit).uniq.size == 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def score_flush cards, is_crib = false
|
94
|
+
raise ArgumentError, "only pass 4 or 5 cards to score_flush" if !cards.size.between?(4, 5)
|
95
|
+
is_five_card_flush = cards.size == 5 && are_same_suit(cards)
|
96
|
+
is_four_card_flush = cards.size == 4 && are_same_suit(cards[0...4])
|
97
|
+
|
98
|
+
return 5 if is_five_card_flush
|
99
|
+
return 4 if is_four_card_flush && !is_crib
|
100
|
+
0
|
101
|
+
end
|
102
|
+
|
103
|
+
def score_nobs cards
|
104
|
+
top_card = cards.pop
|
105
|
+
jack = cards.find { |card| card.num == "Jack" }
|
106
|
+
return 1 if jack && jack.suit == top_card.suit
|
107
|
+
0
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_pile_points pile_cards, is_last_card
|
111
|
+
pile_score = pile_cards.map(&:value).sum
|
112
|
+
|
113
|
+
throw "31 should always be is_last_card" if pile_score == 31 && !is_last_card
|
114
|
+
|
115
|
+
points = 0
|
116
|
+
points = 1 if pile_score == 31
|
117
|
+
points = 2 if pile_score == 15
|
118
|
+
|
119
|
+
points += 1 if is_last_card
|
120
|
+
points += score_consecutive pile_cards
|
121
|
+
points += score_pile_runs pile_cards
|
122
|
+
|
123
|
+
points
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cribbage_game
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- buchowski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: card_deck
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aasm
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 5.0.6
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '5.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 5.0.6
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sum_all_number_combinations
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.1.2
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.1.2
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.0'
|
75
|
+
description:
|
76
|
+
email:
|
77
|
+
- bucholz.adam@gmail.com
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- ".rspec"
|
83
|
+
- CHANGELOG.md
|
84
|
+
- Gemfile
|
85
|
+
- Gemfile.lock
|
86
|
+
- LICENSE.txt
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- cribbage_game.gemspec
|
90
|
+
- lib/cribbage_game.rb
|
91
|
+
- lib/cribbage_game/card.rb
|
92
|
+
- lib/cribbage_game/fsm.rb
|
93
|
+
- lib/cribbage_game/game.rb
|
94
|
+
- lib/cribbage_game/player.rb
|
95
|
+
- lib/cribbage_game/score.rb
|
96
|
+
- lib/cribbage_game/score_utils.rb
|
97
|
+
- lib/cribbage_game/version.rb
|
98
|
+
homepage: https://github.com/buchowski/ruby-cribbage
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata:
|
102
|
+
homepage_uri: https://github.com/buchowski/ruby-cribbage
|
103
|
+
source_code_uri: https://github.com/buchowski/ruby-cribbage
|
104
|
+
changelog_uri: https://github.com/buchowski/ruby-cribbage/blob/master/CHANGELOG.md
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 2.6.0
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubygems_version: 3.2.22
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Two player cribbage card game
|
124
|
+
test_files: []
|