[SUMMARY] Counting Cards (#152)

Discussion in 'Ruby' started by Ruby Quiz, Jan 17, 2008.

  1. Ruby Quiz

    Ruby Quiz Guest

    Denis Hennessy put it very well when explained that there are two challenges to
    this quiz. The first was to implement a card counter. That's the easy bit.

    Here's the library Denis submitted for counting cards:

    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 - 4*decks
    @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,@shoe[j] = @shoe[j],@shoe
    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

    This code is very easy to follow. You construct a Counter from a number of
    decks and it will determine the starting count, load a shoe full of cards, and
    shuffle those cards (the code is just a longhand form of @shoe.sort_by { rand
    }).

    The deal() method is the only other one that does any work. It pulls cards from
    the shoe, but before returning them it makes sure to update the count. Our
    counting system is easy, so it breaks down into the two simple conditional
    checks we see here.

    That's literally all it takes to setup some cards, deal, and track a count.

    The other challenge is how are we going to allow the user to interact with this
    code. I left the quiz wide open on this front, but the truth is that you will
    probably desire a GUI interface if you really want to practice. Recognizing the
    card faces will be import when you are actually sitting at a Blackjack table.

    Denis decided to use Rails as a GUI framework. He located some free card images
    and built a tiny web application to show them. All of the action takes place in
    a single controller and there are only two views. (The library we've already
    examined is the only model needed.)

    When you first visit the application, it will display a simple form:

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <%= javascript_include_tag :defaults %>
    <title>Practice Card Counting</title>
    </head>
    <body>
    <% form_tag :action => 'practice' do %>
    Number of Decks in Shoe: <%= text_field_tag :decks, '1' %><br/>
    Deal between
    <%= text_field_tag :min, '1', :size => 1%> and
    <%= text_field_tag :max, '3', :size => 1 %>
    cards per hand.<br/>
    Deal cards every
    <%= text_field_tag :delay, '5', :size => 1%> seconds.<br/>
    <%= submit_tag("Start") %>
    <% end %>
    </body>
    </html>

    As you can see, this just collects a few parameters for the application. You
    can set a deck count, a range of cards that will be dealt at one time, and a
    delay between new deals.

    Submitting this form will kick us into the controller, where the majority of the
    work is done:

    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 += "<img src='/images/#{card_index(card)}.png' " +
    "width='72' height='96'/>\n"
    end
    text += "<p id='count' style='visibility: hidden'>" +
    "Count is #{counter.count}</p>"
    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

    The form we just saw dumps us into the practice() method which just stores all
    of the parameters in your session. Note that the full Counter object
    (require()d at the top of this file) is constructed at this point and also
    placed in your session.

    The deal() method is the only other action and it's called to replace some page
    elements via Ajax. It reads your session parameters back, selects a random
    number of cards to display based on your range and what is left in the shoe, and
    renders a chunk of HTML text with the images for those cards as well as a hidden
    paragraph with the current count in it.

    The card_index() method is just a helper that constructs image names based on
    the face and suit.

    Just after practice() runs, Rails will render the only other view for this
    application:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <%= javascript_include_tag :defaults %>
    <title>Practice Card Counting</title>
    <script type="text/javascript">
    //<![CDATA[
    paused = false;
    function pause() {
    paused = !paused;
    if (paused) {
    $('count').setStyle('visibility: visible');
    $('pause').update('Continue')
    } else {
    $('pause').update('Pause')
    }
    }
    //]]>
    </script>
    </head>
    <body>
    <a id='pause' href="#" onClick="pause()">Pause</a>
    <div id="cards"></div>

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

    If you glance down at the body, you will see that there are really only two
    elements in this page: a link and an initially empty div.

    The link controls a Javascript toggle function. When you click "Pause" the
    function shows the count, switches the link name to "Continue," and stops the
    periodic Ajax calls. Clicking Continue changes the link name back to Pause and
    restarts the periodic calls. The next Ajax action will render a (re-)hidden
    count.

    When you put all of these pieces together, you get an effective trainer for card
    counting with pictures. Do have a look at the other solutions though, for
    examples of how to handle the event loop in the console.

    My thanks to all who were brave enough to risk having themselves labeled a card
    counter. With any luck, we will train a future generation of super counters.

    Tomorrow we will tackle a classic computer science optimization problem...
    Ruby Quiz, Jan 17, 2008
    #1
    1. Advertising

  2. On 17 Jan 2008, at 22:17, Ruby Quiz wrote:

    > the code is just a longhand form of @shoe.sort_by { rand }).


    That's sweet. Time to go re-read Enumerable...

    /dh
    Denis Hennessy, Jan 17, 2008
    #2
    1. Advertising

  3. Ruby Quiz

    Gareth Adams Guest

    Ruby Quiz <james <at> grayproductions.net> writes:

    > size.times do |i|
    > j = rand(size)
    > @shoe,@shoe[j] = @shoe[j],@shoe
    > end
    > (the code is just a longhand form of @shoe.sort_by { rand }).


    Easy to think that, but this is the exact same definition of a "naive algorithm"
    demonstrated recently and very well summed up by Jeff Atwood:

    http://www.codinghorror.com/blog/archives/001015.html

    Gareth
    Gareth Adams, Jan 18, 2008
    #3
  4. On 18 Jan 2008, at 09:19, Gareth Adams wrote:

    > Ruby Quiz <james <at> grayproductions.net> writes:
    >
    >> size.times do |i|
    >> j = rand(size)
    >> @shoe,@shoe[j] = @shoe[j],@shoe
    >> end
    >> (the code is just a longhand form of @shoe.sort_by { rand }).

    >
    > Easy to think that, but this is the exact same definition of a
    > "naive algorithm"
    > demonstrated recently and very well summed up by Jeff Atwood:
    >
    > http://www.codinghorror.com/blog/archives/001015.html
    >
    > Gareth


    That's very interesting. The core of the bug is the the original
    algorithm generates 52*52 possible outcomes, instead of 52! outcomes.
    The non-randomness occurs because 52*52 is not evenly dividable by 52!.

    Also interesting that you can learn new things by reading C# code...

    /dh
    >
    Denis Hennessy, Jan 18, 2008
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kapil Sachdeva

    Re: credit cards

    Kapil Sachdeva, Oct 14, 2003, in forum: ASP .Net
    Replies:
    1
    Views:
    358
    Vincent V
    Oct 16, 2003
  2. Ruby Quiz
    Replies:
    2
    Views:
    93
    James Edward Gray II
    Feb 2, 2007
  3. Ruby Quiz
    Replies:
    17
    Views:
    183
    Robert Dober
    May 6, 2007
  4. Ruby Quiz

    [QUIZ] Counting Cards (#152)

    Ruby Quiz, Jan 11, 2008, in forum: Ruby
    Replies:
    9
    Views:
    143
    Robert Dober
    Jan 14, 2008
  5. edwardfredriks

    counting up instead of counting down

    edwardfredriks, Sep 6, 2005, in forum: Javascript
    Replies:
    6
    Views:
    185
    Dr John Stockton
    Sep 7, 2005
Loading...

Share This Page