Created
July 25, 2010 21:17
-
-
Save joeyrobert/489897 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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