Skip to content

Commit 6c32fc1

Browse files
Merge pull request #688 from AlaaSarhan/main
Blackjack in Ruby
2 parents 08ddffe + 3130a3d commit 6c32fc1

File tree

6 files changed

+427
-0
lines changed

6 files changed

+427
-0
lines changed

10_Blackjack/ruby/blackjack.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require_relative "./game.rb"
2+
3+
def intro
4+
puts "Welcome to Blackjack"
5+
end
6+
7+
def ask_for_players_count
8+
puts "How many of you want to join the table?"
9+
return gets.to_i
10+
end
11+
12+
begin
13+
intro
14+
players_count = ask_for_players_count
15+
Game.new(players_count).start
16+
rescue SystemExit, Interrupt
17+
exit
18+
rescue => exception
19+
p exception
20+
end

10_Blackjack/ruby/game.rb

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
require_relative "./model/hand.rb"
2+
require_relative "./model/player.rb"
3+
require_relative "./model/card_kind.rb"
4+
require_relative "./model/pack.rb"
5+
6+
class Game
7+
8+
ALLOWED_HAND_ACTIONS = {
9+
"hit" => ["H", "S"],
10+
"split" => ["H", "S", "D"],
11+
"normal" => ["H", "S", "/", "D"]
12+
}
13+
14+
def initialize(players_count)
15+
@pack = Model::Pack.new
16+
@dealer_balance = 0
17+
@dealer_hand = nil
18+
@players = 1.upto(players_count).map { |id| Model::Player.new(id) }
19+
end
20+
21+
def start
22+
loop do
23+
collect_bets_and_deal
24+
play_players
25+
check_for_insurance_bets
26+
play_dealer
27+
settle
28+
end
29+
end
30+
31+
private
32+
33+
def collect_bets_and_deal
34+
puts "BETS"
35+
36+
@players.each_entry do |player|
37+
print "# #{player.id} ? "
38+
bet = gets.to_i
39+
player.deal_initial_hand Model::Hand.new(bet, [@pack.draw, @pack.draw])
40+
end
41+
42+
@dealer_hand = Model::Hand.new(0, [@pack.draw, @pack.draw])
43+
print_players_and_dealer_hands
44+
end
45+
46+
def play_players
47+
@players.each_entry do |player|
48+
play_hand player, player.hand
49+
end
50+
end
51+
52+
def check_for_insurance_bets
53+
return if @dealer_hand.cards[0].label != "A"
54+
55+
print "ANY INSURANCE? "
56+
return if gets.strip != "Y"
57+
58+
@players.each_entry do |player|
59+
print "PLAYER #{player.id} INSURANCE BET? "
60+
player.bet_insurance(gets.to_i)
61+
end
62+
end
63+
64+
def play_dealer
65+
puts "DEALER HAS A \t#{@dealer_hand.cards[1].label} CONCEALED FOR A TOTAL OF #{@dealer_hand.total}"
66+
67+
while @dealer_hand.total(is_dealer: true) < 17
68+
card = @pack.draw
69+
@dealer_hand.hit card
70+
71+
puts "DRAWS #{card.label} \t---TOTAL = #{@dealer_hand.total}"
72+
end
73+
74+
if !@dealer_hand.is_busted?
75+
@dealer_hand.stand
76+
end
77+
end
78+
79+
def settle
80+
@players.each_entry do |player|
81+
player_balance_update = player.update_balance @dealer_hand
82+
@dealer_balance -= player_balance_update
83+
84+
puts "PLAYER #{player.id} #{player_balance_update < 0 ? "LOSES" : "WINS"} \t#{player_balance_update} \tTOTAL=#{player.balance}"
85+
end
86+
87+
puts "DEALER'S TOTAL = #{@dealer_balance}"
88+
end
89+
90+
91+
def print_players_and_dealer_hands
92+
puts "PLAYER\t#{@players.map(&:id).join("\t")}\tDEALER"
93+
puts " \t#{@players.map {|p| p.hand.cards[0].label}.join("\t")}\t#{@dealer_hand.cards[0].label}"
94+
puts " \t#{@players.map {|p| p.hand.cards[1].label}.join("\t")}"
95+
end
96+
97+
def play_hand player, hand
98+
allowed_actions = ALLOWED_HAND_ACTIONS[(hand.is_split_hand || !hand.can_split?) ? "split" : "normal"]
99+
name = "PLAYER #{player.id}"
100+
if hand.is_split_hand
101+
name += " - HAND #{hand === player.hand ? 1 : 2}"
102+
end
103+
104+
did_hit = false
105+
106+
while hand.is_playing?
107+
print "#{name}? "
108+
109+
action = gets.strip
110+
111+
if !allowed_actions.include?(action)
112+
puts "Possible actions: #{allowed_actions.join(", ")}"
113+
next
114+
end
115+
116+
if action === "/"
117+
player.split
118+
119+
play_hand player, player.hand
120+
play_hand player, player.split_hand
121+
122+
return
123+
end
124+
125+
if action === "S"
126+
hand.stand
127+
end
128+
129+
if action === "D"
130+
card = @pack.draw
131+
hand.double_down card
132+
133+
puts "RECEIVED #{card.label}"
134+
end
135+
136+
if action === "H"
137+
did_hit = true
138+
allowed_actions = ALLOWED_HAND_ACTIONS["hit"]
139+
card = @pack.draw
140+
hand.hit card
141+
142+
puts "RECEIVED #{card.label}"
143+
end
144+
end
145+
146+
puts "TOTAL IS #{hand.total}"
147+
148+
if hand.is_busted?
149+
puts "... BUSTED"
150+
end
151+
end
152+
end

10_Blackjack/ruby/model/card_kind.rb

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module Model
2+
class CardKind
3+
def initialize(label, value)
4+
@label = label
5+
@value = value
6+
end
7+
8+
private_class_method :new
9+
10+
TWO = self.new("2", 2)
11+
THREE = self.new("3", 3)
12+
FOUR = self.new("4", 4)
13+
FIVE = self.new("5", 5)
14+
SIX = self.new("6", 6)
15+
SEVEN = self.new("7", 7)
16+
EIGHT = self.new("8", 8)
17+
NINE = self.new("9", 9)
18+
TEN = self.new("10", 10)
19+
JACK = self.new("J", 10)
20+
QUEEN = self.new("Q", 10)
21+
KING = self.new("K", 10)
22+
ACE = self.new("A", 11)
23+
24+
KINDS_SET = [
25+
TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN,
26+
JACK, QUEEN, KING, ACE
27+
]
28+
29+
def same_value?(other_card)
30+
value == other_card.value
31+
end
32+
33+
def +(other)
34+
throw "other doesn't respond to +" unless other.responds_to? :+
35+
36+
other.+(@value)
37+
end
38+
39+
attr_reader :label, :value
40+
end
41+
end

10_Blackjack/ruby/model/hand.rb

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
require_relative "./card_kind.rb"
2+
3+
module Model
4+
class Hand
5+
HAND_STATE_PLAYING = :hand_playing
6+
HAND_STATE_BUSTED = :hand_busted
7+
HAND_STATE_STAND = :hand_stand
8+
HAND_STATE_DOUBLED_DOWN = :hand_doubled_down
9+
10+
def initialize(bet, cards, is_split_hand: false)
11+
@state = HAND_STATE_PLAYING
12+
@bet = bet
13+
@cards = cards
14+
@total = nil
15+
@is_split_hand = is_split_hand
16+
end
17+
18+
attr_reader :bet, :cards, :is_split_hand
19+
20+
def is_playing?
21+
@state == HAND_STATE_PLAYING
22+
end
23+
24+
def is_busted?
25+
@state == HAND_STATE_BUSTED
26+
end
27+
28+
def is_standing?
29+
@state == HAND_STATE_STAND
30+
end
31+
32+
def is_blackjack?
33+
total == 21 && @cards.length == 2
34+
end
35+
36+
def total(is_dealer: false)
37+
return @total unless @total.nil?
38+
39+
@total = @cards.reduce(0) {|sum, card| sum + card.value}
40+
41+
if @total > 21
42+
aces_count = @cards.count {|c| c == CardKind::ACE}
43+
while ((!is_dealer && @total > 21) || (is_dealer && @total < 16)) && aces_count > 0 do
44+
@total -= 10
45+
aces_count -= 1
46+
end
47+
end
48+
49+
@total
50+
end
51+
52+
## Hand actions
53+
54+
def can_split?
55+
not @is_split_hand and @cards.length == 2 && @cards[0].same_value?(cards[1])
56+
end
57+
58+
def split
59+
throw "can't split" unless can_split?
60+
[
61+
Hand.new(@bet, @cards[0...1], is_split_hand: true),
62+
Hand.new(@bet, @cards[1..1], is_split_hand: true)
63+
]
64+
end
65+
66+
def hit(card)
67+
throw "can't hit" unless is_playing?
68+
69+
@cards.push(card)
70+
@total = nil
71+
72+
check_busted
73+
end
74+
75+
def double_down(card)
76+
throw "can't double down" unless is_playing?
77+
78+
@bet *= 2
79+
hit card
80+
81+
@state = HAND_STATE_DOUBLED_DOWN
82+
end
83+
84+
def stand
85+
throw "can't stand" unless is_playing?
86+
87+
@state = HAND_STATE_STAND
88+
end
89+
90+
91+
private
92+
93+
def check_busted
94+
@state = HAND_STATE_BUSTED if total > 21
95+
end
96+
end
97+
end

10_Blackjack/ruby/model/pack.rb

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require_relative "./card_kind.rb"
2+
3+
module Model
4+
class Pack
5+
def initialize
6+
@cards = []
7+
reshuffle
8+
end
9+
10+
def reshuffle_if_necessary
11+
return if @cards.count > 2
12+
reshuffle
13+
end
14+
15+
def draw
16+
reshuffle_if_necessary
17+
@cards.pop
18+
end
19+
20+
private
21+
22+
def reshuffle
23+
puts "RESHUFFLING"
24+
@cards = 4.times.map {|_| CardKind::KINDS_SET}.flatten
25+
@cards.shuffle!
26+
end
27+
end
28+
end

0 commit comments

Comments
 (0)