From: Ruby Quiz Date: 2008-01-18T07:17:51+09:00 Subject: [SUMMARY] Counting Cards (#152) 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[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 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: <%= javascript_include_tag :defaults %> Practice Card Counting <% form_tag :action => 'practice' do %> Number of Decks in Shoe: <%= text_field_tag :decks, '1' %>
Deal between <%= text_field_tag :min, '1', :size => 1%> and <%= text_field_tag :max, '3', :size => 1 %> cards per hand.
Deal cards every <%= text_field_tag :delay, '5', :size => 1%> seconds.
<%= submit_tag("Start") %> <% end %> 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\n" end text += "" 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: <%= javascript_include_tag :defaults %> Practice Card Counting Pause
<%= periodically_call_remote( :condition => "paused == false", :update => "cards", :frequency => session[:delay], :url => { :action => "deal" }) %> 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...