Skip to content

Commit b38cf00

Browse files
committed
add initial implementation
1 parent 6f9cb1b commit b38cf00

File tree

6 files changed

+389
-0
lines changed

6 files changed

+389
-0
lines changed

10_Blackjack/ruby/blackjack.rb

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

10_Blackjack/ruby/game.rb

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
play_dealer
26+
settle
27+
end
28+
end
29+
30+
private
31+
32+
def collect_bets_and_deal
33+
puts "BETS"
34+
35+
@players.each_entry do |player|
36+
print "# #{player.id} ? "
37+
bet = gets.to_i
38+
player.deal_initial_hand Model::Hand.new(bet, [@pack.draw, @pack.draw])
39+
end
40+
41+
@dealer_hand = Model::Hand.new(0, [@pack.draw, @pack.draw])
42+
print_players_and_dealer_hands
43+
end
44+
45+
def play_players
46+
@players.each_entry do |player|
47+
play_hand player, player.hand
48+
end
49+
end
50+
51+
def play_dealer
52+
puts "DEALER HAS A \t#{@dealer_hand.cards[1].label} CONCEALED FOR A TOTAL OF #{@dealer_hand.total}"
53+
54+
while @dealer_hand.total(is_dealer: true) < 17
55+
card = @pack.draw
56+
@dealer_hand.hit card
57+
58+
puts "DRAWS #{card.label} \t---TOTAL = #{@dealer_hand.total}"
59+
end
60+
61+
if !@dealer_hand.is_busted?
62+
@dealer_hand.stand
63+
end
64+
end
65+
66+
def settle
67+
@players.each_entry do |player|
68+
player_balance_update = player.update_balance @dealer_hand
69+
@dealer_balance -= player_balance_update
70+
71+
puts "PLAYER #{player.id} #{player_balance_update < 0 ? "LOSES" : "WINS"} \t#{player_balance_update} \tTOTAL=#{player.balance}"
72+
end
73+
74+
puts "DEALER'S TOTAL = #{@dealer_balance}"
75+
end
76+
77+
78+
def print_players_and_dealer_hands
79+
puts "PLAYER\t#{@players.map(&:id).join("\t")}\tDEALER"
80+
# TODO: Check for split hands
81+
puts " \t#{@players.map {|p| p.hand.cards[0].label}.join("\t")}\t#{@dealer_hand.cards[0].label}"
82+
puts " \t#{@players.map {|p| p.hand.cards[1].label}.join("\t")}"
83+
end
84+
85+
def play_hand player, hand
86+
allowed_actions = ALLOWED_HAND_ACTIONS[(hand.is_split_hand || !hand.can_split?) ? "split" : "normal"]
87+
name = "PLAYER #{player.id}"
88+
did_hit = false
89+
90+
while hand.is_playing?
91+
print "#{name}? "
92+
93+
action = gets.strip
94+
95+
if !allowed_actions.include?(action)
96+
puts "Possible actions: #{allowed_actions.join(", ")}"
97+
next
98+
end
99+
100+
if action === "/"
101+
player.split
102+
103+
play_hand "#{name} - Hand 1", player.hand
104+
play_hand "#{name} - Hand 2", player.split_hand
105+
106+
return
107+
end
108+
109+
if action === "S"
110+
hand.stand
111+
end
112+
113+
if action === "D"
114+
card = @pack.draw
115+
hand.double_down card
116+
117+
puts "RECEIVED #{card.label}"
118+
end
119+
120+
if action === "H"
121+
did_hit = true
122+
allowed_actions = ALLOWED_HAND_ACTIONS["hit"]
123+
card = @pack.draw
124+
hand.hit card
125+
126+
puts "RECEIVED #{card.label}"
127+
end
128+
end
129+
130+
puts "TOTAL IS #{hand.total}"
131+
132+
if hand.is_busted?
133+
puts "... BUSTED"
134+
end
135+
end
136+
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

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 total(is_dealer: false)
33+
return @total unless @total.nil?
34+
35+
@total = @cards.reduce(0) {|sum, card| sum + card.value}
36+
37+
if @total > 21
38+
aces_count = @cards.count {|c| c == CardKind::ACE}
39+
while ((!is_dealer && @total > 21) || (is_dealer && @total < 16)) && aces_count > 0 do
40+
@total -= 10
41+
aces_count -= 1
42+
end
43+
end
44+
45+
@total
46+
end
47+
48+
## Hand actions
49+
50+
def can_split?
51+
not @is_split_hand and @cards.length == 2 && @cards[0].same_value?(cards[1])
52+
end
53+
54+
def split
55+
throw "can't split" unless can_split?
56+
[
57+
Hand.new(@bet, @cards[0..1], is_split_hand: true),
58+
Hand.new(@bet, @cards[1..1], is_split_hand: true)
59+
]
60+
end
61+
62+
def hit(card)
63+
throw "can't hit" unless is_playing?
64+
65+
@cards.push(card)
66+
@total = nil
67+
68+
check_busted
69+
end
70+
71+
def double_down(card)
72+
throw "can't double down" unless is_playing?
73+
74+
@bet *= 2
75+
hit card
76+
77+
@state = HAND_STATE_DOUBLED_DOWN
78+
end
79+
80+
def stand
81+
throw "can't stand" unless is_playing?
82+
83+
@state = HAND_STATE_STAND
84+
end
85+
86+
87+
private
88+
89+
def check_busted
90+
@state = HAND_STATE_BUSTED if total > 21
91+
end
92+
end
93+
end

10_Blackjack/ruby/model/pack.rb

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
@cards.pop
17+
end
18+
19+
private
20+
21+
def reshuffle
22+
puts "RESHUFFLING"
23+
@cards = 4.times.map {|_| CardKind::KINDS_SET}.flatten
24+
@cards.shuffle!
25+
end
26+
end
27+
end

10_Blackjack/ruby/model/player.rb

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require_relative "./hand.rb"
2+
3+
module Model
4+
class Player
5+
def initialize(id)
6+
@id = id
7+
@balance = 0
8+
9+
@hand = nil
10+
@split_hand = nil
11+
end
12+
13+
attr_reader :id, :balance, :hand, :split_hand
14+
15+
## Begining of hand dealing actions
16+
def deal_initial_hand(hand)
17+
@hand = hand
18+
@split_hand = nil
19+
end
20+
21+
def has_split_hand?
22+
!@split_hand.nil?
23+
end
24+
25+
def can_split?
26+
not has_split_hand? and @hand.can_split?
27+
end
28+
29+
def split
30+
throw "can't split" unless can_split?
31+
32+
@hand, @split_hand = @hand.split
33+
end
34+
35+
def bet_insurance(insurance_bet)
36+
end
37+
38+
## End of hand dealing actions
39+
40+
def update_balance(dealer_hand)
41+
balance_update = 0
42+
43+
balance_update += get_balance_update(@hand, dealer_hand)
44+
if has_split_hand? then
45+
balance_update += get_balance_update(@split_hand, dealer_hand)
46+
end
47+
48+
@balance += balance_update
49+
50+
balance_update
51+
end
52+
53+
54+
private
55+
56+
def get_balance_update(hand, dealer_hand)
57+
if hand.is_busted?
58+
return -hand.bet
59+
elsif dealer_hand.is_busted?
60+
return hand.bet
61+
elsif dealer_hand.total == hand.total
62+
return 0
63+
else
64+
return (dealer_hand.total < hand.total ? 1 : -1) * hand.bet
65+
end
66+
end
67+
end
68+
end

0 commit comments

Comments
 (0)