Skip to content

Instantly share code, notes, and snippets.

@joeyrobert
Created July 25, 2010 21:17
Show Gist options
  • Save joeyrobert/489897 to your computer and use it in GitHub Desktop.
Save joeyrobert/489897 to your computer and use it in GitHub Desktop.
# Blackjack
# Author: Joey Robert <joey@joeyrobert.org>
module Blackjack
class Player
attr_accessor :name, :money, :cards, :bets, :pushed
def initialize(name)
@name = name
@money = 1000
@cards = [] # usually only 0 is used. higher is used when split
@bets = []
@pushed = 0
end
# Two special cases: JQK = 10, A = 1 || 11
def hand_value(deck)
aces = 0
value = cards[deck].inject(0) do |memo, card|
if (value = card[:value].to_i) != 0
memo + value
elsif card[:value] == 'A'
aces += 1
memo + 1
else # J, Q, K
memo + 10
end
end
# Each ace will add at least one. Add remaining
# part to find value closest to 21
aces.times { value += 10 if value + 10 <= 21 }
value
end
def card_display(deck)
buffer = ('+----+ ' * cards[deck].length) + "\n"
cards[deck].each { |card| buffer += "|#{'%-3s' % card[:value]}#{card[:suit]}| " }
buffer += "\n"
buffer += (('| | ' * cards[deck].length) + "\n") * 2
buffer += ('+----+ ' * cards[deck].length) + "\n\n"
buffer
end
end
class Dealer < Player
def initialize(name)
super
@pushed = @money = @bets = nil
end
def hidden_display
# show only first card
buffer = ('+----+ ' * 2) + "\n"
buffer += "|#{'%-3s' % cards[0][0][:value]}#{cards[0][0][:suit]}| |FACE|\n"
buffer += "| | |DOWN|\n"
buffer += '| | ' * 2 + "\n"
buffer += '+----+ ' * 2 + "\n\n"
buffer
end
end
class Deck
SUITS = %w(D H C S) # diamonds, hearts, clubs, spades
# SUITS = %w(♠ ♥ ♦ ♣) might look better if you have a unicode
# terminal, like Gnome terminal
CARDS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
def initialize
srand(Time.now.to_i)
reset
shuffle
end
def reset
@cards = []
SUITS.each do |suit|
CARDS.each do |card|
@cards << {:suit => suit, :value => card}
end
end
end
def shuffle
@cards = @cards.sort_by { rand }
end
def pop
@cards.pop
end
end
class Game
DOLLARS = '$%.2f'
def initialize(players)
@players = players
@dealer = Dealer.new('Dealer')
end
def run
until @players.empty?
@deck = Deck.new
get_bets
deal
take_turns
results
end
end
private
def deal
puts 'Dealing cards'
puts '-------------'
@players.each do |player|
player.cards = []
player.cards[0] = [@deck.pop, @deck.pop]
end
@dealer.cards[0] = [@deck.pop, @deck.pop]
puts 'Dealer\'s hand'
puts @dealer.hidden_display
end
def get_bets
puts 'Taking bets'
puts '-----------'
@players.reject! { |player| player.money <= 0 && player.pushed <= 0 }
puts "Players still in the game: #{@players.map {|player| "#{player.name} (#{DOLLARS % player.money})"}.join(', ')}\n\n"
@players.each do |player|
player.bets = []
puts "#{player.name}, you have #{DOLLARS % player.money}."
puts "#{DOLLARS % player.pushed} has been pushed. Your next bet will be in addition to this amount." if player.pushed > 0
begin
puts "How much do you want to bet? Your bet must be a multiple of $1, ranging from $1 to #{DOLLARS % player.money}."
player.bets[0] = gets.chomp.to_i
puts 'Invalid bet.' if failing = (player.bets[0] <= 0 || player.bets[0] > player.money)
end while failing
player.money -= player.bets[0]
player.bets[0] += player.pushed
player.pushed = 0
puts "#{player.name} bets #{DOLLARS % player.bets[0]}\n\n"
end
end
def turn_display(player, deck)
puts "Hand Value: #{player.hand_value(deck)}"
puts "Total bet: #{DOLLARS % player.bets[deck]}" if player.bets
puts player.card_display(deck)
puts "#{player.name} busted.\n\n" if player.hand_value(deck) > 21
puts "#{player.name} has 21!\n\n" if player.hand_value(deck) == 21
end
def turn(player, deck)
turn_display(player, deck)
opts = ['S']
if player.hand_value(deck) < 21
opts << 'H'
opts << 'D' if player.money >= player.bets[deck] # double down allowed on any two cards, if you have enough money
opts << 'T' if player.cards[deck].length == 2 && player.cards[deck][0][:value] == player.cards[deck][1][:value] # splitting on identical cards
elsif player.hand_value(deck) >= 21 # turn over
return
end
puts opts_text(opts)
choice = gets.chomp[0,1].upcase
until opts.include?(choice)
puts "That is an invalid choice. #{opts_text(opts)}"
choice = gets.chomp[0,1].upcase
end
puts ''
case choice
when 'H'
player.cards[deck] << @deck.pop
turn(player, deck)
when 'D'
player.cards[deck] << @deck.pop
player.money -= player.bets[deck]
player.bets[deck] *= 2
turn_display(player, deck)
when 'T' # should allow arbitrary splitting
player.cards << [player.cards[deck].delete_at(0)]
player.bets << player.bets[deck] / 2.0
player.bets[deck] /= 2.0
turn(player, deck)
when 'S'
return
end
end
def take_turns
@players.each do |player|
puts "#{player.name}'s Turn"
player.cards.each_index do |deck|
turn(player, deck)
end
end
end
OPTIONS = {'D' => 'Double down', 'S' => 'Stand', 'H' => 'Hit', 'T' => 'Split'}
def opts_text(opts)
opts.map { |v| OPTIONS[v] + " (#{v})" }.join(', ') + '?'
end
def results
puts "Dealer's Turn"
puts '-------------'
turn_display(@dealer, 0)
while(@dealer.hand_value(0) < 17)
@dealer.cards[0] << @deck.pop
turn_display(@dealer, 0)
end
number_to_beat = @dealer.hand_value(0)
@players.each do |player|
player.cards.each_with_index do |cards, i|
next if cards.nil?
hand_value = player.hand_value(i)
if (number_to_beat > 21 || hand_value > number_to_beat) && hand_value <= 21
print "#{player.name} has won #{DOLLARS % player.bets[i]}"
player.money += 2 * player.bets[i]
elsif hand_value == number_to_beat && hand_value <= 21
print "#{player.name} has pushed #{DOLLARS % player.bets[i]}"
player.pushed += player.bets[i]
else
print "#{player.name} has lost #{DOLLARS % player.bets[i]}"
end
puts " (#{player.name}'s #{hand_value} vs. Dealer's #{number_to_beat})"
end
end
puts ''
end
end
end
# I/O
puts 'Blackjack, by Joey Robert'
puts "-------------------------\n\n"
puts 'How many players are playing?'
number_of_players = gets.chomp.to_i
puts ''
if number_of_players == 0
puts 'Not enough players. Come back some other time.'
exit(0)
end
players = Array.new(number_of_players) do |i|
puts "What's Player #{i + 1}'s name?"
name = gets.chomp
puts ''
Blackjack::Player.new(name)
end
Blackjack::Game.new(players).run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment