cribbage_game 0.1.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +11 -3
- data/Gemfile +0 -5
- data/Gemfile.lock +89 -42
- data/README.md +25 -23
- data/bin/play_with_ai +12 -0
- data/cribbage_game.gemspec +13 -6
- data/lib/cribbage_game/fsm.rb +18 -2
- data/lib/cribbage_game/game.rb +68 -26
- data/lib/cribbage_game/play_with_ai.rb +305 -0
- data/lib/cribbage_game/score.rb +6 -3
- data/lib/cribbage_game/score_utils.rb +18 -7
- data/lib/cribbage_game/version.rb +1 -1
- metadata +82 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28b316ae4a4a4e446b114ea4f9615199078ead5aa27dcee342a7d75e53cdb5d1
|
|
4
|
+
data.tar.gz: a178f8da7d001822da9f8bcb13f5d9e291c63a91c53ba30b11d991dc7a93e5f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ca57a72414bd5652bbb4d0e86e20cbc22fff89f17ed508c949785adaa17a83c0fec9c3c81ef04817fd7c10e3917c5b13cb55ecb34ca96917ac4da1eb9190011
|
|
7
|
+
data.tar.gz: c70f594049fa96113247949d3491c222f5200fe86328ae4e0583bb405737c04412a65c2d17733ff955b5ddc1db30397e49b15096e88d28e588704cfd4103d300
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 1.0.0 - 2026-09-07
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
- Added support for three-player games.
|
|
6
|
+
- Added interactive AI gameplay.
|
|
7
|
+
- Ruby 3.0.0 is now the minimum supported version.
|
|
8
|
+
- Preserved the existing two-player API and behavior.
|
|
9
|
+
|
|
10
|
+
## 0.1.0 - 2023-12-07
|
|
11
|
+
|
|
12
|
+
- Initial release.
|
|
13
|
+
- Added support for two-player games.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,73 +1,120 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
cribbage_game (
|
|
4
|
+
cribbage_game (1.0.0)
|
|
5
5
|
aasm (~> 5.0, >= 5.0.6)
|
|
6
|
-
card_deck
|
|
6
|
+
card_deck (~> 4.02)
|
|
7
|
+
logger (>= 1.7)
|
|
8
|
+
ostruct (~> 0.6.0)
|
|
7
9
|
sum_all_number_combinations (~> 0.1.2)
|
|
8
10
|
|
|
9
11
|
GEM
|
|
10
12
|
remote: https://rubygems.org/
|
|
11
13
|
specs:
|
|
12
|
-
aasm (5.
|
|
14
|
+
aasm (5.5.2)
|
|
13
15
|
concurrent-ruby (~> 1.0)
|
|
14
|
-
ast (2.4.
|
|
16
|
+
ast (2.4.3)
|
|
15
17
|
card_deck (4.02)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
cgi (0.5.2)
|
|
19
|
+
concurrent-ruby (1.3.8)
|
|
20
|
+
date (3.5.1)
|
|
21
|
+
diff-lcs (1.6.2)
|
|
22
|
+
erb (4.0.4.1)
|
|
23
|
+
cgi (>= 0.3.3)
|
|
24
|
+
io-console (0.9.2)
|
|
25
|
+
irb (1.18.0)
|
|
26
|
+
pp (>= 0.6.0)
|
|
27
|
+
prism (>= 1.3.0)
|
|
28
|
+
rdoc (>= 4.0.0)
|
|
29
|
+
reline (>= 0.4.2)
|
|
30
|
+
json (2.21.2)
|
|
31
|
+
language_server-protocol (3.17.0.6)
|
|
32
|
+
lint_roller (1.1.0)
|
|
33
|
+
logger (1.7.0)
|
|
34
|
+
ostruct (0.6.3)
|
|
35
|
+
parallel (1.28.0)
|
|
36
|
+
parser (3.3.12.0)
|
|
21
37
|
ast (~> 2.4.1)
|
|
38
|
+
racc
|
|
39
|
+
pp (0.6.4)
|
|
40
|
+
prettyprint
|
|
41
|
+
prettyprint (0.2.0)
|
|
42
|
+
prism (1.9.0)
|
|
43
|
+
psych (5.5.0)
|
|
44
|
+
date
|
|
45
|
+
stringio
|
|
46
|
+
racc (1.8.1)
|
|
22
47
|
rainbow (3.1.1)
|
|
23
|
-
rake (13.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
rake (13.4.2)
|
|
49
|
+
rdoc (7.2.0)
|
|
50
|
+
erb
|
|
51
|
+
psych (>= 4.0.0)
|
|
52
|
+
tsort
|
|
53
|
+
regexp_parser (2.12.0)
|
|
54
|
+
reline (0.7.0)
|
|
55
|
+
io-console (~> 0.5)
|
|
56
|
+
rspec (3.13.2)
|
|
57
|
+
rspec-core (~> 3.13.0)
|
|
58
|
+
rspec-expectations (~> 3.13.0)
|
|
59
|
+
rspec-mocks (~> 3.13.0)
|
|
60
|
+
rspec-core (3.13.6)
|
|
61
|
+
rspec-support (~> 3.13.0)
|
|
62
|
+
rspec-expectations (3.13.5)
|
|
33
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
34
|
-
rspec-support (~> 3.
|
|
35
|
-
rspec-mocks (3.
|
|
64
|
+
rspec-support (~> 3.13.0)
|
|
65
|
+
rspec-mocks (3.13.8)
|
|
36
66
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
37
|
-
rspec-support (~> 3.
|
|
38
|
-
rspec-support (3.
|
|
39
|
-
rubocop (1.
|
|
67
|
+
rspec-support (~> 3.13.0)
|
|
68
|
+
rspec-support (3.13.7)
|
|
69
|
+
rubocop (1.88.2)
|
|
40
70
|
json (~> 2.3)
|
|
41
|
-
|
|
42
|
-
|
|
71
|
+
language_server-protocol (~> 3.17.0.2)
|
|
72
|
+
lint_roller (~> 1.1.0)
|
|
73
|
+
parallel (>= 1.10)
|
|
74
|
+
parser (>= 3.3.0.2)
|
|
43
75
|
rainbow (>= 2.2.2, < 4.0)
|
|
44
|
-
regexp_parser (>=
|
|
45
|
-
|
|
46
|
-
rubocop-ast (>= 1.23.0, < 2.0)
|
|
76
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
77
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
47
78
|
ruby-progressbar (~> 1.7)
|
|
48
|
-
unicode-display_width (>=
|
|
49
|
-
rubocop-ast (1.
|
|
50
|
-
parser (>= 3.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
80
|
+
rubocop-ast (1.50.0)
|
|
81
|
+
parser (>= 3.3.7.2)
|
|
82
|
+
prism (~> 1.7)
|
|
83
|
+
rubocop-performance (1.26.1)
|
|
84
|
+
lint_roller (~> 1.1)
|
|
85
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
86
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
87
|
+
ruby-progressbar (1.13.0)
|
|
88
|
+
standard (1.56.0)
|
|
89
|
+
language_server-protocol (~> 3.17.0.2)
|
|
90
|
+
lint_roller (~> 1.0)
|
|
91
|
+
rubocop (~> 1.88.0)
|
|
92
|
+
standard-custom (~> 1.0.0)
|
|
93
|
+
standard-performance (~> 1.8)
|
|
94
|
+
standard-custom (1.0.2)
|
|
95
|
+
lint_roller (~> 1.0)
|
|
96
|
+
rubocop (~> 1.50)
|
|
97
|
+
standard-performance (1.9.0)
|
|
98
|
+
lint_roller (~> 1.1)
|
|
99
|
+
rubocop-performance (~> 1.26.0)
|
|
100
|
+
stringio (3.2.0)
|
|
58
101
|
sum_all_number_combinations (0.1.2)
|
|
59
|
-
|
|
102
|
+
tsort (0.2.0)
|
|
103
|
+
unicode-display_width (3.2.0)
|
|
104
|
+
unicode-emoji (~> 4.1)
|
|
105
|
+
unicode-emoji (4.2.0)
|
|
60
106
|
|
|
61
107
|
PLATFORMS
|
|
62
108
|
ruby
|
|
63
|
-
x64-
|
|
109
|
+
x64-unknown
|
|
64
110
|
x86-mingw32
|
|
65
111
|
|
|
66
112
|
DEPENDENCIES
|
|
67
113
|
cribbage_game!
|
|
114
|
+
irb
|
|
68
115
|
rake (~> 13.0)
|
|
69
116
|
rspec (~> 3.0)
|
|
70
|
-
standard (~> 1.
|
|
117
|
+
standard (~> 1.56)
|
|
71
118
|
|
|
72
119
|
BUNDLED WITH
|
|
73
120
|
2.3.6
|
data/README.md
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
# CribbageGame
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ruby [cribbage](https://bicyclecards.com/how-to-play/cribbage/) game engine. Includes a `play_with_ai` game runner for play against AI players using OpenAI models
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
gem 'cribbage_game'
|
|
7
|
+
```sh
|
|
8
|
+
gem install cribbage_game
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
And then execute:
|
|
14
|
-
|
|
15
|
-
$ bundle install
|
|
16
|
-
|
|
17
|
-
Or install it yourself as:
|
|
18
|
-
|
|
19
|
-
$ gem install cribbage_game
|
|
20
|
-
|
|
21
11
|
## Usage
|
|
22
12
|
|
|
23
13
|
```ruby
|
|
24
|
-
game = Game.new
|
|
14
|
+
game = CribbageGame::Game.new({ number_of_players: 2 })
|
|
25
15
|
game.cut_for_deal # randomly determines dealer
|
|
26
16
|
game.deal # deals 6 cards to game.dealer.hand & game.opponent.hand
|
|
27
17
|
game.dealer.discard ["7d", "qc"] # takes cards from hand and puts in crib
|
|
@@ -35,19 +25,31 @@ game.submit_hand_scores(game.dealer)
|
|
|
35
25
|
game.submit_crib_scores # adds the crib's score to game.dealer.total_score
|
|
36
26
|
game.deal # begin next round
|
|
37
27
|
```
|
|
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
28
|
|
|
46
29
|
## Development
|
|
47
30
|
|
|
48
|
-
|
|
31
|
+
```sh
|
|
32
|
+
bundle install
|
|
33
|
+
bundle exec rake spec # tests
|
|
34
|
+
bundle exec rake standard # linting
|
|
35
|
+
./bin/console # shell script to start an interactive console
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Play against AI
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
## Dependencies
|
|
41
|
+
```sh
|
|
42
|
+
bundle install
|
|
43
|
+
gem install ruby-openai
|
|
44
|
+
export OPENAI_API_KEY=your-api-key
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Playing the game
|
|
48
|
+
```sh
|
|
49
|
+
bin/play_with_ai --help # print command line options
|
|
50
|
+
bin/play_with_ai --players 3 --name Kevin --points-to-win 32 # initialize game
|
|
51
|
+
rdbg -O --port 12345 bin/play_with_ai # run with debugger
|
|
52
|
+
```
|
|
51
53
|
|
|
52
54
|
## Contributing
|
|
53
55
|
|
data/bin/play_with_ai
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
5
|
+
require "cribbage_game/play_with_ai"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
HumanVsAiRunner.from_argv(ARGV).run
|
|
9
|
+
rescue ArgumentError => error
|
|
10
|
+
warn error.message
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
data/cribbage_game.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "Two player cribbage card game"
|
|
12
12
|
spec.homepage = "https://github.com/buchowski/ruby-cribbage"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.required_ruby_version = ">=
|
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/buchowski/ruby-cribbage"
|
|
@@ -20,18 +20,25 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
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)})
|
|
23
|
+
files = `git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(f == __FILE__) || f.end_with?(".gem") || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
25
25
|
end
|
|
26
|
+
(files + ["bin/play_with_ai"]).uniq
|
|
26
27
|
end
|
|
27
|
-
spec.bindir = "
|
|
28
|
-
spec.executables =
|
|
28
|
+
spec.bindir = "bin"
|
|
29
|
+
spec.executables = ["play_with_ai"]
|
|
29
30
|
spec.require_paths = ["lib"]
|
|
30
31
|
|
|
31
|
-
spec.add_dependency "card_deck"
|
|
32
|
+
spec.add_dependency "card_deck", "~> 4.02"
|
|
32
33
|
spec.add_dependency "aasm", "~> 5.0", ">= 5.0.6"
|
|
33
34
|
spec.add_dependency "sum_all_number_combinations", "~> 0.1.2"
|
|
35
|
+
spec.add_dependency "ostruct", "~> 0.6.0"
|
|
36
|
+
spec.add_dependency "logger", ">= 1.7"
|
|
37
|
+
|
|
34
38
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
39
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
40
|
+
spec.add_development_dependency "standard", "~> 1.56"
|
|
41
|
+
spec.add_development_dependency "irb"
|
|
35
42
|
|
|
36
43
|
# For more information and examples about making a new gem, check out our
|
|
37
44
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/lib/cribbage_game/fsm.rb
CHANGED
|
@@ -4,11 +4,25 @@ module CribbageGame
|
|
|
4
4
|
class Fsm
|
|
5
5
|
include AASM
|
|
6
6
|
|
|
7
|
+
attr_accessor :game
|
|
8
|
+
|
|
9
|
+
def initialize(game = nil)
|
|
10
|
+
@game = game
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def three_player?
|
|
14
|
+
game&.players&.size == 3
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def two_player?
|
|
18
|
+
!three_player?
|
|
19
|
+
end
|
|
20
|
+
|
|
7
21
|
aasm do
|
|
8
22
|
state :cutting_for_deal, initial: true
|
|
9
23
|
state :flipping_top_card
|
|
10
24
|
state :discarding, :dealing, :playing
|
|
11
|
-
state :scoring_opponent_hand, :scoring_dealer_hand, :scoring_dealer_crib
|
|
25
|
+
state :scoring_opponent_hand, :scoring_opponent_2_hand, :scoring_dealer_hand, :scoring_dealer_crib
|
|
12
26
|
state :game_over
|
|
13
27
|
|
|
14
28
|
event :deal do
|
|
@@ -30,7 +44,9 @@ module CribbageGame
|
|
|
30
44
|
|
|
31
45
|
event :score do
|
|
32
46
|
transitions from: :playing, to: :scoring_opponent_hand
|
|
33
|
-
transitions from: :scoring_opponent_hand, to: :scoring_dealer_hand
|
|
47
|
+
transitions from: :scoring_opponent_hand, to: :scoring_dealer_hand, if: :two_player?
|
|
48
|
+
transitions from: :scoring_opponent_hand, to: :scoring_opponent_2_hand, if: :three_player?
|
|
49
|
+
transitions from: :scoring_opponent_2_hand, to: :scoring_dealer_hand
|
|
34
50
|
transitions from: :scoring_dealer_hand, to: :scoring_dealer_crib
|
|
35
51
|
end
|
|
36
52
|
|
data/lib/cribbage_game/game.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
require_relative "card"
|
|
2
|
+
require_relative "score"
|
|
3
|
+
require_relative "player"
|
|
4
|
+
require_relative "fsm"
|
|
5
5
|
|
|
6
6
|
module CribbageGame
|
|
7
7
|
class NotYourTurnError < RuntimeError; end
|
|
@@ -20,9 +20,15 @@ module CribbageGame
|
|
|
20
20
|
@points_to_win = args[:points_to_win] || 121
|
|
21
21
|
@auto_score = args[:is_auto_score] || true
|
|
22
22
|
@game_over_cb = args[:game_over_cb] || lambda {}
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
@number_of_players = args.fetch(:number_of_players, 2)
|
|
25
|
+
unless [2, 3].include?(@number_of_players)
|
|
26
|
+
raise ArgumentError, ":number_of_players must be either 2 or 3"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@players = @number_of_players.times.map { |id| Player.new self, id.to_s }
|
|
24
30
|
@score_client = Score.new self
|
|
25
|
-
@fsm = Fsm.new
|
|
31
|
+
@fsm = Fsm.new(self)
|
|
26
32
|
@deck = self.class.get_cards_hash CardDeck::Deck.new.cards
|
|
27
33
|
@round = 0
|
|
28
34
|
@winner = nil
|
|
@@ -30,6 +36,10 @@ module CribbageGame
|
|
|
30
36
|
reset_cards
|
|
31
37
|
end
|
|
32
38
|
|
|
39
|
+
def scorecards
|
|
40
|
+
@score_client.scorecards
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
def self.get_cards_hash cards
|
|
34
44
|
cards.each_with_object({}) do |card, card_map|
|
|
35
45
|
card_map[card.id] = card
|
|
@@ -40,16 +50,33 @@ module CribbageGame
|
|
|
40
50
|
card_ids.to_h { |id| [id, true] }
|
|
41
51
|
end
|
|
42
52
|
|
|
53
|
+
def is_three_player_game?
|
|
54
|
+
@number_of_players == 3
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def player_index(player)
|
|
58
|
+
@players.index(player)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def next_player(player)
|
|
62
|
+
current_index = player_index(player)
|
|
63
|
+
@players[(current_index + 1) % @players.size]
|
|
64
|
+
end
|
|
65
|
+
|
|
43
66
|
def undealt_card_ids
|
|
44
|
-
@
|
|
67
|
+
dealt_cards = @players.flat_map { |player| player.hand.keys }
|
|
68
|
+
dealt_cards += @crib if @crib
|
|
69
|
+
dealt_cards << @cut_card if @cut_card
|
|
70
|
+
|
|
71
|
+
@deck.keys.difference(dealt_cards)
|
|
45
72
|
end
|
|
46
73
|
|
|
47
74
|
def opponent
|
|
48
|
-
|
|
75
|
+
next_player(@dealer)
|
|
49
76
|
end
|
|
50
77
|
|
|
51
|
-
def
|
|
52
|
-
|
|
78
|
+
def opponent_2
|
|
79
|
+
is_three_player_game? ? next_player(opponent) : nil
|
|
53
80
|
end
|
|
54
81
|
|
|
55
82
|
def can_play_card? card_id
|
|
@@ -65,16 +92,12 @@ module CribbageGame
|
|
|
65
92
|
card_ids.map { |card_id| can_play_card?(card_id) }.any?
|
|
66
93
|
end
|
|
67
94
|
|
|
68
|
-
def
|
|
69
|
-
has_playable_card?
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def can_not_whose_turn_play?
|
|
73
|
-
has_playable_card? not_whose_turn.hand
|
|
95
|
+
def can_player_play? player
|
|
96
|
+
player.nil? ? false : has_playable_card?(player.hand)
|
|
74
97
|
end
|
|
75
98
|
|
|
76
|
-
def
|
|
77
|
-
|
|
99
|
+
def can_any_player_play?
|
|
100
|
+
@players.any? { |player| can_player_play? player }
|
|
78
101
|
end
|
|
79
102
|
|
|
80
103
|
def pile_score
|
|
@@ -105,16 +128,24 @@ module CribbageGame
|
|
|
105
128
|
raise WrongStateError if !@fsm.cutting_for_deal?
|
|
106
129
|
|
|
107
130
|
@dealer = @players.sample
|
|
108
|
-
@whose_turn =
|
|
131
|
+
@whose_turn = next_player(@dealer)
|
|
109
132
|
@fsm.deal
|
|
110
133
|
end
|
|
111
134
|
|
|
112
135
|
def deal
|
|
113
136
|
raise WrongStateError if !@fsm.dealing?
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
cards_per_player = is_three_player_game? ? 5 : 6
|
|
139
|
+
cards_to_draw = @players.size * cards_per_player
|
|
140
|
+
# draw an extra card for the crib in three player game
|
|
141
|
+
cards_to_draw += 1 if is_three_player_game?
|
|
142
|
+
|
|
143
|
+
random_card_ids = @deck.keys.sample cards_to_draw
|
|
144
|
+
@players.each do |player|
|
|
145
|
+
player.hand = self.class.get_hand_hash random_card_ids.slice!(0, cards_per_player)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
@crib << random_card_ids.shift if is_three_player_game?
|
|
118
149
|
|
|
119
150
|
@fsm.discard
|
|
120
151
|
end
|
|
@@ -124,13 +155,22 @@ module CribbageGame
|
|
|
124
155
|
|
|
125
156
|
player.hand[card_id] = false
|
|
126
157
|
@pile << card_id
|
|
127
|
-
is_last_card = !
|
|
158
|
+
is_last_card = !can_any_player_play?
|
|
128
159
|
@score_client.score_play(@pile, is_last_card, player)
|
|
129
160
|
@score_client.submit_play_score player
|
|
130
161
|
return if we_have_a_winner?
|
|
131
162
|
|
|
132
163
|
@pile = [] if is_last_card
|
|
133
|
-
|
|
164
|
+
next_player = next_player(player)
|
|
165
|
+
next_next_player = is_three_player_game? ? next_player(next_player) : nil
|
|
166
|
+
@whose_turn =
|
|
167
|
+
if can_player_play? next_player
|
|
168
|
+
next_player
|
|
169
|
+
elsif can_player_play? next_next_player
|
|
170
|
+
next_next_player
|
|
171
|
+
else
|
|
172
|
+
player
|
|
173
|
+
end
|
|
134
174
|
@fsm.score if player_hands_empty?
|
|
135
175
|
end
|
|
136
176
|
|
|
@@ -138,7 +178,7 @@ module CribbageGame
|
|
|
138
178
|
raise WrongStateError if !@fsm.flipping_top_card?
|
|
139
179
|
|
|
140
180
|
@cut_card = top_card || undealt_card_ids.sample
|
|
141
|
-
@whose_turn =
|
|
181
|
+
@whose_turn = next_player(@dealer)
|
|
142
182
|
@score_client.score_crib
|
|
143
183
|
@score_client.score_hands
|
|
144
184
|
@fsm.play
|
|
@@ -157,9 +197,11 @@ module CribbageGame
|
|
|
157
197
|
def submit_hand_scores player
|
|
158
198
|
raise NotYourTurnError if player == @dealer && !@fsm.scoring_dealer_hand?
|
|
159
199
|
raise NotYourTurnError if player == opponent && !@fsm.scoring_opponent_hand?
|
|
200
|
+
raise NotYourTurnError if player == opponent_2 && !@fsm.scoring_opponent_2_hand?
|
|
160
201
|
|
|
161
202
|
@score_client.submit_scores player, :hand
|
|
162
203
|
return if we_have_a_winner?
|
|
204
|
+
|
|
163
205
|
@fsm.score
|
|
164
206
|
end
|
|
165
207
|
|
|
@@ -169,7 +211,7 @@ module CribbageGame
|
|
|
169
211
|
@score_client.submit_scores @dealer, :crib
|
|
170
212
|
return if we_have_a_winner?
|
|
171
213
|
reset_cards
|
|
172
|
-
@dealer =
|
|
214
|
+
@dealer = next_player(@dealer)
|
|
173
215
|
@fsm.deal
|
|
174
216
|
end
|
|
175
217
|
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "optparse"
|
|
3
|
+
require_relative "game"
|
|
4
|
+
|
|
5
|
+
class HumanVsAiRunner
|
|
6
|
+
MODEL = "gpt-5-nano"
|
|
7
|
+
USAGE = "Usage: play_with_ai [--name NAME] [--players 2|3] [--points-to-win POINTS] [--all-ai] [--log-ai-inputs]"
|
|
8
|
+
|
|
9
|
+
DISCARD_TOOL = {
|
|
10
|
+
"type" => "function",
|
|
11
|
+
"name" => "discard",
|
|
12
|
+
"description" => "Discard the required number of cards from the player's hand",
|
|
13
|
+
"parameters" => {
|
|
14
|
+
"type" => "object",
|
|
15
|
+
"properties" => {
|
|
16
|
+
"card_ids" => {
|
|
17
|
+
"type" => "array",
|
|
18
|
+
"items" => {"type" => "string"},
|
|
19
|
+
"description" => "The card identifiers to discard"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required" => ["card_ids"],
|
|
23
|
+
"additionalProperties" => false
|
|
24
|
+
}
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
PLAY_CARD_TOOL = {
|
|
28
|
+
"type" => "function",
|
|
29
|
+
"name" => "play_card",
|
|
30
|
+
"description" => "Play one legal card from the player's hand",
|
|
31
|
+
"parameters" => {
|
|
32
|
+
"type" => "object",
|
|
33
|
+
"properties" => {
|
|
34
|
+
"card_id" => {
|
|
35
|
+
"type" => "string",
|
|
36
|
+
"description" => "A card identifier such as 5h, kd, or 10s"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"required" => ["card_id"],
|
|
40
|
+
"additionalProperties" => false
|
|
41
|
+
}
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def initialize(number_of_players: 2, points_to_win: 121, name: nil, all_ai: false, log_ai_inputs: false, client: nil, input: $stdin, output: $stdout)
|
|
45
|
+
unless [2, 3].include?(number_of_players)
|
|
46
|
+
raise ArgumentError, "number_of_players must be either 2 or 3"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@number_of_players = number_of_players
|
|
50
|
+
@points_to_win = points_to_win
|
|
51
|
+
@all_ai = all_ai
|
|
52
|
+
@human_name = name.to_s.strip
|
|
53
|
+
@human_name = "Human Player" if @human_name.empty?
|
|
54
|
+
@log_ai_inputs = log_ai_inputs
|
|
55
|
+
@client = client || default_client
|
|
56
|
+
@input = input
|
|
57
|
+
@output = output
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.from_argv(arguments)
|
|
61
|
+
options = {name: nil, number_of_players: 2, points_to_win: 121, all_ai: false, log_ai_inputs: false}
|
|
62
|
+
parser = OptionParser.new do |opts|
|
|
63
|
+
opts.banner = USAGE
|
|
64
|
+
opts.on_tail("-h", "--help", "Show available options") do
|
|
65
|
+
puts opts
|
|
66
|
+
exit
|
|
67
|
+
end
|
|
68
|
+
opts.on("--name NAME", String, "Human player's name (default: Human Player)") { |name| options[:name] = name }
|
|
69
|
+
opts.on("--players COUNT", Integer, "Number of players (2 or 3; default: 2)") do |count|
|
|
70
|
+
options[:number_of_players] = count
|
|
71
|
+
end
|
|
72
|
+
opts.on("--number-of-players COUNT", Integer, "Alias for --players (default: 2)") do |count|
|
|
73
|
+
options[:number_of_players] = count
|
|
74
|
+
end
|
|
75
|
+
opts.on("--points-to-win POINTS", Integer, "Points needed to win (default: 121)") do |points|
|
|
76
|
+
options[:points_to_win] = points
|
|
77
|
+
end
|
|
78
|
+
opts.on("--log-ai-inputs", "Log AI inputs and failed tool attempts") do
|
|
79
|
+
options[:log_ai_inputs] = true
|
|
80
|
+
end
|
|
81
|
+
opts.on("--all-ai", "Watch all players play by AI") do
|
|
82
|
+
options[:all_ai] = true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
remaining_arguments = parser.parse!(arguments.dup)
|
|
87
|
+
raise ArgumentError, "Unexpected arguments: #{remaining_arguments.join(" ")}" unless remaining_arguments.empty?
|
|
88
|
+
|
|
89
|
+
new(**options)
|
|
90
|
+
rescue OptionParser::ParseError, ArgumentError => error
|
|
91
|
+
raise ArgumentError, "#{error.message}\n#{USAGE}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def run
|
|
95
|
+
game = CribbageGame::Game.new(number_of_players: @number_of_players, points_to_win: @points_to_win)
|
|
96
|
+
human = @all_ai ? nil : game.players.first
|
|
97
|
+
human.name = @human_name unless human.nil?
|
|
98
|
+
@human_player = human
|
|
99
|
+
|
|
100
|
+
game.cut_for_deal
|
|
101
|
+
until game.fsm.game_over?
|
|
102
|
+
announce_round(game, human)
|
|
103
|
+
game.deal
|
|
104
|
+
discard_hands(game, human)
|
|
105
|
+
game.flip_top_card
|
|
106
|
+
log("Cut card: #{game.cut_card}")
|
|
107
|
+
play_cards(game, human)
|
|
108
|
+
break if game.fsm.game_over?
|
|
109
|
+
|
|
110
|
+
log("")
|
|
111
|
+
score_round(game)
|
|
112
|
+
log("")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
log("Winner: #{player_label(game.winner)} with #{game.winner.total_score} points.")
|
|
116
|
+
game
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def default_client
|
|
122
|
+
require "openai"
|
|
123
|
+
OpenAI::Client.new(
|
|
124
|
+
access_token: ENV.fetch("OPENAI_API_KEY"),
|
|
125
|
+
log_errors: true
|
|
126
|
+
)
|
|
127
|
+
rescue LoadError
|
|
128
|
+
raise LoadError, "The AI runner requires the ruby-openai gem"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def discard_hands(game, human)
|
|
132
|
+
game.players.each do |player|
|
|
133
|
+
count = game.is_three_player_game? ? 1 : 2
|
|
134
|
+
if player == human
|
|
135
|
+
human_discard(player, count)
|
|
136
|
+
else
|
|
137
|
+
ai_discard(player, count)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def human_discard(player, count)
|
|
143
|
+
loop do
|
|
144
|
+
ids = prompt("Your hand: #{available_cards(player).join(", ")}\nDiscard #{count} card#{"s" if count > 1}: ")
|
|
145
|
+
.split(/[\s,]+/)
|
|
146
|
+
.reject(&:empty?)
|
|
147
|
+
|
|
148
|
+
if ids.size == count && ids.uniq.size == count && ids.all? { |id| player.hand[id] }
|
|
149
|
+
player.discard(ids)
|
|
150
|
+
return
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
log("Please enter exactly #{count} card IDs from your hand.")
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ai_discard(player, count)
|
|
158
|
+
arguments = ai_tool_arguments(
|
|
159
|
+
player,
|
|
160
|
+
DISCARD_TOOL,
|
|
161
|
+
"Choose #{count} cards to discard from your hand: #{available_cards(player).join(", ")} ."
|
|
162
|
+
) do |data|
|
|
163
|
+
ids = data["card_ids"]
|
|
164
|
+
ids.is_a?(Array) && ids.size == count && ids.uniq.size == count && ids.all? { |id| player.hand[id] }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
ids = arguments.fetch("card_ids")
|
|
168
|
+
log("#{player_label(player)} discards: #{ids.join(", ")}")
|
|
169
|
+
player.discard(ids)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def play_cards(game, human)
|
|
173
|
+
while game.fsm.playing? && !game.player_hands_empty?
|
|
174
|
+
player = game.whose_turn
|
|
175
|
+
playable = available_cards(player).select { |id| game.can_play_card?(id) }
|
|
176
|
+
|
|
177
|
+
card_id = if player == human
|
|
178
|
+
prompt_for_play(game, player, playable)
|
|
179
|
+
else
|
|
180
|
+
ai_play(game, player, playable)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
pile_was_active = !game.pile.empty?
|
|
184
|
+
pile_total_after_play = game.pile_score + game.deck.fetch(card_id).value
|
|
185
|
+
game.play_card(player, card_id)
|
|
186
|
+
play_score = game.scorecards.fetch(game.round).fetch(:play).last
|
|
187
|
+
log("#{player_label(player)} played #{card_id}; pile total #{pile_total_after_play}")
|
|
188
|
+
if play_score[:points] > 0
|
|
189
|
+
log("#{player_label(player)} scored #{play_score[:points]} point#{"s" if play_score[:points] != 1} during play: #{format_score_reasons(play_score[:reasons])}.")
|
|
190
|
+
end
|
|
191
|
+
if pile_was_active && game.pile.empty?
|
|
192
|
+
log("")
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def prompt_for_play(game, player, playable)
|
|
198
|
+
loop do
|
|
199
|
+
card_id = prompt(
|
|
200
|
+
"Your cards: #{available_cards(player).join(", ")} " \
|
|
201
|
+
"(playable: #{playable.join(", ")}; pile: #{game.pile_score})\nPlay card: "
|
|
202
|
+
).strip.downcase
|
|
203
|
+
return card_id if playable.include?(card_id)
|
|
204
|
+
|
|
205
|
+
log("Choose one of the playable cards: #{playable.join(", ")}.")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def ai_play(game, player, playable)
|
|
210
|
+
arguments = ai_tool_arguments(
|
|
211
|
+
player,
|
|
212
|
+
PLAY_CARD_TOOL,
|
|
213
|
+
"Choose one card to play. Playable cards: #{playable.join(", ")}. " \
|
|
214
|
+
"Current pile total: #{game.pile_score}."
|
|
215
|
+
) { |data| playable.include?(data["card_id"]) }
|
|
216
|
+
|
|
217
|
+
arguments.fetch("card_id")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def score_round(game)
|
|
221
|
+
round_scorecard = game.scorecards.fetch(game.round)
|
|
222
|
+
pegging_scores = round_scorecard.fetch(:play, []).group_by { |play| play.fetch(:player_id) }
|
|
223
|
+
scoring_players = [game.opponent]
|
|
224
|
+
scoring_players << game.opponent_2 if game.is_three_player_game?
|
|
225
|
+
scoring_players << game.dealer
|
|
226
|
+
|
|
227
|
+
scoring_players.each do |player|
|
|
228
|
+
pegging_points = pegging_scores.fetch(player.id, []).sum { |play| play.fetch(:points) }
|
|
229
|
+
log("#{player_label(player)} pegging score: #{pegging_points} point#{"s" if pegging_points != 1}.")
|
|
230
|
+
hand_score = round_scorecard.fetch(player.id).fetch(:hand).fetch(:total_score)
|
|
231
|
+
log("#{player_label(player)} hand score: #{hand_score} point#{"s" if hand_score != 1}.")
|
|
232
|
+
game.submit_hand_scores(player)
|
|
233
|
+
break if game.fsm.game_over?
|
|
234
|
+
end
|
|
235
|
+
return if game.fsm.game_over?
|
|
236
|
+
|
|
237
|
+
crib_owner = game.dealer
|
|
238
|
+
crib_score = round_scorecard.fetch(crib_owner.id).fetch(:crib).fetch(:total_score)
|
|
239
|
+
game.submit_crib_scores
|
|
240
|
+
log("#{player_label(crib_owner)} crib score: #{crib_score} point#{"s" if crib_score != 1}.")
|
|
241
|
+
log("Player totals: #{game.players.map { |player| "#{player_label(player)}: #{player.total_score}" }.join(", ")}")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def ai_tool_arguments(player, tool, input)
|
|
245
|
+
3.times do |attempt|
|
|
246
|
+
log("#{player_label(player)} input: #{input}") if @log_ai_inputs
|
|
247
|
+
response = @client.responses.create(
|
|
248
|
+
parameters: {
|
|
249
|
+
model: MODEL,
|
|
250
|
+
input: input,
|
|
251
|
+
tools: [tool],
|
|
252
|
+
tool_choice: "required"
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
function_call = response.dig("output")&.find { |item| item["type"] == "function_call" }
|
|
256
|
+
tool_name = function_call&.fetch("name", tool["name"])
|
|
257
|
+
raw_arguments = function_call&.fetch("arguments", nil)
|
|
258
|
+
arguments = raw_arguments && JSON.parse(raw_arguments)
|
|
259
|
+
return arguments if arguments && yield(arguments)
|
|
260
|
+
|
|
261
|
+
if @log_ai_inputs
|
|
262
|
+
log("#{player_label(player)} failed tool #{tool_name} with arguments #{arguments.inspect} (attempt #{attempt + 1}).")
|
|
263
|
+
end
|
|
264
|
+
rescue JSON::ParserError, KeyError, TypeError
|
|
265
|
+
if @log_ai_inputs
|
|
266
|
+
log("#{player_label(player)} failed tool #{tool_name || tool["name"]} with raw arguments #{raw_arguments.inspect} (attempt #{attempt + 1}).")
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
raise "#{player_label(player)} failed to provide a legal action"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def announce_round(game, human)
|
|
274
|
+
dealer = (game.dealer == human) ? @human_name : player_label(game.dealer)
|
|
275
|
+
log("Round #{game.round}: dealer is #{dealer}.")
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def player_label(player)
|
|
279
|
+
return @human_name if player == @human_player
|
|
280
|
+
|
|
281
|
+
ai_names = @all_ai ? {"0" => "One", "1" => "Two", "2" => "Three"} : {"1" => "One", "2" => "Two", "0" => "Three"}
|
|
282
|
+
ai_name = ai_names.fetch(player.id, player.id)
|
|
283
|
+
"AI Player #{ai_name}"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def format_score_reasons(reasons)
|
|
287
|
+
reasons.map { |reason| "#{reason[:type].tr("_", " ")} (#{reason[:points]})" }.join(", ")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def available_cards(player)
|
|
291
|
+
player.hand.select { |_id, available| available }.keys
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def prompt(message)
|
|
295
|
+
@output.print(message)
|
|
296
|
+
line = @input.gets
|
|
297
|
+
raise "Input ended before the game finished" if line.nil?
|
|
298
|
+
|
|
299
|
+
line
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def log(message)
|
|
303
|
+
@output.puts(message)
|
|
304
|
+
end
|
|
305
|
+
end
|
data/lib/cribbage_game/score.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
require_relative "score_utils"
|
|
2
2
|
|
|
3
3
|
module CribbageGame
|
|
4
4
|
class Score
|
|
@@ -9,6 +9,8 @@ module CribbageGame
|
|
|
9
9
|
@scorecards = {}
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
attr_reader :scorecards
|
|
13
|
+
|
|
12
14
|
def get_cards card_ids
|
|
13
15
|
card_ids.map { |id| @game.deck[id] }
|
|
14
16
|
end
|
|
@@ -47,9 +49,10 @@ module CribbageGame
|
|
|
47
49
|
def score_play pile_ids, is_last_card, player
|
|
48
50
|
@scorecards[@game.round] = @scorecards[@game.round] || {play: []}
|
|
49
51
|
pile_cards = get_cards pile_ids
|
|
50
|
-
|
|
52
|
+
score = get_pile_points(pile_cards, is_last_card) # pass pile cards not ids
|
|
51
53
|
@scorecards[@game.round][:play] << {
|
|
52
|
-
points: points,
|
|
54
|
+
points: score[:points],
|
|
55
|
+
reasons: score[:reasons],
|
|
53
56
|
player_id: player.id,
|
|
54
57
|
pile: pile_ids,
|
|
55
58
|
card_id: pile_ids.last
|
|
@@ -112,15 +112,26 @@ module CribbageGame
|
|
|
112
112
|
|
|
113
113
|
throw "31 should always be is_last_card" if pile_score == 31 && !is_last_card
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
reasons = []
|
|
116
|
+
if pile_score == 31
|
|
117
|
+
reasons << {type: "thirty_one", points: 2}
|
|
118
|
+
elsif pile_score == 15
|
|
119
|
+
reasons << {type: "fifteen", points: 2}
|
|
120
|
+
end
|
|
121
|
+
if is_last_card && pile_score != 31
|
|
122
|
+
reasons << {type: "last_card", points: 1}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
consecutive_points = score_consecutive pile_cards
|
|
126
|
+
if consecutive_points > 0
|
|
127
|
+
consecutive_type = {2 => "pair", 6 => "three_of_a_kind", 12 => "four_of_a_kind"}.fetch(consecutive_points)
|
|
128
|
+
reasons << {type: consecutive_type, points: consecutive_points}
|
|
129
|
+
end
|
|
118
130
|
|
|
119
|
-
|
|
120
|
-
points
|
|
121
|
-
points += score_pile_runs pile_cards
|
|
131
|
+
run_points = score_pile_runs pile_cards
|
|
132
|
+
reasons << {type: "run", points: run_points} if run_points > 0
|
|
122
133
|
|
|
123
|
-
points
|
|
134
|
+
{points: reasons.sum { |reason| reason[:points] }, reasons: reasons}
|
|
124
135
|
end
|
|
125
136
|
end
|
|
126
137
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cribbage_game
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- buchowski
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: card_deck
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '4.02'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '4.02'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: aasm
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,6 +58,34 @@ dependencies:
|
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
60
|
version: 0.1.2
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: ostruct
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 0.6.0
|
|
68
|
+
type: :runtime
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 0.6.0
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: logger
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.7'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.7'
|
|
61
89
|
- !ruby/object:Gem::Dependency
|
|
62
90
|
name: rspec
|
|
63
91
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,10 +100,53 @@ dependencies:
|
|
|
72
100
|
- - "~>"
|
|
73
101
|
- !ruby/object:Gem::Version
|
|
74
102
|
version: '3.0'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: rake
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '13.0'
|
|
110
|
+
type: :development
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '13.0'
|
|
117
|
+
- !ruby/object:Gem::Dependency
|
|
118
|
+
name: standard
|
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '1.56'
|
|
124
|
+
type: :development
|
|
125
|
+
prerelease: false
|
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '1.56'
|
|
131
|
+
- !ruby/object:Gem::Dependency
|
|
132
|
+
name: irb
|
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
138
|
+
type: :development
|
|
139
|
+
prerelease: false
|
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
75
145
|
description:
|
|
76
146
|
email:
|
|
77
147
|
- bucholz.adam@gmail.com
|
|
78
|
-
executables:
|
|
148
|
+
executables:
|
|
149
|
+
- play_with_ai
|
|
79
150
|
extensions: []
|
|
80
151
|
extra_rdoc_files: []
|
|
81
152
|
files:
|
|
@@ -86,11 +157,13 @@ files:
|
|
|
86
157
|
- LICENSE.txt
|
|
87
158
|
- README.md
|
|
88
159
|
- Rakefile
|
|
160
|
+
- bin/play_with_ai
|
|
89
161
|
- cribbage_game.gemspec
|
|
90
162
|
- lib/cribbage_game.rb
|
|
91
163
|
- lib/cribbage_game/card.rb
|
|
92
164
|
- lib/cribbage_game/fsm.rb
|
|
93
165
|
- lib/cribbage_game/game.rb
|
|
166
|
+
- lib/cribbage_game/play_with_ai.rb
|
|
94
167
|
- lib/cribbage_game/player.rb
|
|
95
168
|
- lib/cribbage_game/score.rb
|
|
96
169
|
- lib/cribbage_game/score_utils.rb
|
|
@@ -110,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
110
183
|
requirements:
|
|
111
184
|
- - ">="
|
|
112
185
|
- !ruby/object:Gem::Version
|
|
113
|
-
version:
|
|
186
|
+
version: 3.0.0
|
|
114
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
188
|
requirements:
|
|
116
189
|
- - ">="
|