image thumbnail

Texas Hold'Em Poker Simulator

by

 

This application simulates Texas Hold'Em poker probabilities via Monte Carlo simulation.

TexasHoldEmSimulator(varargin)
function TexasHoldEmSimulator(varargin)
%--------------------------------------------------------------------------
% Syntax:       TexasHoldEmSimulator;
%               TexasHoldEmSimulator(NumPlayers);
%               TexasHoldEmSimulator(FlopTurnRiverMode);
%               TexasHoldEmSimulator(NumPlayers,FlopTurnRiverMode);
%               TexasHoldEmSimulator(FlopTurnRiverMode,NumPlayers);
%
% Inputs:       NumPlayers is the number of players in the poker game. If
%               not specified, the user is prompted to enter this
%               information during execution.
%
%               FlopTurnRiverMode can be {'Manual','Random'} and specifies
%               whether the user will manually enter the Flop/Turn/River
%               cards or if the program should randomly generate them. If
%               not specified, the user is prompted to enter this
%               information during execution.
%
% Description:  This function interactively simulates the probability of
%               each player winning a hand of Texas Hold'Em Poker given
%               their respective hole cards after 1) the Deal, 2) the Flop,
%               and 3) the Turn. Finally, after the River, this program
%               displays the actual results of the hand.
%
% Method:       Monte Carlo simulation is used to estimate all
%               probabilities of interest.
%
% Author:       Brian Moore
%               brimoor@umich.edu
%
% Date:         September 15, 2012
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Program constants that you may adjust if desired
%--------------------------------------------------------------------------
%
% Number of Monte Carlo simulations to run
%
NUM_MONTE_CARLO_SIMS = 3000;

%
% Update Monte Carlo plots after every UPDATE_PLOT_SPACING simulations
%
UPDATE_PLOT_SPACING = 100;

%
% Determines whether or not to display percentage values on the Monte Carlo
% plots by default
%
DefaultDisplayPercentages = false; % can be {true,false}

%
% Determines whether or not to play music during program execution
%
PlayMusic = true;  % can be {true,false}

%
% Path + filename + extension to a .wav file to play during Monte Carlo
% execution
%
% Note: monteCarloMusic is played only when PlayMusic = true
%
monteCarloMusic = 'jeopardy.wav'; % must be a .wav file!

%
% Path + filename + extension to a .wav file to play at Showdown
%
% Note: showdownMusic is played only when PlayMusic = true
%
showdownMusic = 'applause.wav'; % must be a .wav file!

%
% Define number of cards added at each step
%
NumHoleCards = 2;
NumFlopCards = 3;
NumTurnCards = 1;
NumRiverCards = 1;

%
% The card strings in descending order of value (Cards{1} is highest card)
%
% Note: Each card string can be any desired length
%
Cards = {'A','K','Q','J','10','9','8','7','6','5','4','3','2'};

%
% The suit symbols in descending order of value (Suits{1} is highest suit)
%
% Note: Each suit string must be a SINGLE CHARACTER
%
Suits = {'S','D','H','C'};

%
% Cell array that describes the valid poker hands
%
% Each row describes a valid poker hand listed in descending order of value
% Row format: {Name string, Description string, Identifier/label string}
% 
% NOTE: Don't change the Identifier/label strings (3rd column) unless you
% are prepared to modify the BestHand() function
%
% NOTE: If you want to create your own hand, you'll have to add a case that
% recognizes your Identifer/label string to the switch statement in the 
% BestHand() function
%
Hands = {'Royal Flush','AKQJ10 of same suit','RF';
         'Straight Flush','5 consecutive cards of same suit','SF';
         'Four of a Kind','4 cards of same number','4K';
         'Full House','3 cards of one number and 2 cards of another number','FH';
         'Flush','5 cards of 1 suit','F';
         'Straight','5 consecutive cards (suit doesn''t matter)','S';
         'Three of a Kind','3 cards of same number','3K';
         'Two Pair','2 cards each of 2 different numbers','2P';
         'One Pair','2 cards of same number','1P';
         'High Card','Highest card in hand','H';
         };

%
% Program name
%
ProgramName = 'TexasHoldEmSimulator v1.0.0';

%
% String to display upon exiting the program via 'Exit'
%
ExitMessageStr = '"Thanks for playing!" - Brian Moore';

%
% Organization of subplots for simulation graphs
%
% Note: If there are more than 8 players, the hand distribution plots for
% players 9+ will not be displayed unless you add rows to SubplotOrgChart
% that define a subplot structure to use in those cases
% 
% The ith row of SubplotOrgChart describes the subplot structure of
% the Monte Carlo simulation figures for i players.
%
% Format: (Assuming i players)
%
% Probability of Winning Plot Location:
%
% subplot(SubplotOrgChart{i,1}, ...
%         SubplotOrgChart{i,2}, ...
%         SubplotOrgChart{i,3})
% 
% Hand Distribution Plot Location (for the jth player, 1 <= j <= i):
%
% subplot(SubplotOrgChart{i,1}, ...
%         SubplotOrgChart{i,2}, ...
%         SubplotOrgChart{i,4}(j))
%
SubplotOrgChart = {1,2,1,2;
                   2,2,[1,2],[3,4];
                   2,2,1,[2,3,4];
                   2,3,[1,2],[3,4,5,6];
                   2,3,1,[2,3,4,5,6];
                   2,4,[2,3],[1,4,5,6,7,8];
                   2,4,1,[2,3,4,5,6,7,8];
                   3,3,5,[1,2,3,4,6,7,8,9]};
%--------------------------------------------------------------------------

% Initialize size variables
NumCards = length(Cards);
NumSuits = length(Suits);
DeckSize = NumCards * NumSuits;
NumHands = size(Hands,1);
MaxPlayersToPlot = size(SubplotOrgChart,1);
outerFnCall = true;

% Set font size based on OS
if strcmpi(getenv('os'),'Windows_NT')
    % Windows computer
    fontSize = 12;
else
    % Mac, presumably
    fontSize = 14;
end

% Load music if requested
monteCarloAudioPlayer = [];
showdownAudioPlayer = [];
if PlayMusic
    if exist(monteCarloMusic,'file')
        [wav Fs Nbits] = wavread(monteCarloMusic);
        monteCarloAudioPlayer = audioplayer(wav,Fs,Nbits);
    end
    if exist(showdownMusic,'file')
        [wav Fs Nbits] = wavread(showdownMusic);
        showdownAudioPlayer = audioplayer(wav,Fs,Nbits);
    end
end

% Process user input
if (nargin == 1)
    if ischar(varargin{1})
        FlopTurnRiverMode = varargin{1};
        NumPlayers = NaN;
    else
        NumPlayers = varargin{1};
        FlopTurnRiverMode = NaN;
    end
elseif (nargin == 2)
    if ischar(varargin{1})
        FlopTurnRiverMode = varargin{1};
        NumPlayers = varargin{2};
    else
        FlopTurnRiverMode = varargin{2};
        NumPlayers = varargin{1};
    end
else
    FlopTurnRiverMode = NaN;
    NumPlayers = NaN;
end

% Print some stuff
DisplayHelp;

%
% Put everything in a try-catch block to allow a graceful exit when user
% types 'Exit'
% 
% Note: All exceptions that do not carry ExitMessageStr are re-thrown
%
try
%--------------------------------------------------------------------------
% Get any information that wasn't passed in the function call
%--------------------------------------------------------------------------
% Get number of players at the table (if necessary)
if isnan(NumPlayers)
    NumPlayers = str2double(ProcessUserInput('How many players at the table? '));
    while ~isfinite(NumPlayers)
        NumPlayers = str2double(ProcessUserInput('Invalid number. Try again: '));
    end
end

% Get FlopTurnRiverMode (if necessary)
if sum(isnan(FlopTurnRiverMode))
    str = ProcessUserInput('Enter Flop/Turn/River Cards Manually? (y or n): ');
    if sum(strcmpi(str,'y'))
        FlopTurnRiverMode = 'Manual';
    else
        FlopTurnRiverMode = 'Random';
    end
end
%--------------------------------------------------------------------------

% Initialize deck
Deck = 1:DeckSize;

%--------------------------------------------------------------------------
% Get everyone's hole cards
%--------------------------------------------------------------------------
PlayersCards = zeros(NumPlayers,NumHoleCards);
for i = 1:NumPlayers
    PlayersCards(i,:) = ParseHoleCards(sprintf('What are Player %s''s hole cards? ',num2str(i)));
end
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Perform Monte Carlo simulations after each betting round
%--------------------------------------------------------------------------
% Simulate winning percentages and hand-distributions based on hole cards
SimulateHand([],'Deal');

% Get the Flop
if strcmpi(FlopTurnRiverMode,'Manual')
    FlopTurnRiver = GetCards('What was the Flop? ',NumFlopCards);
else
    flop = DrawCards(NumFlopCards);
    fprintf(['\nThe Flop was: ' GetHandString(flop) '\n']);
    FlopTurnRiver = flop;
end

% Simulate winning percentages up to (and including) the Flop
SimulateHand(FlopTurnRiver,'Flop');

% Get the Turn
if strcmpi(FlopTurnRiverMode,'Manual')
    FlopTurnRiver = [FlopTurnRiver GetCards('What was the Turn? ',NumTurnCards)];
else
    turn = DrawCards(NumTurnCards);
    fprintf(['\nThe Turn was: ' GetHandString(turn) '\n']);
    FlopTurnRiver = [FlopTurnRiver turn];
end

% Simulate winning percentages up to (and including) the Turn
SimulateHand(FlopTurnRiver,'Turn');
%--------------------------------------------------------------------------

% Get the River
if strcmpi(FlopTurnRiverMode,'Manual')
    FlopTurnRiver = [FlopTurnRiver GetCards('What was the River? ',NumRiverCards)];
else
    river = DrawCards(NumRiverCards);
    fprintf(['\nThe River was: ' GetHandString(river) '\n']);
    FlopTurnRiver = [FlopTurnRiver river];
end

% Determine results of hand
[handRanking handResults] = HandResults(FlopTurnRiver);

%--------------------------------------------------------------------------
% Display hand results
%--------------------------------------------------------------------------
% Play music if requested
if ~isempty(showdownAudioPlayer)
    play(showdownAudioPlayer);
end

% Show the winning hand seperately
line1 = ' The Winner is '; len1 = length(line1);
line2 = ['Player ' num2str(handRanking(1))]; len2 = length(line2);
if (handResults(handRanking(1),1) > NumHands)
    line3 = '| Hand: ???                |'; len3 = length(line3);
else
    line3 = ['| Hand: ' Hands{handResults(handRanking(1),1),1} ' - ' GetHandString(handResults(handRanking(1),2:end)) ' |']; len3 = length(line3);
end
fprintf(sprintf('\n%s',['+' repmat('-',1,floor((len3 - len1 - 2) / 2)) line1 repmat('-',1,ceil((len3 - len1 - 2) / 2)) '+']));
fprintf(sprintf('\n%s',['|' repmat(' ',1,floor((len3 - len2 - 2) / 2)) line2 repmat(' ',1,ceil((len3 - len2 - 2) / 2)) '|']));
fprintf(sprintf('\n%s',line3));
fprintf(sprintf('\n%s\n',['+' repmat('-',1,len3 - 2) '+']));

% Show the rest of the hands
for i = 2:NumPlayers
    if (handResults(handRanking(i),1) > NumHands)
        handStr = '???';
    else
        handStr = [Hands{handResults(handRanking(i),1),1} ' - ' GetHandString(handResults(handRanking(i),2:end))];
    end
    fprintf(['\nPlayer ' num2str(handRanking(i)) '\nHand: ' handStr '\n']);
end
%--------------------------------------------------------------------------

% Ask user if they want to simulate another hand
str = ProcessUserInput('Simulate another hand? (y or n): ');
if sum(strcmpi(str,'y'))
    outerFnCall = false;
    TexasHoldEmSimulator(NumPlayers,FlopTurnRiverMode);
end
catch e
    if ~strcmpi(e.message,ExitMessageStr)
        throw(e);
    end
end

% Goodbye!
if outerFnCall, fprintf(sprintf('\n%s\n\n',ExitMessageStr)); end;

%--------------------------------------------------------------------------
% Nested functions
%--------------------------------------------------------------------------
% Simulate the rest of a hand given the current FlopTurnRiver state
function SimulateHand(flopTurnRiver,dispStr)
    % Play music during simulation if requested
    if ~isempty(monteCarloAudioPlayer)
        play(monteCarloAudioPlayer);
    end
    
    % Save Players cards state
    playersCardsCache = PlayersCards;
    
    % Save deck state
    deckCache = Deck;
    
    % Save FlopTurnRiver state
    flopTurnRiverCache = flopTurnRiver;
    numDraws = NumFlopCards + NumTurnCards + NumRiverCards - length(flopTurnRiver);
    
    % Find locations of hands to fill in
    emptyHandInds = find(PlayersCards == 0);
    
    % Initialize reporting variables
    winCount = zeros(1,NumPlayers);
    handCount = zeros(NumPlayers,NumHands);
    
    % Generate simulation output figure
    if DefaultDisplayPercentages
        boolVal = 1;
    else
        boolVal = 0;
    end
    f = figure('name',[ProgramName ' - Monte Carlo Simulation - After ' dispStr ' - Brian Moore 2012']);
    cb1 = uicontrol(f,'Style','checkbox','Value',boolVal,'String',char(37),'Position',[10 30 60 20],'HorizontalAlignment','left','FontSize',fontSize-2,'Background',get(f,'Color'),'callback',@checkboxCallback);
    cb2 = uicontrol(f,'Style','text','String','','Position',[10 5 500 20],'HorizontalAlignment','left','FontSize',fontSize-2,'Background',get(f,'Color'));
    npp = min(MaxPlayersToPlot,NumPlayers);
    DisplayPercentages = DefaultDisplayPercentages;
    UpdatePlots(f,0);
    
    % Perform Monte Carlo simulation
    for idx = 1:NUM_MONTE_CARLO_SIMS
        % Check if any player hands need to be randomly generated
        if ~isempty(emptyHandInds)
            % Recall original deck state
            Deck = deckCache;
            
            % Randomly generate unknown hands
            for kdx = 1:length(emptyHandInds)
                PlayersCards(emptyHandInds(kdx)) = DrawCards(1);
            end
        end
        
        % Generate rest of FlopTurnRiver randomly
        ftr = [flopTurnRiverCache RandomDraw(numDraws)];
        
        % Determine outcome of hand
        [ranking Results] = HandResults(ftr);
        
        % Save statistics
        winCount(ranking(1)) = winCount(ranking(1)) + 1;
        for iidx = 1:NumPlayers
            handCount(iidx,Results(iidx,1)) = handCount(iidx,Results(iidx,1)) + 1;
        end
        
        % Update graph every once and a while
        if ~mod(idx,UPDATE_PLOT_SPACING)
            if ishandle(f)
                UpdatePlots(f,idx);
            else
                break;
            end
        end
    end
    
    % Stop music if it was playing
    if ~isempty(monteCarloAudioPlayer)
        stop(monteCarloAudioPlayer);
    end
    
    if ishandle(f)
        % Update graphs with final data
        UpdatePlots(f,NUM_MONTE_CARLO_SIMS);
        
        % Wait for user to inspect graphs before continuing
        fprintf('\nPress any key to continue...\n');
        pause;
    end
    
    % Recall initial state
    Deck = deckCache;
    PlayersCards = playersCardsCache;
    
    % Updates simulation plots in figure f
    function UpdatePlots(f,numSimsCompleted)
        % Get current figure
        figure(f);
        colors = hsv(NumPlayers);
        
        % Update victory probability graph
        subplot(SubplotOrgChart{npp,1},SubplotOrgChart{npp,2},SubplotOrgChart{npp,3});
        b = bar(winCount / sum(winCount));
        if DisplayPercentages
            x_loc = get(b,'XData');
            y_height = get(b,'YData');
            arrayfun(@(x,y) text(x,y+0.06,sprintf('%2.1f%s',100*y,char(37)),'FontSize',fontSize-2,'HorizontalAlignment','center'),x_loc,y_height);
        else
            grid on;
        end
        set(gca,'FontSize',fontSize);
        ch = get(b,'Children');
        set(ch,'FaceVertexCData',colors);
        titleStr = ['Probability of Victory After ' dispStr];
        if ~isempty(flopTurnRiverCache), titleStr = [titleStr ' (' GetHandString(flopTurnRiverCache) ')']; end;
        title(titleStr,'FontSize',fontSize);
        ylabel('Probability','FontSize',fontSize);
        xlabel('Player','FontSize',fontSize);
        axis([0 (NumPlayers + 1) 0 1])
        
        % Update hand distribution graphs
        for i_ = 1:npp
            subplot(SubplotOrgChart{npp,1},SubplotOrgChart{npp,2},SubplotOrgChart{npp,4}(i_));
            colormap(hsv(i_+1));
            b = bar(handCount(i_,:) / sum(handCount(i_,:)));
            if DisplayPercentages
                x_loc = get(b,'XData');
                y_height = get(b,'YData');
                arrayfun(@(x,y) text(x+0.1,y+0.06,sprintf('%2.1f%s',100*y,char(37)),'FontSize',fontSize-2,'HorizontalAlignment','center'),x_loc,y_height);
            else
                grid on;
            end
            set(gca,'FontSize',fontSize);
            set(b,'FaceColor',colors(i_,:));
            title(['Player ' num2str(i_) ' (' GetHandString(playersCardsCache(i_,:)) ')'],'FontSize',fontSize);
            ylabel('Probability','FontSize',fontSize);
            xlabel('Hand','FontSize',fontSize);
            set(gca,'XTickLabel',Hands(:,3),'FontSize',fontSize);
            axis([0 (NumHands + 1) 0 1])
        end
        
        % Update simulation count
        set(cb2,'String',[num2str(numSimsCompleted) '/' num2str(NUM_MONTE_CARLO_SIMS) ' Simulations Completed']);
                
        % Force a figure refresh
        drawnow;
    end
    
    % Callback function for when the display percentage checkbox is clicked
    function checkboxCallback(~,~)
        val = get(cb1,'Value');
        if (val == 1)
            DisplayPercentages = true;
        else
            DisplayPercentages = false;
        end
        UpdatePlots(gcf,idx);
    end
end

% Return results of hand
function [ranking result] = HandResults(commonCards)
    % Compute each player's best hand
    result = zeros(NumPlayers,6);
    for jjj = 1:NumPlayers
        if ~isempty(find(PlayersCards(jjj,:) == 0,1))
            result(jjj,:) = [(NumHands + 1) 0 0 0 0 0];
        else
            result(jjj,:) = BestHand([PlayersCards(jjj,:) commonCards]);
        end
    end

    % Figure out who won the hand
    results = sum(result .* repmat(logspace(2,-8,6),NumPlayers,1),2);
    [~,ranking] = sort(results);
end

% Figure out the best hand given hole cards + FlopTurnRiver
function result = BestHand(cards)    
    % Sort cards
    cards = sort(cards);
    
    % Create hand matrix
    handMatrix = zeros(NumSuits,NumCards);
    for idx = 1:length(cards)
        handMatrix(cards(idx)) = 1;
    end
    
    % Get some info for the rest of the checks
    NumCardsPerSuit = sum(handMatrix,2);
    NumEachCard = sum(handMatrix,1);
    ind4 = find(NumEachCard >= 4,1,'first');
    ind3 = find(NumEachCard >= 3,2,'first');
    ind2 = find(NumEachCard >= 2,2,'first');
    indFl = find(NumCardsPerSuit >= 5,1,'first');
    
    % Determine best hand from given cards 
    result = zeros(1,6);
    for jjdx = 1:size(Hands,1)
        switch Hands{jjdx,3}
            case 'RF'
                % Check for Royal Flush
                suitsRF = (sum(handMatrix(:,1:5),2) == 5);
                suitRF = find(suitsRF,1,'first');
                if ~isempty(suitRF)
                    % Found Royal Flush
                    result(1) = jjdx;
                    result(2:6) = suitRF + NumSuits * (0:4);
                    return;
                end
            case 'SF'
                % Check for Straight Flush
                mat = [handMatrix handMatrix(:,1)];
                for suitStFl = 1:NumSuits
                    startInd = strfind(mat(suitStFl,:),ones(1,5));
                    if ~isempty(startInd)
                        % Found Straight Flush
                        result(1) = jjdx;
                        result(2:6) = suitStFl + NumSuits * (startInd(1) + (-1:3));
                        if (startInd(1) == (NumCards - 3))
                            result(6) = result(6) - DeckSize;
                        end
                        return;
                    end
                end
            case '4K'
                % Check for Four of a Kind
                if ~isempty(ind4)
                    % Found Four of a Kind
                    result(1) = jjdx;
                    suits4 = find(handMatrix(:,ind4(1)),4,'first');
                    result(2:5) = (NumSuits * (ind4(1) - 1) + suits4);
                    hand = setdiff(cards,result(2:5));
                    result(6) = hand(1);
                    return;
                end
            case 'FH'
                % Check for Full House
                if ~isempty(ind3)
                    ind23 = setdiff(ind2,ind3(1));
                    if ~isempty(ind23)
                        % Found Full House
                        result(1) = jjdx;
                        suits3 = find(handMatrix(:,ind3(1)),3,'first');
                        suits2 = find(handMatrix(:,ind23(1)),2,'first');
                        result(2:4) = (NumSuits * (ind3(1) - 1) + suits3);
                        result(5:6) = (NumSuits * (ind23(1) - 1) + suits2);
                        return;
                    end
                end
            case 'F'
                % Check for Flush
                if ~isempty(indFl)
                    % Found Flush
                    result(1) = jjdx;
                    cardNumFl = find(handMatrix(indFl(1),:),5,'first');
                    result(2:6) = (NumSuits * (cardNumFl - 1) + indFl(1));
                    return;
                end
            case 'S'
                % Check for Straight
                startInd = strfind([NumEachCard NumEachCard(1)] > 0,ones(1,5));
                if ~isempty(startInd)
                    % Found Straight
                    result(1) = jjdx;
                    if (startInd(1) == (NumCards - 3))
                        for idx = 1:4
                            result(idx + 1) = NumSuits * (startInd(1) + idx - 2) + find(handMatrix(:,startInd(1) + idx - 1),1,'first');
                        end
                        result(6) = find(handMatrix(:,1),1,'first');
                    else
                        for idx = 1:5
                            result(idx + 1) = NumSuits * (startInd(1) + idx - 2) + find(handMatrix(:,startInd(1) + idx - 1),1,'first');
                        end
                    end
                    return;
                end
            case '3K'
                % Check for Three of a Kind
                if ~isempty(ind3)
                    % Found Three of a Kind
                    result(1) = jjdx;
                    suits3 = find(handMatrix(:,ind3(1)),3,'first');
                    result(2:4) = (NumSuits * (ind3(1) - 1) + suits3);
                    hand = setdiff(cards,result(2:4));
                    result(5:6) = hand(1:2);
                    return;
                end
            case '2P'
                % Check for Two Pair
                if (length(ind2) >= 2)
                    % Found Two Pair
                    result(1) = jjdx;
                    suits2a = find(handMatrix(:,ind2(1)),2,'first');
                    suits2b = find(handMatrix(:,ind2(2)),2,'first');
                    result(2:3) = (NumSuits * (ind2(1) - 1) + suits2a);
                    result(4:5) = (NumSuits * (ind2(2) - 1) + suits2b);
                    hand = setdiff(cards,result(2:5));
                    result(6) = hand(1);
                    return;
                end
            case '1P'
                % Check for One Pair
                if ~isempty(ind2)
                    % Found One Pair
                    result(1) = jjdx;
                    suits2 = find(handMatrix(:,ind2(1)),2,'first');
                    result(2:3) = (NumSuits * (ind2(1) - 1) + suits2);
                    hand = setdiff(cards,result(2:3));
                    result(4:6) = hand(1:3);
                    return;
                end
            case 'H'
                % High Card
                result(1) = jjdx;
                result(2:6) = cards(1:5);
                return;
            otherwise
              error('Hand type not supported');
        end
    end
end

% Draw N random cards from the deck (and remove them from deck)
function cards = DrawCards(N)
    inds = randperm(length(Deck),N);
    cards = Deck(inds);
    Deck(inds) = [];
end

% Draw N random cards from the deck (and leave them in deck)
function cards = RandomDraw(N)
    inds = randperm(length(Deck),N);
    cards = Deck(inds);
end

% Parses all user input
function userStr = ProcessUserInput(text)
    userStr = input(sprintf('\n%s',text),'s');
    if strcmpi(userStr,'Help')
        DisplayHelp;
        userStr = ProcessUserInput(text);
    elseif strcmpi(userStr,'Cards')
        DisplayCards;
        userStr = ProcessUserInput(text);
    elseif strcmpi(userStr,'Suits')
        DisplaySuits;
        userStr = ProcessUserInput(text);
    elseif strcmpi(userStr,'Hands')
        DisplayHands;
        userStr = ProcessUserInput(text);
    elseif strcmpi(userStr,'Exit')
        error(ExitMessageStr);
    end
end

% Parses the specified number of cards from user input
function cards = GetCards(text,num)
    cardStr = ProcessUserInput(text);
    cards = zeros(1,num);
    for idx = 1:num
        [c cardStr] = strtok(cardStr,[',',' ']); %#ok
        cardStr = cardStr(2:end);
        cards(idx) = ParseUserCard(c);
    end
end

% Takes a string like 'AH,9D' and converts it to an array of cards
function holeCards = ParseHoleCards(text)
    cardStr = ProcessUserInput(text);
    holeCards = zeros(1,NumHoleCards);
    if ~strcmpi(cardStr,'X')
        for idx = 1:NumHoleCards
            [c cardStr] = strtok(cardStr,[',',' ']); %#ok
            cardStr = cardStr(2:end);
            holeCards(idx) = ParseHoleCard(c);
        end
    end
end

% Converts a single hole card string to a card (allows for 'X')
function holeCard = ParseHoleCard(cardStr)
    if strcmpi(cardStr,'X')
        holeCard = 0;
    else
        holeCard = GetCard(cardStr);
        if (holeCard == 0)
            holeCard = ParseHoleCard(ProcessUserInput(['Card ''' cardStr ''' was invalid. Try entering the card again: ']));
        else
            index = (Deck == holeCard);
            if (sum(index) ~= 1)
                holeCard = ParseHoleCard(ProcessUserInput(['Card ''' cardStr ''' has already been drawn. Try entering the card again: ']));
            else
                Deck(index) = [];
            end
        end
    end
end

% Takes a string like 'AH' and converts it to a card
function card = ParseUserCard(cardStr)
    card = GetCard(cardStr);
    if (card == 0)
        card = ParseUserCard(ProcessUserInput(['Card ''' cardStr ''' was invalid. Try entering the card again: ']));
    else
        index = (Deck == card);
        if (sum(index) ~= 1)
            card = ParseUserCard(ProcessUserInput(['Card ''' cardStr ''' has already been drawn. Try entering the card again: ']));
        else
            Deck(index) = [];
        end
    end
end

% Gets the card associated with a card string
function card = GetCard(cardStr)
    if isempty(cardStr)
        card = 0;
    else
        cardNum = find(strcmpi(Cards,cardStr(1:(end-1))));
        suit = find(strcmpi(Suits,cardStr(end)));
        if ((length(cardNum) ~= 1) || (length(suit) ~= 1))
            card = 0;
        else
            card = suit + (cardNum - 1) * NumSuits;
        end
    end
end

% Gets the card string associated with a card
function cardStr = GetCardString(card)
    if (card == 0)
        cardStr = 'X';
    else
        cardNum = ceil(card / NumSuits);
        suit = card - (cardNum - 1) * NumSuits;
        cardStr = sprintf('%s%s',Cards{cardNum},Suits{suit});
    end
end

% Get the string correpsonding to a given hand
function handStr = GetHandString(hand)
    handStr = '';
    for idx = 1:(length(hand)-1)
        handStr = sprintf('%s%s ',handStr,GetCardString(hand(idx)));
    end
    handStr = sprintf('%s%s',handStr,GetCardString(hand(end)));
end

% Display help information
function DisplayHelp
    fprintf('\n');
    len = length(ProgramName);
    progStr = ['||+--' repmat('-',1,len) '--+||';
               '|||  ' repmat(' ',1,len) '  |||';
               '|||  '    ProgramName    '  |||';
               '|||  ' repmat(' ',1,len) '  |||';
               '||+--' repmat('-',1,len) '--+||'];
    disp(progStr);
    fprintf('\nType ''Cards'' at any prompt to view the valid card symbols\n\n');
    fprintf('Type ''Suits'' at any prompt to view the valid suit symbols\n\n');
    fprintf('Type ''Hands'' at any prompt for a description of the Poker hand hierarchy\n\n');
    fprintf('Type ''Exit'' at any prompt to stop the program\n\n');
    fprintf('Type ''Help'' at any prompt to reprint this information\n\n');
    fprintf(sprintf('%s (e.g., %s%s,%s%s or %s%s %s%s)\n%s (e.g., %s%s X or X,%s%s or X,X)\n', ...
                    'Enter cards in comma/space delimited lists', ...
                    Cards{1}, ...
                    Suits{1}, ...
                    Cards{2}, ...
                    Suits{2}, ...
                    Cards{1}, ...
                    Suits{1}, ...
                    Cards{2}, ...
                    Suits{2}, ...
                    'Use ''X'' to denote an unknown holecard', ...
                    Cards{1}, ...
                    Suits{2}, ...
                    Cards{2}, ...
                    Suits{1}));
end

% Display valid cards
function DisplayCards
    fprintf('\nValid Cards:\n\n');
    for ii = 1:NumCards
        disp(Cards{ii});
    end
end

% Display valid suits
function DisplaySuits
    fprintf('\nValid Suits:\n\n');
    for ii = 1:NumSuits
        disp(Suits{ii});
    end
end

% Display valid poker hands
function DisplayHands
    fprintf('\nValid Poker Hands:\n\n');
    for ii = 1:size(Hands,1)
        disp([Hands{ii,1} ': ' Hands{ii,2}]);
    end
end
%--------------------------------------------------------------------------
end

Contact us