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 - 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
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 += "<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
practice.html.erb:
<!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>
Enjoy,
Denis
>