Counting Cards (#152)

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz
    until
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Learning to count cards is much easier than Hollywood or the casinos
would have
us believe. Some systems only require you to track a single running
total in
your head.

One such system, called the Knock-out system of card counting, is extra
easy.
You start your count at 4 - (4 x number_of_decks). That gives us an
initial
running count of 0, -4, -20, or -28 for the common casino shoe sizes of
1, 2, 6,
or 8 decks. From there, you add one each time you see a 2, 3, 4, 5, 6
or 7 and
subtract one when you see a 10, jack, queen, king, or ace. The 8 and 9
cards do
not affect the count. Once you learn to track the running count, you
can make
strategy decisions and vary your bets based on the times when the count
is in
your favor.

That’s not a lot to remember, but it does take practice to get fast.
You really
need to get to where you can count a deck in 20 to 30 seconds if you are
going
to keep up with those fast moving casinos dealers.

This week’s Ruby Q. is to build a training program for helping you
learn to
count cards.

The program needs to show you one or more cards at a time, running
through a
Blackjack shoe. As it goes, the program should track the running count.
Have
it pause at random intervals, ask you the count, and notify you if you
are right
or wrong.

Both the time to go through the deck and the number of cards displayed
at a time
should be configurable. It’s important to practice with seeing multiple
cards
at once because you learn to cancel out pairs of high and low cards. It
might
even be nice to provide a mixed mode, which varies the number of cards
shown at
a time.

You can show cards as simple Strings, ASCII art, or full graphics as you
prefer.
You may wish to make cards fully disappear after their display time
though, to
make the conditions more like they would be in a casino.

On Jan 11, 2008 2:56 PM, Ruby Q. [email protected] wrote:

One such system, called the Knock-out system of card counting, is extra easy.
to keep up with those fast moving casinos dealers.
should be configurable. It’s important to practice with seeing multiple cards
at once because you learn to cancel out pairs of high and low cards. It might
even be nice to provide a mixed mode, which varies the number of cards shown at
a time.

You can show cards as simple Strings, ASCII art, or full graphics as you prefer.
You may wish to make cards fully disappear after their display time though, to
make the conditions more like they would be in a casino.

Nice quiz James but shall those who want to continue to play BJ in
casinos maybe refrain from posting answers ;).
I am saying this just to be the only to answer of course.
Robert

http://ruby-smalltalk.blogspot.com/


Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

On Jan 11, 2008, at 10:15 AM, Robert D. wrote:

Nice quiz James but shall those who want to continue to play BJ in
casinos maybe refrain from posting answers ;).
I am saying this just to be the only to answer of course.

If you’re asking me if counting cards is illegal, the answer is no, at
least in the United States. The casinos like to push that myth but
they will need to overturn a Supreme Court ruling to turn it into
reality.

However, when gambling at the casinos, you are on private property and
essentially subject to their whims. They can and definitely do:
alter the rules of the game to remove your advantage, force you to
stop varying your bets (the card counter’s main weapon), or eject you
from the game or the casino. The point is, it’s not illegal but you
still don’t want to be caught doing it.

The key is not to get noticed. Pit bosses are trained in many card
counter recognition tricks. They will watch for someone who doesn’t
make basic strategy errors, someone who varies their bets at seemingly
random times, someone who doesn’t drink or tip the dealer, etc. Some
of them are also trained to count cards themselves. Once identified,
they will take countermeasures, so it’s that identification process
you never want to get past.

Pit bosses tend to try relying on memorizing your most prominent
physical feature for possible future encounters.

The point of all of this is simple: while it’s conceivable that
participating in this quiz could get you identified as a card counter,
it’s unlikely a pit boss will recognize you when you sit down at the
table from your email address. Thus, I think we’re safe and should
have some fun.

If Ruby Q. becomes responsible for more people winning at the
Blackjack tables, that’s just an added bonus to me. :wink:

James Edward G. II

On Jan 11, 2008 5:48 PM, James G. [email protected] wrote:

random times, someone who doesn’t drink or tip the dealer, etc. Some
table from your email address. Thus, I think we’re safe and should
have some fun.

If Ruby Q. becomes responsible for more people winning at the
Blackjack tables, that’s just an added bonus to me. :wink:

Thx for that detailed description of what is going on - I kind of
understood it like this.

Robert

James Edward G. II


http://ruby-smalltalk.blogspot.com/


Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

On Fri, 11 Jan 2008 11:48:53 -0500, James G. wrote:

The point of all of this is simple: while it’s conceivable that
participating in this quiz could get you identified as a card counter,
it’s unlikely a pit boss will recognize you when you sit down at the
table from your email address. Thus, I think we’re safe and should have
some fun.

Only if you’re careful online and they can’t piece together your picture
with your email address from other sources (e.g. Facebook). If you’ve
ever read “Bringing down the House”, there’s a whole network between the
casinos to share pictures and information about card counters.

–Ken

On Jan 11, 2008, at 1:40 PM, Ken B. wrote:

Only if you’re careful online and they can’t piece together your
picture
with your email address from other sources (e.g. Facebook). If you’ve
ever read “Bringing down the House”, there’s a whole network between
the
casinos to share pictures and information about card counters.

I knew that they use to do a lot of this in the past. I wasn’t sure
how much it still goes on after Griffin went bankrupt. The courts did
rule they were invading privacy after all.

I guess I still feel it’s unlikely spies are watching the Ruby Q. in
the hopes of nabbing card counters using training software. Imagining
them scouring the Web for more details on these just seems to add to
the paranoia.

Of course, only participate in this quiz if you feel safe.

James Edward G. II

Well I feel save enough to answer ;).
The attached solution does not seem to work on Windows
(Timeout::timeout does not timeout) and as I have no Mac I cannot
tell.
On Linux it seems to work fine though, sorry for the revolutionary
user interface but I tried to separate the dealing mechanism from the
UI quite ok so if someone wants to write a plugin ;).
Ok here we go
file: human-interface.rb

require ‘timeout’

class << HumanInterface = Object.new
attr_reader :errors
def read_int_within_delay delay
Timeout::timeout( delay ){ $stdin.readline.chomp }
rescue Timeout::Error
nil
end

def show_cards cards, delay, count
@errors ||= 0
puts cards.join(",")
print "> "
$stdout.flush
read_value = read_int_within_delay delay
case read_value
when nil
@errors += 1
puts “Sorry timeout, current count is #{count}”
else
if read_value.to_i == count
puts “ok”
else
@errors += 1
puts “Sorry this is incorrect, current count is #{count}”
end
end
end
end

file:robert-dober-152.rb

require ‘human-interface’

class Array
def random_element
self[ rand( size ) ]
end
def shift_some n=1
(1…[n,size].min).map{ shift }
end
def zip_with zip_value
zip [ zip_value ] * size
end

end

class Shoe
ShoeParameters = %w{ decks delay cards }

HonorCards = %w{ 10 J Q K A }
MediumCards = %w{ 8 9 }
SmallCards = %w{ 2 3 4 5 6 7 }

CardValues = Hash[ *(
HonorCards.zip_with( -1 ) +
MediumCards.zip_with( 0 ) +
SmallCards.zip_with( 1 )
).flatten ]

FaceValues = SmallCards + MediumCards + HonorCards

private
def draw_cards
cards = @deck.shift_some @cards.random_element
@count += cards.inject(0){|s, c| s + CardValues[c[1…-1]]}
cards
end

def initialize params = {}
set_ivars_default

params.each do  | name, value |
  instance_variable_set "@#{name}", value
  raise RuntimeError, "No such parameter #{name}" unless
    ShoeParameters.include? name.to_s
end

set_computed_ivars

end

def set_computed_ivars
@count = 4 * ( 1 - @decks )
@deck = %w{S H D C}.inject([]){ |deck, suit|
deck + FaceValues.map{ |c| suit + c }
} * @decks
@deck = @deck.sort_by{ rand }
end

def set_ivars_default
@decks = 2
@delay = 3
@cards = 2
end

public
def training human_interface
loop do
return if @deck.empty?
cards = draw_cards
human_interface.show_cards cards, @delay, @count
end
end
end

def usage
puts <<-EOS
usage:
#{$0} []

Trains you in counting cards. The trainer uses the number of decks
you indicate
as first parameter, shows you the drawn cards for a delay in seconds
indicated
as the second parameter.
The number of cards drawn at each draw is either constant, as
indicated by the
third parameter or random in a range bound by the third and fourth
parameter.
EOS
exit -1
end

usage if ARGV.size < 3 || /^-h|^–help/ === ARGV.first
training_shoe = Shoe.new :decks => ARGV[0].to_i,
:delay => ARGV[1].to_i,
:cards => [*ARGV[2].to_i … (ARGV[3]||ARGV[2]).to_i]

puts “Starting training with #{ARGV.first} decks of cards:”
sleep 1
training_shoe.training HumanInterface

puts “Total errors made: #{HumanInterface.errors}”

This week’s puzzle had two challenges:

  1. Implement a simple card counting scheme.
  2. Present a simulated card deal to the user to help him develop the
    counting technique.

The real challenge is the second part. I ruled out the card names
(3d,As,Tc…) and the ascii art options which left me needing a way of
display proper graphic card images. I had a brief look at some gui
toolkits (shoes, tk, …) but in the end decided on a browser-based
approach. I made a simple rails app and used an ajax request to update
the training page. If anyone wants to try it, download
http://www.hennessynet.com/counter.tar.gz
and unpack it into a temporary directory. Then run ./script/server
and browse to http://localhost:3000/card_counter

The card counting algorithm is implemented first. The Counter class
contains both the shuffled shoe as well as the running count.

card_counter.rb:
CARDS = %w{A K Q J T 9 8 7 6 5 4 3 2}
SUITS = %w{c s h d}

class Counter
def initialize(decks)
@count = 4 - 4decks
@shoe = []
decks.times do
CARDS.each do |c|
SUITS.each do |s|
@shoe << c.to_s + s.to_s
end
end
end
size = 52
decks
size.times do |i|
j = rand(size)
@shoe[i],@shoe[j] = @shoe[j],@shoe[i]
end
end

def deal
card = @shoe.pop
@count += 1 if “234567”.include? card[0,1].to_s
@count -= 1 if “TJQKA”.include? card[0,1].to_s
card
end

def count
@count
end

def size
@shoe.size
end
end

Then I made a rails app with a controller called
card_counter_controller.rb and a web page (practice.html.erb) to ‘run’
the training. The interesting bit is the periodically_call_remote()
call which gets a new set of cards every n seconds and displays them
to the user. A pause button suspends the dealing and displays the
current count. I found a free set of card images at
http://www.jfitz.com/cards/

card_counter_controller.rb:
require ‘card_counter’

class CardCounterController < ApplicationController

def practice
session[:counter] = Counter.new params[:decks].to_i
session[:min] = params[:min].to_i
session[:max] = params[:max].to_i
session[:delay] = params[:delay].to_i
end

def deal
min = session[:min]
max = session[:max]
counter = session[:counter]
max = counter.size if counter.size<max
min = max if max < min
count = min + rand(max-min+1)
text = “”
text = “Shoe complete” if count == 0
count.times do
card = session[:counter].deal
text += “\n”
end
text += “

Count is
#{counter.count}


render :text => text
end

Convert card name (“6d”, “Qs”…) to image index where 1=Ac,2=As,

3=Ah,4=Ad,5=Kc and so on
def card_index(card)
c = CARDS.index card[0,1].to_s
s = SUITS.index card[1,1].to_s
c * 4 + s + 1
end
end

practice.html.erb:

<%= javascript_include_tag :defaults %> Practice Card Counting Pause

<%= periodically_call_remote(
:condition => “paused == false”,
:update => “cards”,
:frequency => session[:delay],
:url => { :action => “deal” }) %>

Enjoy,
Denis

On 13 Jan 2008, at 18:21, Robert D. wrote:

The attached solution does not seem to work on Windows
(Timeout::timeout does not timeout) and as I have no Mac I cannot
tell.

It does indeed work fine on a Mac.

/dh

Two basic ASCII front-ends are provided. If figlet is available and
the
script is run with the -f command line option, you get a slightly
more
appealing visualization of the cards. Otherwise only the card names
are
displayed.

Command-line options:
quiz152 [TIME=2] [CARDS=2] [DECKS=4] [MAXSTEP=10]
- pause between deals
- max cards displayed simultaneously
- number of decks
- after max steps, users are asked to make their guess

Thomas.

#!/usr/bin/env ruby

Author:: Thomas Link (micathom AT gmail com)

Created:: 2008-01-11.

module Quiz152; end

class Quiz152::Game
NAMES = [2, 3, 4, 5, 6, 7, 8, 9, 10, ‘J’, ‘Q’, ‘K’, ‘A’]
VALUES = [1] * 6 + [0, 0] + [-1] * 5
SUITS = [‘c’, ‘d’, ‘h’, ‘s’]

def initialize(ui, time=2, cardn=2, decks=4, maxstep=10)
    @ui      = ui.new(self)
    @time    = time
    @msgtime = 1
    @cardn   = cardn
    @maxstep = maxstep
    cards    = NAMES.zip(VALUES)
    @cards   = SUITS.inject([]) {|a, s| a += cards.map{|c| c.dup

<< s}} * decks
@decks = decks
@count = 4 - 4 * decks
end

def run
    @ui.message "Decks: #@decks",
        "Pause: #{@time}s",
        "Cards at a time: 1-#@cardn",
        "Ready? (Press ENTER)"
    @ui.input
    while deal(rand(@maxstep) + 1)
        break unless query_count
    end
    @ui.message "Bye!"
end

def deal(n)
    n.times do
        @ui.deal_new
        (1 + rand(@cardn)).times do |i|
            cname, cvalue, csuit =

@cards.delete_at(rand(@cards.size))
if cname
@ui.deal_card(i, cname, csuit)
@count += cvalue
else
@ui.message(‘This is the end.’)
return false
end
end
@ui.deal_show
sleep @time
end
return true
end

def query_count
    @ui.clear
    @ui.message 'Your guess:'
    count = @ui.input.chomp
    if ['q', 'x', 'bye', 'exit', 'quit'].include?(count)
        return false
    else
        case count.to_i
        when 0
            @ui.message "The current count is #@count."
        when @count
            @ui.message "Well."
        else
            @ui.message "It's always a pleasure playing with you.

(count: #@count)"
end
sleep @msgtime
return true
end
end

end

class Quiz152::TextUI
def initialize(game)
@game = game
clear
end

def message(*text)
    text.each {|t| puts t}
end

def input
    STDIN.gets
end

def clear
    100.times {puts "\n"}
end

def deal_card(nth, name, suit)
    @output << [name]
end

def deal_new
    @output = []
    clear
end

def deal_show
    @output.transpose.each do |lines|
        puts lines.join('  ')
    end
    puts
end

end

class Quiz152::Figlet < Quiz152::TextUI
def initialize(*args)
super
@t_card = <<‘CARD’


/
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
__________________/
CARD
@t_suit = {
‘c’ => <<‘SUIT’,
_
/ _
/ _/
_/^_/
/

SUIT
‘d’ => <<‘SUIT’,

/
/
\ /
/
SUIT
‘h’ => <<‘SUIT’,
_ _
/ /
\ /
\ /
/
SUIT
‘s’ => <<‘SUIT’,
__
/
| |
_/_/
/__
SUIT
}
end

def deal_card(nth, name, suit)
    card = @t_card.dup.split("\n")
    fill_card(card, @t_suit[suit], 3, 2)
    fill_card(card, `figlet -k "#{name}"`, 8, 8)
    @output << card
end

def fill_card(template, text, x0, y0)
    text.each_with_index do |l, i|
        template[i + y0][x0 .. (x0 + l.size - 2)] = l.chomp
    end
    template
end

end

if FILE == $0
ui = Quiz152::TextUI
loop do
case ARGV[0]
when ‘-h’, ‘–help’
puts “#$0 [TIME=2] [CARDS=2] [DECKS=4] [MAXSTEP=10]”
exit 1
when ‘-f’, ‘–figlet’
ui = Quiz152::Figlet
ARGV.shift
else
break
end
end

Quiz152::Game.new(ui, *ARGV.map{|e| e.to_i}).run

end

On Jan 13, 2008 11:34 PM, Denis H. [email protected] wrote:

On 13 Jan 2008, at 18:21, Robert D. wrote:

The attached solution does not seem to work on Windows
(Timeout::timeout does not timeout) and as I have no Mac I cannot
tell.

It does indeed work fine on a Mac.
Oh thanks, BTW great solution you have come up with.
R.