add initial implementation

This commit is contained in:
Alaa Sarhan
2022-03-29 00:31:45 +02:00
parent 6f9cb1bac2
commit b38cf0018a
6 changed files with 389 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
require_relative "./game.rb"
# TODOS
# 1. check if we should implement insurances
# 2. test splitting
def intro
puts "Welcome to Blackjack"
end
def ask_for_players_count
puts "How many of you want to join the table?"
return gets.to_i
end
begin
intro
players_count = ask_for_players_count
Game.new(players_count).start
rescue SystemExit, Interrupt
exit
rescue => exception
p exception
end

136
10_Blackjack/ruby/game.rb Normal file
View File

@@ -0,0 +1,136 @@
require_relative "./model/hand.rb"
require_relative "./model/player.rb"
require_relative "./model/card_kind.rb"
require_relative "./model/pack.rb"
class Game
ALLOWED_HAND_ACTIONS = {
"hit" => ["H", "S"],
"split" => ["H", "S", "D"],
"normal" => ["H", "S", "/", "D"]
}
def initialize(players_count)
@pack = Model::Pack.new
@dealer_balance = 0
@dealer_hand = nil
@players = 1.upto(players_count).map { |id| Model::Player.new(id) }
end
def start
loop do
collect_bets_and_deal
play_players
play_dealer
settle
end
end
private
def collect_bets_and_deal
puts "BETS"
@players.each_entry do |player|
print "# #{player.id} ? "
bet = gets.to_i
player.deal_initial_hand Model::Hand.new(bet, [@pack.draw, @pack.draw])
end
@dealer_hand = Model::Hand.new(0, [@pack.draw, @pack.draw])
print_players_and_dealer_hands
end
def play_players
@players.each_entry do |player|
play_hand player, player.hand
end
end
def play_dealer
puts "DEALER HAS A \t#{@dealer_hand.cards[1].label} CONCEALED FOR A TOTAL OF #{@dealer_hand.total}"
while @dealer_hand.total(is_dealer: true) < 17
card = @pack.draw
@dealer_hand.hit card
puts "DRAWS #{card.label} \t---TOTAL = #{@dealer_hand.total}"
end
if !@dealer_hand.is_busted?
@dealer_hand.stand
end
end
def settle
@players.each_entry do |player|
player_balance_update = player.update_balance @dealer_hand
@dealer_balance -= player_balance_update
puts "PLAYER #{player.id} #{player_balance_update < 0 ? "LOSES" : "WINS"} \t#{player_balance_update} \tTOTAL=#{player.balance}"
end
puts "DEALER'S TOTAL = #{@dealer_balance}"
end
def print_players_and_dealer_hands
puts "PLAYER\t#{@players.map(&:id).join("\t")}\tDEALER"
# TODO: Check for split hands
puts " \t#{@players.map {|p| p.hand.cards[0].label}.join("\t")}\t#{@dealer_hand.cards[0].label}"
puts " \t#{@players.map {|p| p.hand.cards[1].label}.join("\t")}"
end
def play_hand player, hand
allowed_actions = ALLOWED_HAND_ACTIONS[(hand.is_split_hand || !hand.can_split?) ? "split" : "normal"]
name = "PLAYER #{player.id}"
did_hit = false
while hand.is_playing?
print "#{name}? "
action = gets.strip
if !allowed_actions.include?(action)
puts "Possible actions: #{allowed_actions.join(", ")}"
next
end
if action === "/"
player.split
play_hand "#{name} - Hand 1", player.hand
play_hand "#{name} - Hand 2", player.split_hand
return
end
if action === "S"
hand.stand
end
if action === "D"
card = @pack.draw
hand.double_down card
puts "RECEIVED #{card.label}"
end
if action === "H"
did_hit = true
allowed_actions = ALLOWED_HAND_ACTIONS["hit"]
card = @pack.draw
hand.hit card
puts "RECEIVED #{card.label}"
end
end
puts "TOTAL IS #{hand.total}"
if hand.is_busted?
puts "... BUSTED"
end
end
end

View File

@@ -0,0 +1,41 @@
module Model
class CardKind
def initialize(label, value)
@label = label
@value = value
end
private_class_method :new
TWO = self.new("2", 2)
THREE = self.new("3", 3)
FOUR = self.new("4", 4)
FIVE = self.new("5", 5)
SIX = self.new("6", 6)
SEVEN = self.new("7", 7)
EIGHT = self.new("8", 8)
NINE = self.new("9", 9)
TEN = self.new("10", 10)
JACK = self.new("J", 10)
QUEEN = self.new("Q", 10)
KING = self.new("K", 10)
ACE = self.new("A", 11)
KINDS_SET = [
TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN,
JACK, QUEEN, KING, ACE
]
def same_value?(other_card)
value == other_card.value
end
def +(other)
throw "other doesn't respond to +" unless other.responds_to? :+
other.+(@value)
end
attr_reader :label, :value
end
end

View File

@@ -0,0 +1,93 @@
require_relative "./card_kind.rb"
module Model
class Hand
HAND_STATE_PLAYING = :hand_playing
HAND_STATE_BUSTED = :hand_busted
HAND_STATE_STAND = :hand_stand
HAND_STATE_DOUBLED_DOWN = :hand_doubled_down
def initialize(bet, cards, is_split_hand: false)
@state = HAND_STATE_PLAYING
@bet = bet
@cards = cards
@total = nil
@is_split_hand = is_split_hand
end
attr_reader :bet, :cards, :is_split_hand
def is_playing?
@state == HAND_STATE_PLAYING
end
def is_busted?
@state == HAND_STATE_BUSTED
end
def is_standing?
@state == HAND_STATE_STAND
end
def total(is_dealer: false)
return @total unless @total.nil?
@total = @cards.reduce(0) {|sum, card| sum + card.value}
if @total > 21
aces_count = @cards.count {|c| c == CardKind::ACE}
while ((!is_dealer && @total > 21) || (is_dealer && @total < 16)) && aces_count > 0 do
@total -= 10
aces_count -= 1
end
end
@total
end
## Hand actions
def can_split?
not @is_split_hand and @cards.length == 2 && @cards[0].same_value?(cards[1])
end
def split
throw "can't split" unless can_split?
[
Hand.new(@bet, @cards[0..1], is_split_hand: true),
Hand.new(@bet, @cards[1..1], is_split_hand: true)
]
end
def hit(card)
throw "can't hit" unless is_playing?
@cards.push(card)
@total = nil
check_busted
end
def double_down(card)
throw "can't double down" unless is_playing?
@bet *= 2
hit card
@state = HAND_STATE_DOUBLED_DOWN
end
def stand
throw "can't stand" unless is_playing?
@state = HAND_STATE_STAND
end
private
def check_busted
@state = HAND_STATE_BUSTED if total > 21
end
end
end

View File

@@ -0,0 +1,27 @@
require_relative "./card_kind.rb"
module Model
class Pack
def initialize
@cards = []
reshuffle
end
def reshuffle_if_necessary
return if @cards.count > 2
reshuffle
end
def draw
@cards.pop
end
private
def reshuffle
puts "RESHUFFLING"
@cards = 4.times.map {|_| CardKind::KINDS_SET}.flatten
@cards.shuffle!
end
end
end

View File

@@ -0,0 +1,68 @@
require_relative "./hand.rb"
module Model
class Player
def initialize(id)
@id = id
@balance = 0
@hand = nil
@split_hand = nil
end
attr_reader :id, :balance, :hand, :split_hand
## Begining of hand dealing actions
def deal_initial_hand(hand)
@hand = hand
@split_hand = nil
end
def has_split_hand?
!@split_hand.nil?
end
def can_split?
not has_split_hand? and @hand.can_split?
end
def split
throw "can't split" unless can_split?
@hand, @split_hand = @hand.split
end
def bet_insurance(insurance_bet)
end
## End of hand dealing actions
def update_balance(dealer_hand)
balance_update = 0
balance_update += get_balance_update(@hand, dealer_hand)
if has_split_hand? then
balance_update += get_balance_update(@split_hand, dealer_hand)
end
@balance += balance_update
balance_update
end
private
def get_balance_update(hand, dealer_hand)
if hand.is_busted?
return -hand.bet
elsif dealer_hand.is_busted?
return hand.bet
elsif dealer_hand.total == hand.total
return 0
else
return (dealer_hand.total < hand.total ? 1 : -1) * hand.bet
end
end
end
end