console-blackjack 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 +7 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +108 -0
- data/LICENSE +21 -0
- data/README.md +28 -0
- data/Rakefile +5 -0
- data/bin/console-blackjack +5 -0
- data/bj.png +0 -0
- data/bj.txt +1 -0
- data/console-blackjack.gemspec +25 -0
- data/lib/blackjack.rb +382 -0
- data/lib/blackjack/card.rb +29 -0
- data/lib/blackjack/dealer_hand.rb +63 -0
- data/lib/blackjack/hand.rb +33 -0
- data/lib/blackjack/player_hand.rb +184 -0
- data/lib/blackjack/shoe.rb +108 -0
- data/spec/factories/blackjack_factory.rb +13 -0
- data/spec/factories/card_factory.rb +42 -0
- data/spec/factories/dealer_hand_factory.rb +10 -0
- data/spec/factories/hand_factory.rb +11 -0
- data/spec/factories/player_hand_factory.rb +13 -0
- data/spec/factories/shoe_factory.rb +14 -0
- data/spec/lib/blackjack/card_spec.rb +53 -0
- data/spec/lib/blackjack/dealer_hand_spec.rb +223 -0
- data/spec/lib/blackjack/hand_spec.rb +53 -0
- data/spec/lib/blackjack/player_hand_spec.rb +563 -0
- data/spec/lib/blackjack/shoe_spec.rb +164 -0
- data/spec/lib/blackjack_spec.rb +871 -0
- data/spec/spec_helper.rb +36 -0
- metadata +75 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Card
|
4
|
+
attr_reader :value, :suite
|
5
|
+
|
6
|
+
def initialize(value, suite)
|
7
|
+
@value = value
|
8
|
+
@suite = suite
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
Card.faces[value][suite]
|
13
|
+
end
|
14
|
+
|
15
|
+
def ace?
|
16
|
+
value.zero?
|
17
|
+
end
|
18
|
+
|
19
|
+
def ten?
|
20
|
+
value > 8
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.faces
|
24
|
+
[%w[🂡 🂱 🃁 🃑], %w[🂢 🂲 🃂 🃒], %w[🂣 🂳 🃃 🃓], %w[🂤 🂴 🃄 🃔],
|
25
|
+
%w[🂥 🂵 🃅 🃕], %w[🂦 🂶 🃆 🃖], %w[🂧 🂷 🃇 🃗], %w[🂨 🂸 🃈 🃘],
|
26
|
+
%w[🂩 🂹 🃉 🃙], %w[🂪 🂺 🃊 🃚], %w[🂫 🂻 🃋 🃛], %w[🂭 🂽 🃍 🃝],
|
27
|
+
%w[🂮 🂾 🃎 🃞], %w[🂠]]
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'hand'
|
4
|
+
|
5
|
+
class DealerHand < Hand
|
6
|
+
attr_accessor :blackjack, :hide_down_card
|
7
|
+
|
8
|
+
def initialize(blackjack)
|
9
|
+
super(blackjack)
|
10
|
+
@hide_down_card = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def value(count_method)
|
14
|
+
total = 0
|
15
|
+
cards.each_with_index do |card, index|
|
16
|
+
next if index == 1 && hide_down_card
|
17
|
+
|
18
|
+
value = card.value + 1
|
19
|
+
v = value > 9 ? 10 : value
|
20
|
+
v = 11 if count_method == SOFT && v == 1 && total < 11
|
21
|
+
total += v
|
22
|
+
end
|
23
|
+
|
24
|
+
if count_method == SOFT && total > 21
|
25
|
+
value(HARD)
|
26
|
+
else
|
27
|
+
total
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def upcard_is_ace?
|
32
|
+
cards.first.ace?
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw
|
36
|
+
out = String.new(' ')
|
37
|
+
cards.each_with_index do |card, index|
|
38
|
+
out << (index == 1 && hide_down_card ? Card.faces[13][0] : card).to_s
|
39
|
+
out << ' '
|
40
|
+
end
|
41
|
+
out << ' ⇒ ' << value(SOFT).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def deal_required_cards
|
45
|
+
soft, hard = both_values
|
46
|
+
while soft < 18 && hard < 17
|
47
|
+
deal_card
|
48
|
+
soft, hard = both_values
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def both_values
|
53
|
+
[value(SOFT), value(HARD)]
|
54
|
+
end
|
55
|
+
|
56
|
+
def play
|
57
|
+
playing = blackjack.need_to_play_dealer_hand?
|
58
|
+
self.hide_down_card = false if blackjack? || playing
|
59
|
+
deal_required_cards if playing
|
60
|
+
self.played = true
|
61
|
+
blackjack.pay_hands
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
UNKNOWN = 0
|
4
|
+
WON = 1
|
5
|
+
LOST = 2
|
6
|
+
PUSH = 3
|
7
|
+
|
8
|
+
SOFT = 4
|
9
|
+
HARD = 5
|
10
|
+
|
11
|
+
class Hand
|
12
|
+
attr_accessor :cards, :blackjack, :played
|
13
|
+
|
14
|
+
def initialize(blackjack)
|
15
|
+
@blackjack = blackjack
|
16
|
+
@played = false
|
17
|
+
@cards = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def busted?
|
21
|
+
value(SOFT) > 21
|
22
|
+
end
|
23
|
+
|
24
|
+
def deal_card
|
25
|
+
cards << blackjack.shoe.next_card
|
26
|
+
end
|
27
|
+
|
28
|
+
def blackjack?
|
29
|
+
return false if cards.size != 2
|
30
|
+
|
31
|
+
cards.first.ace? && cards.last.ten? || cards.first.ten? && cards.last.ace?
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'hand'
|
4
|
+
|
5
|
+
class PlayerHand < Hand
|
6
|
+
MAX_PLAYER_HANDS = 7
|
7
|
+
|
8
|
+
attr_accessor :blackjack, :bet, :status, :payed, :cards, :stood
|
9
|
+
|
10
|
+
def initialize(blackjack, bet)
|
11
|
+
super(blackjack)
|
12
|
+
@bet = bet
|
13
|
+
@status = UNKNOWN
|
14
|
+
@payed = false
|
15
|
+
@stood = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def pay(dealer_hand_value, dealer_busted)
|
19
|
+
return if payed
|
20
|
+
|
21
|
+
self.payed = true
|
22
|
+
player_hand_value = value(SOFT)
|
23
|
+
|
24
|
+
if dealer_busted || player_hand_value > dealer_hand_value
|
25
|
+
self.bet *= 1.5 if blackjack?
|
26
|
+
blackjack.money += bet
|
27
|
+
self.status = WON
|
28
|
+
elsif player_hand_value < dealer_hand_value
|
29
|
+
blackjack.money -= bet
|
30
|
+
self.status = LOST
|
31
|
+
else
|
32
|
+
self.status = PUSH
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def value(count_method)
|
37
|
+
total = 0
|
38
|
+
cards.each do |card|
|
39
|
+
value = card.value + 1
|
40
|
+
v = value > 9 ? 10 : value
|
41
|
+
v = 11 if count_method == SOFT && v == 1 && total < 11
|
42
|
+
total += v
|
43
|
+
end
|
44
|
+
|
45
|
+
return value(HARD) if count_method == SOFT && total > 21
|
46
|
+
|
47
|
+
total
|
48
|
+
end
|
49
|
+
|
50
|
+
def done?
|
51
|
+
if played || stood || blackjack? || busted? ||
|
52
|
+
value(SOFT) == 21 ||
|
53
|
+
value(HARD) == 21
|
54
|
+
self.played = true
|
55
|
+
|
56
|
+
if !payed && busted?
|
57
|
+
self.payed = true
|
58
|
+
self.status = LOST
|
59
|
+
blackjack.money -= bet
|
60
|
+
end
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def can_split?
|
68
|
+
return false if stood || blackjack.player_hands.size >= MAX_PLAYER_HANDS
|
69
|
+
|
70
|
+
return false if blackjack.money < blackjack.all_bets + bet
|
71
|
+
|
72
|
+
cards.size == 2 && cards.first.value == cards.last.value
|
73
|
+
end
|
74
|
+
|
75
|
+
def can_dbl?
|
76
|
+
return false if blackjack.money < blackjack.all_bets + bet
|
77
|
+
|
78
|
+
!(stood || cards.size != 2 || blackjack?)
|
79
|
+
end
|
80
|
+
|
81
|
+
def can_stand?
|
82
|
+
!(stood || busted? || blackjack?)
|
83
|
+
end
|
84
|
+
|
85
|
+
def can_hit?
|
86
|
+
!(played || stood || value(HARD) == 21 || blackjack? || busted?)
|
87
|
+
end
|
88
|
+
|
89
|
+
def hit
|
90
|
+
deal_card
|
91
|
+
|
92
|
+
if done?
|
93
|
+
process
|
94
|
+
else
|
95
|
+
blackjack.draw_hands
|
96
|
+
blackjack.current_player_hand.action?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def dbl
|
101
|
+
deal_card
|
102
|
+
|
103
|
+
self.played = true
|
104
|
+
self.bet *= 2
|
105
|
+
process if done?
|
106
|
+
end
|
107
|
+
|
108
|
+
def stand
|
109
|
+
self.stood = true
|
110
|
+
self.played = true
|
111
|
+
process
|
112
|
+
end
|
113
|
+
|
114
|
+
def process
|
115
|
+
if blackjack.more_hands_to_play?
|
116
|
+
blackjack.play_more_hands
|
117
|
+
else
|
118
|
+
blackjack.play_dealer_hand
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def draw(index)
|
123
|
+
out = String.new(' ')
|
124
|
+
cards.each do |card|
|
125
|
+
out << "#{card} "
|
126
|
+
end
|
127
|
+
|
128
|
+
out << ' ⇒ ' << value(SOFT).to_s << ' '
|
129
|
+
|
130
|
+
if status == LOST
|
131
|
+
out << '-'
|
132
|
+
elsif status == WON
|
133
|
+
out << '+'
|
134
|
+
end
|
135
|
+
|
136
|
+
out << '$' << Blackjack.format_money(bet / 100.0)
|
137
|
+
out << ' ⇐' if !played && index == blackjack.current_hand
|
138
|
+
out << ' '
|
139
|
+
|
140
|
+
if status == LOST
|
141
|
+
out << (busted? ? 'Busted!' : 'Lose!')
|
142
|
+
elsif status == WON
|
143
|
+
out << (blackjack? ? 'Blackjack!' : 'Won!')
|
144
|
+
elsif status == PUSH
|
145
|
+
out << 'Push'
|
146
|
+
end
|
147
|
+
|
148
|
+
out << "\n\n"
|
149
|
+
out
|
150
|
+
end
|
151
|
+
|
152
|
+
def action?
|
153
|
+
out = String.new(' ')
|
154
|
+
out << '(H) Hit ' if can_hit?
|
155
|
+
out << '(S) Stand ' if can_stand?
|
156
|
+
out << '(P) Split ' if can_split?
|
157
|
+
out << '(D) Double ' if can_dbl?
|
158
|
+
puts out
|
159
|
+
|
160
|
+
loop do
|
161
|
+
br = false
|
162
|
+
case Blackjack.getc
|
163
|
+
when 'h'
|
164
|
+
br = true
|
165
|
+
hit
|
166
|
+
when 's'
|
167
|
+
br = true
|
168
|
+
stand
|
169
|
+
when 'p'
|
170
|
+
br = true
|
171
|
+
blackjack.split_current_hand
|
172
|
+
when 'd'
|
173
|
+
br = true
|
174
|
+
dbl
|
175
|
+
else
|
176
|
+
blackjack.clear
|
177
|
+
blackjack.draw_hands
|
178
|
+
action?
|
179
|
+
end
|
180
|
+
|
181
|
+
break if br
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'card'
|
4
|
+
|
5
|
+
class Shoe
|
6
|
+
attr_accessor :num_decks, :cards
|
7
|
+
|
8
|
+
def initialize(num_decks = 1)
|
9
|
+
@num_decks = num_decks
|
10
|
+
@cards = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def needs_to_shuffle?
|
14
|
+
return true if cards.size.zero?
|
15
|
+
|
16
|
+
total_cards = num_decks * 52
|
17
|
+
cards_dealt = total_cards - cards.size
|
18
|
+
used = cards_dealt / total_cards.to_f * 100.0
|
19
|
+
|
20
|
+
Shoe.shuffle_specs.each do |spec|
|
21
|
+
return true if used > spec.first && num_decks == spec.last
|
22
|
+
end
|
23
|
+
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def shuffle
|
28
|
+
7.times { cards.shuffle! }
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_regular
|
32
|
+
self.cards = []
|
33
|
+
num_decks.times do
|
34
|
+
(0..3).each do |suite_value|
|
35
|
+
(0..12).each do |value|
|
36
|
+
cards << Card.new(value, suite_value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
shuffle
|
41
|
+
end
|
42
|
+
|
43
|
+
def new_aces
|
44
|
+
self.cards = []
|
45
|
+
(num_decks * 10).times do
|
46
|
+
(0..3).each do |suite_value|
|
47
|
+
cards << Card.new(0, suite_value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
shuffle
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_jacks
|
54
|
+
self.cards = []
|
55
|
+
(num_decks * 10).times do
|
56
|
+
(0..3).each do |suite_value|
|
57
|
+
cards << Card.new(10, suite_value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
shuffle
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_aces_jacks
|
64
|
+
self.cards = []
|
65
|
+
(num_decks * 10).times do
|
66
|
+
(0..3).each do |suite_value|
|
67
|
+
cards << Card.new(0, suite_value)
|
68
|
+
cards << Card.new(10, suite_value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
shuffle
|
72
|
+
end
|
73
|
+
|
74
|
+
def new_sevens
|
75
|
+
self.cards = []
|
76
|
+
(num_decks * 10).times do
|
77
|
+
(0..3).each do |suite_value|
|
78
|
+
cards << Card.new(6, suite_value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
shuffle
|
82
|
+
end
|
83
|
+
|
84
|
+
def new_eights
|
85
|
+
self.cards = []
|
86
|
+
(num_decks * 10).times do
|
87
|
+
(0..3).each do |suite_value|
|
88
|
+
cards << Card.new(7, suite_value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
shuffle
|
92
|
+
end
|
93
|
+
|
94
|
+
def next_card
|
95
|
+
cards.shift
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.shuffle_specs
|
99
|
+
[[95, 8],
|
100
|
+
[92, 7],
|
101
|
+
[89, 6],
|
102
|
+
[86, 5],
|
103
|
+
[84, 4],
|
104
|
+
[82, 3],
|
105
|
+
[81, 2],
|
106
|
+
[80, 1]]
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :card do
|
5
|
+
value { 0 }
|
6
|
+
suite { 0 }
|
7
|
+
|
8
|
+
trait :ace do
|
9
|
+
value { 0 }
|
10
|
+
end
|
11
|
+
|
12
|
+
trait :two do
|
13
|
+
value { 1 }
|
14
|
+
end
|
15
|
+
|
16
|
+
trait :five do
|
17
|
+
value { 4 }
|
18
|
+
end
|
19
|
+
|
20
|
+
trait :six do
|
21
|
+
value { 5 }
|
22
|
+
end
|
23
|
+
|
24
|
+
trait :seven do
|
25
|
+
value { 6 }
|
26
|
+
end
|
27
|
+
|
28
|
+
trait :eight do
|
29
|
+
value { 7 }
|
30
|
+
end
|
31
|
+
|
32
|
+
trait :nine do
|
33
|
+
value { 8 }
|
34
|
+
end
|
35
|
+
|
36
|
+
trait :ten do
|
37
|
+
value { 9 }
|
38
|
+
end
|
39
|
+
|
40
|
+
initialize_with { new(value, suite) }
|
41
|
+
end
|
42
|
+
end
|