www.pudn.com > NumericalComputingwithMatlabCode.zip > blackjack.m, change:2003-10-27,size:15008b

```function blackjack(N)
% BLACKJACK.  Use random numbers in Monte Carlo simulation.
% Play the game of Blackjack, either one hand, or thousands of hands,
% at a time, and display payoff statistics.
%
% In Blackjack, face cards count 10 points, aces count one or 11 points,
% all other cards count their face value.  The objective is to reach,
% but not exceed, 21 points.  If you go over 21, or "bust", before the
% dealer, you lose your bet on that hand.  If you have 21 on the first
% two cards, and the dealer does not, this is "blackjack" and is worth
% 1.5 times the bet.  If your first two cards are a pair, you may "split"
% the pair by doubling the bet and use the two cards to start two
% independent hands.  You may "double down" after seeing the first two
% cards by doubling the bet and receiving just one more card.
% "Hit" and "draw" mean take another card.  "Stand" means stop drawing.
% "Push" means the two hands have the same total.
%
% The first mathematical analysis of Blackjack was published in 1956
% by Baldwin, Cantey, Maisel and McDermott. Their basic strategy, which
% is also described in many more recent books, makes Blackjack very
% close to a fair game.  With basic strategy, the expected win or loss
% per hand is less than one percent of the bet.  The key idea is to
% avoid going bust before the dealer.  The dealer must play a fixed
% strategy, hitting on 16 or less and standing on 17 or more.  Since
% almost one-third of the cards are worth 10 points, you can compare
% your hand with the dealer's under the assumption that the dealer's
% hole card is a 10.  If the dealer's up card is a six or less, she
% must draw.  Consequently, the strategy has you stand on any total over
% 11 when the dealder is showing a six or less.  Split aces and split 8's.
% Do not split anything else.  Double down with 11, or with 10 if the
% dealer is showing a six or less.  The complete basic strategy is
% defined by three arrays, HARD, SOFT and SPLIT, in the code.
%
% A more elaborate strategy, called "card counting", can provide a
% definite mathematical advantage.  Card counting players keep track
% of the cards that have appeared in previous hands, and use that
% information to alter both the bet and the play as the deck becomes
% depleated.  Our simulation does not involve card counting.
%
% BLACKJACK(N) plays N hands with an initial bet of \$10 for each hand.
% "Play" mode, N = 1, indicates the basic stragegy with color, but allows
% you to make other choices.  "Simulate" mode, N > 1, plays N hands
% using basic strategy and displays the evolving payoff results.
% One graph shows the total return accumulated over the duration of the
% simulation.  Another graph shows the observed probabilities of the
% ten possible payoffs for each hand.  These payoffs include zero for a
% push, win \$15 for a blackjack, win or lose \$10 on a hand that has not been
% split or doubled, win or lose \$20 on a hand that has been split or doubled,
% and win or lose \$30 or \$40 on hands that are after doubled after a split.
% The \$30 and \$40 payoffs occur rarely (and may not be allowed at some
% casinos), but are important in determining the expected return from the
% basic strategy.  The second graph also displays with 0.xxxx +/- 0.xxxx
% the expected fraction of the bet that is won or lost each hand, together
% with its confidence interval.  Note that the expected return is usually
% negative, but within the confidence interval.  The total return in any
% session with less than a few million hands is determined more by the luck
% of the cards than by the expected return.
%

clf
shg
'double','on','userdata',[])
initialize_arrays
rand('state',sum(100*clock))
if nargin == 0
N = 10000;
kase = 1;
else
if ischar(N)
N = str2double(N);
end
bj(N)
kase = 2;
end
while kase > 0
kase = bjbuttonclick(kase);
switch kase
case 0, break    % Close
case 1, bj(1)    % Play one hand
case 2, bj(N)    % Simulate
end
end
close(gcf)

% ------------------------

function initialize_arrays
%Create global strategy arrays

global SOFT HARD PAIR

% 0 = stand or keep pair
% 1 = hit or split pair
% 2 = double down

n = NaN; % Not possible

% Dealer shows:
%      2 3 4 5 6 7 8 9 T A

HARD = [ ...
1   n n n n n n n n n n
2   1 1 1 1 1 1 1 1 1 1
3   1 1 1 1 1 1 1 1 1 1
4   1 1 1 1 1 1 1 1 1 1
5   1 1 1 1 1 1 1 1 1 1
6   1 1 1 1 1 1 1 1 1 1
7   1 1 1 1 1 1 1 1 1 1
8   1 1 1 1 1 1 1 1 1 1
9   2 2 2 2 2 1 1 1 1 1
10   2 2 2 2 2 2 2 2 1 1
11   2 2 2 2 2 2 2 2 2 2
12   1 1 0 0 0 1 1 1 1 1
13   0 0 0 0 0 1 1 1 1 1
14   0 0 0 0 0 1 1 1 1 1
15   0 0 0 0 0 1 1 1 1 1
16   0 0 0 0 0 1 1 1 1 1
17   0 0 0 0 0 0 0 0 0 0
18   0 0 0 0 0 0 0 0 0 0
19   0 0 0 0 0 0 0 0 0 0
20   0 0 0 0 0 0 0 0 0 0];

SOFT = [ ...
1   n n n n n n n n n n
2   1 1 2 2 2 1 1 1 1 1
3   1 1 2 2 2 1 1 1 1 1
4   1 1 2 2 2 1 1 1 1 1
5   1 1 2 2 2 1 1 1 1 1
6   2 2 2 2 2 1 1 1 1 1
7   0 2 2 2 2 0 0 1 1 0
8   0 0 0 0 0 0 0 0 0 0
9   0 0 0 0 0 0 0 0 0 0];

PAIR = [ ...
1   n n n n n n n n n n
2   1 1 1 1 1 1 0 0 0 0
3   1 1 1 1 1 1 0 0 0 0
4   0 0 0 1 0 0 0 0 0 0
5   0 0 0 0 0 0 0 0 0 0
6   1 1 1 1 1 1 0 0 0 0
7   1 1 1 1 1 1 1 0 0 0
8   1 1 1 1 1 1 1 1 1 1
9   1 1 1 1 1 0 1 1 0 0
10   0 0 0 0 0 0 0 0 0 0
11   1 1 1 1 1 1 1 1 1 1];

% ------------------------

function bj(N)
% Blackjack, main program.
% Play N hands.
% If N == 1, show detail and allow interaction.

global PAIR

S = get(gcf,'userdata');
n = length(S);
bet = 10;
detail = N==1;

% Set up graphics

if detail
delete(get(gca,'children'))
delete(findobj(gcf,'type','axes'))
axes('pos',[0 0 1 1])
axis([-5 5 -5 5])
axis off
bjbuttons('detail');
stake = sum(S);
if stake >= 0, sig = '+'; else, sig = '-'; end
str = sprintf('%6.0f hands,  \$ %c%d',n,sig,abs(stake));
titl = text(-2.5,4.5,str,'fontsize',20);
n0 = n+1;
n1 = n0;
else
bjbuttons('off');
payoffs = [-4:1 1.5 2:4]*bet;   % Possible payoffs
counts = hist(S,payoffs);
n0 = n+1;
n1 = ceil((n0)/N)*N;
subplot(2,1,2)
h = plot(0,0);
end
S = [S zeros(1,n1-n0+1)];

for n = n0:n1
bet1 = bet;
P = deal;         % Player's hand
D = deal;         % Dealer's hand
P = [P deal];
D = [D -deal];    % Hide dealer's hole card

% Split pairs
split = mod(P(1),13)==mod(P(2),13);
if split
if detail
show('Player',P)
show('Dealer',D)
split = PAIR(value(P(1)),value(D(1)));
% 0 = Keep pair
% 1 = Split pair
split = bjbuttonclick('split',split+1);
else
split = PAIR(value(P(1)),value(D(1)));
end
end
if split
P2 = P(2);
if detail, show('Split',P2); end
P = [P(1) deal];
bet2 = bet1;
end

% Play player's hand(s)
if detail
[P,bet1] = playhand('Player',P,D,bet1);
show('Player',P)
if split
P2 = [P2 deal];
show('Split',P2)
[P2,bet2] = playhand('Split',P2,D,bet2);
end
else
[P,bet1] = playhand('',P,D,bet1);
if split
P2 = [P2 deal];
[P2,bet2] = playhand('',P2,D,bet2);
end
end

% Play dealer's hand
D(2) = -D(2);     % Reveal dealer's hole card
while value(D) <= 16
D = [D deal];
end

% Payoff
if detail
show('Dealer',D)
show('Player',P)
s = payoff('Player',P,D,split,bet1);
if split
show('Split',P2)
s = s + payoff('Split',P2,D,split,bet2);
end
else
s = payoff('',P,D,split,bet1);
if split
s = s + payoff('',P2,D,split,bet2);
end
end
S(n) = s;

if detail
stake = stake + s;
if stake >= 0, sig = '+'; else, sig = '-'; end
str = sprintf('%6.0f hands,  \$ %c%d',n,sig,abs(stake));
set(titl,'string',str)
end

chunk = min(2000,N);
if ~detail & mod(n,chunk) == 0
Schunk = S(n-chunk+1:n);

subplot(2,1,2)
ydata = get(h,'ydata');
ydata = ydata(end) + cumsum(Schunk);
ylim = get(gca,'ylim');
if max(ydata) > ylim(1) | min(ydata) < ylim(2)
ydata = cumsum(S(1:n));
h = plot(1:n,ydata,'erasemode','none');
line([1 n1],[0 0],'color','black')
ylim = 1000*[floor(min(min(ydata)/1000,-1)) ...
ceil(max(max(ydata)/1000,1))];
axis([1 n1 ylim])
else
set(h,'xdata',n-chunk+1:n,'ydata',ydata);
end

subplot(2,1,1)
[kounts,x] = hist(S(n-chunk+1:n),payoffs);
counts = counts + kounts;
p = counts/n;
bar(x,p)
axis([-5*bet 5*bet 0 .5])
stake = ydata(end);
if stake >= 0, sig = '+'; else, sig = '-'; end
str = sprintf('%c%d',sig,abs(stake));
if abs(stake) < 1000, str = [' ' str]; end
if abs(stake) < 100, str = [' ' str]; end
if abs(stake) < 10, str = [' ' str]; end
text(-20,.55,sprintf('%6.0f hands,  \$ %s',n,str))
set(gca,'xtick',payoffs);
for k = 1:length(payoffs)
if payoffs(k)==15, y = -.12; else, y = -.08; end
text(payoffs(k)-6.5,y,sprintf('%9.3f',100*p(k)));
end

%     Mean and confidence interval, relative to unit bet

r = payoffs/bet;
mu = p*r';
crit = 1.96;         % norminv(.975)
rho = crit*sqrt((p*(r.^2)'-mu^2)/n);
pm = char(177);
text(20,.3,sprintf('%6.4f %c %6.4f',mu,pm,rho));
drawnow
end
end
set(gcf,'userdata',S);

% ------------------------

function shuffle
% Shuffle the deck
global DECK
DECK = mod(randperm(4*52)-1,52)+1;

% ------------------------

function d = deal
% Deal one card
global DECK
if length(DECK) <= 2
shuffle
end
d = DECK(1);
DECK(1) = [];

% ------------------------

function v = valuehard(X)
% Evaluate hand
X = mod(X-1,13)+1;
X = min(X,10);
v = sum(X);

% ------------------------

function v = value(X)
% Evaluate hand
X = mod(X-1,13)+1;
X = min(X,10);
v = sum(X);
% Promote soft ace
if any(X==1) & v<=11
v = v + 10;
end

% ------------------------

function [P,bet] = playhand(hand,P,D,bet)
% Play player's hand

global SOFT HARD

while value(P) < 21
% 0 = stand
% 1 = hit
% 2 = double down
if any(mod(P,13)==1) & valuehard(P)<=10
strat = SOFT(value(P)-11,value(D(1)));
else
strat = HARD(value(P),value(D(1)));
end
if length(P) > 2 & strat == 2
strat = 1;
end
if ~isempty(hand)
show(hand,P)
show('Dealer',D)
strat = bjbuttonclick('hit',strat+1,length(P)>2);
end
switch strat
case 0
break
case 1
P = [P deal];
case 2
% Double down.
% Double bet and get one more card
bet = 2*bet;
P = [P deal];
break
otherwise
break
end
end

% ------------------------

function s = payoff(who,P,D,split,bet)
% Payoff
detail = ~isempty(who);
fs = 20;
valP = value(P);
valD = value(D);
if valP == 21 & length(P) == 2 & ...
~(valD == 21 & length(D) == 2) & ~split
s = 1.5*bet;
if detail, str = ['BLACKJACK: +' int2str(s)]; end
elseif valP > 21
s = -bet;
if detail, str = ['BUST: ' int2str(s)]; end
elseif valD > 21
s = bet;
str = ['WIN: +' int2str(s)];
if detail
text(min(1.5*length(D)-4.5,2.75),-2.5,'BUST','fontsize',fs)
end
elseif valD > valP
s = -bet;
if detail, str = ['LOSE: ' int2str(s)]; end
elseif valD < valP
s = bet;
if detail, str = ['WIN: +' int2str(s)]; end
else
s = 0;
if detail, str = 'PUSH'; end
end
if detail
x = min(1.5*length(P)-4.5,2.75);
if isequal(who,'Player')
y = 2.5;
else
y = 0;
end
text(x,y,str,'fontsize',fs)
end

% ------------------------

function show(who,H)
% Displays one hand
switch who
case 'Player', y = 2.5;
case 'Split', y = 0;
case 'Dealer', y = -2.5;
end
x = -4;
for j = 1:length(H)
card(x,y,H(j),length(H))
x = x + 1.5;
end

% ------------------------

function card(x,y,v,gray)
% card(x,y,v) plots v-th card at position (x,y).
z = exp((0:16)/16*pi/2*i)/16;
edge = [z+1/2+7*i/8 i*z-1/2+7*i/8 -z-1/2-7*i/8 -i*z+1/2-7*i/8 9/16+7*i/8];
pips = {'A','2','3','4','5','6','7','8','9','10','J','Q','K'};
if v <= 0
% Hole card
patch(real(edge)+x,imag(edge)+y,[0 0 2/3])
else
suit = ceil(v/13);
v = mod(v-1,13)+1;
x1 = x-.2;
fs = 20;
if v==10, x1 = x1-.2; end
pc = [1 1 1];
if y == 0 & gray == 1, pc = [.75 .75 .75]; end
patch(real(edge)+x,imag(edge)+y,pc)
switch suit
case {1,4}, color = [0 0 0];
case {2,3}, color = [2/3 0 0];
end
text(x1,y,pips{v},'fontname','courier','fontsize',fs, ...
'fontweight','bold','color',color)
text(x,y+.025,char(166+suit),'fontname','symbol','fontsize',fs, ...
'color',color)
end

% ------------------------

function val = bjbuttonclick(kase,basic,disable)
bjb = bjbuttons(kase);
if nargin == 3 & disable
set(bjb(3),'enable','off')
end
if nargin >= 2
set(bjb(basic),'fore','red')
end
while all(cell2mat(get(bjb,'val')) == 0)
drawnow
end
val = find(cell2mat(get(bjb,'val')))-1;

% ------------------------

function bjb = bjbuttons(kase)

bjb = findobj(gcf,'style','toggle');
if isempty(bjb)
for b = 3:-1:1
bjb(b,1) = uicontrol('units','normal','style','toggle', ...
'pos',[.95-.18*b .02 .16 .08],'fontweight','bold');
end
end
set(bjb,'fore','black')
switch kase
case {1,2}
switch kase
case 1
fs = 12; y = .02; dy = .08;
case 2
fs = 10; y = .01; dy = .06;
end
for b = 1:3
set(bjb(b),'pos',[.95-.18*b y .16 dy])
end
set(bjb,'val',0,'vis','on','enable','on','fontsize',fs)
set(bjb(1),'string','Close')
set(bjb(2),'string','Play')
set(bjb(3),'string','Simulate')
set(bjb(kase+1),'fore','red')
case 'detail'
set(bjb(1:2),'vis','on')
set(bjb(3),'vis','off')
for b = 1:3
set(bjb(b),'pos',[.95-.18*b .02 .16 .08])
end
set(bjb,'val',0,'fontsize',12)
case 'off'
set(bjb,'vis','off')
case 'split'
set(bjb,'val',0,'fontsize',12)
set(bjb(1),'string','Keep')
set(bjb(2),'string','Split')
case 'hit'
set(bjb,'val',0,'vis','on','fontsize',12)
set(bjb(1),'string','Stand')
set(bjb(2),'string','Hit')
set(bjb(3),'string','Double')
end
```