function [rank,description,usedcards,unusedcards] = rankp(hands,handsize)
% A function that evaluates poker hands and returns a numerical rank and
% text description for each. The list of cards used to make the best
% possible hand is returned in 'usedcards'. If handsize is smaller than
% the length of hands given, then the cards not used in the best possible
% hand are returned in 'unusedcards'. Rank of 1 is best (Royal Flush),
% higher numbers are worse. Rank values are not contiguous across all
% hands, but are correctly ordered.
%
% The hand matrix expected is an mxn list of m hands with n cards. The
% cards are numbered from 1 to 52. Suit doesn't matter for ranking. Order
% of the cards in the input vector does not matter. Numbering starts with
% the Ace at position 1. Here is a suggested card assignment:
%
% A | K | Q | J | 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | Suit
% -------------------------------------------------------------
% 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| Spades
% 14| 15| 16| 17| 18| 19| 20| 21| 22| 23| 24| 25| 26| Hearts
% 27| 28| 29| 30| 31| 32| 33| 34| 35| 36| 37| 38| 39| Clubs
% 40| 41| 42| 43| 44| 45| 46| 47| 48| 49| 50| 51| 52| Diamonds
%
% Example call to the function:
% trick = [33,3,43,1,25,4,19;
% 6,36,20,10,35,21,38;
% 45,31,51,40,37,22,26;
% 28,49,46,32,17,16,39];
% [ranks,descriptions,usedcards,unusedcards] = rankp(trick,2);
%
% revision 5
% - fixed a conditional error in the numerical ranking section,
% thanks to Tim C. for testing it
% revision 4
% - fixed a hole in the condition to wipe low aces if not used
% revision 3
% - made rankp "toolbox neutral" by using nchoosek instead of combnk,
% thanks to James for pointing that out
% - fixed a hole in the conditional statements for unusedcards
% revision 2
% - added handsize input, usedcards and unusedcards outputs
% - improved speed of classification with help from Doug
% revision 1
% - written for Doug's LazyWeb by Rob
%% Setup variables
handlen = size(hands,2);
numhands = size(hands,1);
% quality check on handsize argument
if nargin > 1
handsize = min([handsize handlen 5]);
else
handsize = min([handlen 5]);
end
rank = zeros(numhands,1);
description = cell(numhands,1);
usedcards = zeros(numhands,handsize);
if handlen > handsize
unusedcards = zeros(numhands,(handlen-handsize));
else
unusedcards = [];
end
%% Main process loop
% test each hand, keep max rank
for i = 1:numhands
handcombs = nchoosek(hands(i,:),handsize);
a = zeros(size(handcombs,1),1);
b = cell(size(a));
for j = 1:size(handcombs,1)
[a(j),b{j}] = rankhand(handcombs(j,:));
end
[a,ix] = sort(a);
rank(i) = a(1);
description{i} = b(ix(1));
usedcards(i,:) = handcombs(ix(1),:);
if handlen > handsize
unusedcards(i,:) = setxor(usedcards(i,:),hands(i,:));
end
end
%% Sub function
function [r,handclass] = rankhand(hand)
% Setup variables
if nargin < 1 || length(hand) > 5
r = 0;
handclass = 'Hand must have 1 to 5 cards!';
return;
end
len = length(hand);
cards = zeros(13,4);
cards(hand) = 1;
cards(14,:) = cards(1,:);
colsum = sum(cards);
rowsum = sum(cards,2);
rownames = {'Ace';'King';'Queen';'Jack';'10';'9';'8';'7';'6';'5';'4'; ...
'3';'2';'Ace'};
handclass = 'High';
done = false;
%% Classify the hand
% test for Straight and Straight-flush, wipe low Ace if not used
if len==5
if any(sum(logical([rowsum(1:5) rowsum(2:6) rowsum(3:7) rowsum(4:8)...
rowsum(5:9) rowsum(6:10) rowsum(7:11) rowsum(8:12)...
rowsum(9:13) rowsum(10:14)]))==5)
if sum(logical(colsum))==1
handclass = 'Straigh Flush';
else
handclass = 'Straight';
end
if sum(logical(rowsum(10:14))) == 5
cards(1,:) = 0;
else
cards(14,:) = 0;
end
colsum = sum(cards);
rowsum = sum(cards,2);
done = true;
end
end
if ~done
cards(14,:) = 0;
colsum = sum(cards);
rowsum = sum(cards,2);
end
% test for Four of a Kind
if len >= 4
if ~done && sum(rowsum==4)==1
handclass = 'Four';
done = true;
end
end
% test for full house
if len == 5
if ~done && sum(rowsum==3)==1 && sum(rowsum==2)==1
handclass = 'Full House';
done = true;
end
% test for Flush
if ~done && sum(colsum==5)==1
handclass = 'Flush';
done = true;
end
end
% test for 3 of a kind
if len >= 3
if ~done && sum(rowsum==3)==1
handclass = 'Three';
done = true;
end
end
% test for 2 pairs
if len >= 4
if ~done && sum(rowsum==2)==2
handclass = 'Two Pairs';
done = true;
end
end
% test for 1 pair
if len >= 2
if ~done && sum(rowsum==2)==1
handclass = 'Pair';
end
end
% if nothing else, handclass stays 'High'
%% Numerically rank the hand within its class
switch handclass
case 'Straigh Flush'
r = find(rowsum,1,'first');
case 'Four'
r = 1e1*find(rowsum==4);
if len == 5
r = r + find(rowsum==1);
end
case 'Full House'
r = 2e2*find(rowsum==3) + find(rowsum==2);
case 'Flush'
a = find(rowsum);
r = 3e3*a(1)+1e2*a(2)+1e2/2*a(3)+1e1*a(4)+a(5);
case 'Straight'
r = 4e4*find(rowsum,1,'first');
case 'Three'
r = 5e5*find(rowsum==3);
if len > 3
a = find(rowsum==1);
r = r+1e4*a(1);
if len==5
r = r+1e3*a(2);
end
end
case 'Two Pairs'
a = find(rowsum==2);
r = 7e6*a(1)+1e5*a(2);
if len == 5
b = find(rowsum==1);
r = r+1e4*b(1);
end
case 'Pair'
a = find(rowsum==2);
r = 2e8*a(1);
if len > 2
b = find(rowsum==1);
r = r+1e6*b(1);
if len > 3
r = r+1e5*b(2);
if len == 5
r = r+1e4*b(3);
end
end
end
case 'High'
a = find(rowsum);
r = 5e9*a(1);
if len > 1
r = r+1e7*a(2);
if len > 2
r = r+1e6*a(3);
if len > 3
r = r+1e5*a(4);
if len == 5
r = r+a(5);
end
end
end
end
otherwise
disp('There was an error classifying this hand!');
return;
end
%% Generate text output to describe the hand
switch handclass
case 'Straigh Flush'
if r == 1
handclass = 'Royal Flush!';
else
handclass = [handclass,' to the ',rownames{find(rowsum,1,'first')}];
end
case 'Four'
handclass = [handclass,' ',rownames{find(rowsum==4)},'''s'];
if len == 5
handclass = [handclass,' and a ',rownames{find(rowsum==1)}];
end
case 'Full House'
handclass = [handclass,', ',rownames{find(rowsum==3)},'''s and ',...
rownames{find(rowsum==2)},'''s'];
case 'Flush'
handclass = [handclass,' with ',rownames{a(1)},', ',rownames{a(2)},...
', ',rownames{a(3)},', ',rownames{a(4)},', and ',rownames{a(5)}];
case 'Straight'
handclass = [handclass,' to the ',rownames{find(rowsum,1,'first')}];
case 'Three'
handclass = [handclass,' ',rownames{find(rowsum==3)},'s'];
if len > 3
handclass = [handclass,' with ',rownames{a(1)}];
if len == 5
handclass = [handclass,', and ',rownames{a(2)}];
end
end
case 'Two Pairs'
handclass = [handclass,', ',rownames{a(1)},'''s and ',rownames{a(2)},'''s'];
if len == 5
handclass = [handclass,' with a(n) ',rownames{b(1)}];
end
case 'Pair'
handclass = [handclass,' of ',rownames{a(1)},'''s'];
if len > 2
handclass = [handclass,' with ',rownames{b(1)}];
if len > 3
handclass = [handclass,', ',rownames{b(2)}];
if len == 5
handclass = [handclass,', and ',rownames{b(3)}];
end
end
end
case 'High'
handclass = [rownames{a(1)},' ',handclass,' with ',rownames{a(2)}];
if len > 2
handclass = [handclass,', ',rownames{a(3)}];
if len > 3
handclass = [handclass,', ',rownames{a(4)}];
if len == 5
handclass = [handclass,', and ',rownames{a(5)}];
end
end
end
otherwise
disp('There was an error generating text for this hand.');
end