Code covered by the BSD License  

Highlights from
Text Yahtzee

image thumbnail
from Text Yahtzee by Zachary Danziger
Play Yahtzee in the command window, with multiple players per game.

ZYahtzee(np)
function ZYahtzee(np)
% Play text-based Yahtzee!
%
% ZYAHTZEE
% ZYAHTZEE(n), where n is the number of players.
%
% All standard Yahtzee rules apply.
%
% On your turn, you may roll the dice up to 3 times, although you may stop
% and score after your first or second roll. The 1st roll will be done
% automatically and displayed next to the "your roll" field. The 1st roll
% of each turn is of 5 6-sided dice. For your 2nd or 3rd rolls you may keep
% any or all of the dice of your previous roll. Select which rolls to keep
% by entering their die number(s) in the prompt. (You are now no longer
% required to enter spaces when deciding which dice to hold, i.e.,
% entering 125 will hold dice numbers 1, 2, and 5.)
%
% In addition to entering numbers in the prompt, you may enter "all" to
% hold all dice, enter to reroll all dice or "quit" to exit the game.
%
% Once you have held all dice or taken 3 rolls on this turn, you select the
% category to apply the roll to, and earn points accordingly. Your score
% sheet will be updated according to standard Yahtzee rules. You will be
% allowed 13 total turns per game.
%
% Good luck, and good dice.
%
% For more information see the official rules at:
% http://www.hasbro.com/common/instruct/Yahtzee.pdf
%
% Technical Note: You may wish to randomize the seed of MATLAB's random
% number generators prior to playing to avoid games that are the same, or
% even insert a randomization line at the beginning of this code.
%
%
% %%% ZCD June 2010 %%%
%

% Edits (ZCD) July 2010:
%   * fixed spelling error in game board
%   * do not accept bad die number inputs (instead of ignoring them)
%   * added statistics "easter egg"
%   * added multi-player versus capability
%   * automatically score final roll
% Edits (ZCD) August 2010:
%   * fixed improper Yahtzee points allocations in versus mode
%   * adjusted figure location on stats display
% Edits (ZCD) September 2010:
%   * added features to stats bonus display
%   * will save scores to appropriate directory (even when not in that dir)
% Edits (ZCD) February 2011:
%   * there is now no need to include spaces when entering held die numbers


% check inputs
if nargin == 0
    np=1;
elseif ~(np>0 && real(np) && numel(np)==1)
    error('Invalid input for number of players.')
end
% display variable for multiple players
pz = [np 1:np-1];

% set up initial points array
points = ones(13,np)*NaN;
% roll variable
roll = zeros(1,5);

% main game loop runs 13 rounds, with 3 throws per round
for r = 1:13
    % run one round loop per player
    for p = 1:np
    % when playing with multiple players, briefly show the previous
    % player's updated board
    if np>1 && ~(p==1 && r==1)
        clc
        dispPoints(points(:,pz(p)),zeros(1,5),0,pz(p));
        pause(1.5)
    end
        
    % initialize our rerolls variable
    rerolls = 1:5;
    % set up for a bonus yatzee
    bflag = 0; strbonus = '';
    
    for t = 1:3
        % throw the dice
        roll(rerolls) = randi(6,[1 length(rerolls)]);
        % show the user the game state
        dispPoints(points(:,p),roll,t,p);
        
        % --- do special yahtzee things ---
        % check for yahtzee
        fq = hist(roll,1:6);
        if any(fq==5)
            if isnan(points(13,p))
                points(13,p) = 50;
                dispPoints(points(:,p),roll,t,p);
                % be excited about the yahtzee
                disp(' ');disp('YAHTZEE!');disp(' ');pause(3);
                break;
            elseif points(13,p)>0
                points(13,p) = points(13,p)+100;
                dispPoints(points(:,p),roll,t,p);
                disp(' ');
                disp(['BONUS YAHTZEE' repmat('!!',[1 (points(13,p)-50)/100])]);
                disp(' ');pause(3);
                bflag = 1;
                strbonus = 'BONUS ';
                action = 'all';
            elseif points(13,p)==0
                bflag = 1;
                action = 'all';
                disp(' ')
                disp('Seems like you shouldn''t have taken a zero for Yahtzees.')
                disp(' ');pause(3);           
            end
        end        
        % there are special restrictions when the joker scoring
        % for bonus yahtzees are in effect
        if bflag
            % you must fill the top if its available
            if isnan(points(roll(1)))
                points(roll(1),p) = scoreLogic(roll,roll(1));
                break;
            end
        end
        % --- end special yahtzee things ---
        
        
        % obtain the user action
        if t<3 && ~bflag
            keepflag = 1;
            while keepflag
                action = input('Which die numbers do you keep?   ','s');
                % separate each character by a space
                sAction = blanks(2*length(action)); % long row of blanks
                sAction(1:2:end) = action;          % interleave with choies
                % check for bad inputs
                [actstr cf] = str2num(sAction);
                if ( isempty(setdiff(actstr,1:5)) && cf ) || any(strcmp(action,{'','all','quit'}))
                    % accept this input if it is a (legit die # AND we can
                    % do a succesful number conversion on it) OR its a
                    % special word ('' all or quit).
                    keepflag = 0;
                else
                    disp('invalid die number')
                end
            end
        end
        % take user action
        switch action
            case 'all'
                rerolls = [];
            case 'quit'
                return;
            otherwise
                % obtain which die indicies we are to reroll
                % rerolls = setxor(str2num(action),1:5);
                rerolls = setxor(str2num(sAction),1:5);
        end
        
        % get the score placement
        if t==3 || isempty(rerolls) || bflag
            catflag = 1;        % viable category choice
            % make sure this is an appropriate category
            while catflag
                % if this is the final turn, we know the category
                if sum(isnan(points(:,p))) == 1
                    catflag = 0;
                    catg = find(isnan(points(:,p)));
                    points(catg,p) = scoreLogic(roll,catg,bflag);
                    pause(1.5);
                else
                    % ask user for category
                    response = input(['Mark ' strbonus 'score in which category?   '],'s');
                    % the only valid string here is 'quit' which exits the
                    % game now
                    if strcmpi(response,'quit')
                        return;
                    else
                        catg = str2double(response);
                    end
                    % if we are in the right category range and do not yet have
                    % a score for that category we allow the assignment
                    if catg>0 && catg<14 && isnan(points(catg,p))
                        catflag = 0;
                        % get the category score
                        points(catg,p) = scoreLogic(roll,catg,bflag);
                    else
                        disp('invalid category')
                    end
                end
            end
            break;
        end
        
    end % throw loop
    end % player loop
end     % round loop


% --- End game operations ---
clc
gtotal = zeros(1,np);
for p=1:np
    % give final accounting to the player
    gtotal(p) = dispPoints(points(:,p),zeros(1,5),0,p);
end
for p=1:np
    % run score records
    highScores(gtotal(p),points(13,p),[np p])
end
if np>1
    % interleave player no and score
    c = [1:np;gtotal];
    s = sprintf('Player%d: %d,  ',c(:));
    disp(s(1:end-3)); disp(' ')
    disp(['PLAYER ' num2str(find(gtotal==max(gtotal))) ' WINS' repmat('!',[1 np])])
    disp(' ')   
end
% ---------------------------






    function score = scoreLogic(roll,cat,joker)
        score = 0;
        % how many points is this roll worth to this category?
        switch cat
            case {1 2 3 4 5 6}
                score = sum(roll==cat)*cat;
            case 7
                fq = hist(roll,1:6);
                if any(fq>2) || joker, score = sum(roll); end
            case 8
                fq = hist(roll,1:6);
                if any(fq>3) || joker, score = sum(roll); end
            case 9
                fq = hist(roll,1:6);
                if all(diff(sort(fq))==[0 0 0 2 1]) || joker
                    score = 25;
                end
            case 10
                if ~isempty( strfind(diff(unique(roll)),[1 1 1]) ) || joker
                    score = 30;
                end
            case 11
                if ~isempty( strfind(diff(unique(roll)),[1 1 1 1]) ) || joker
                    score = 40;
                end
            case 12
                score = sum(roll);
            case 13
                % case dealt with earlier in code
        end 
    end

    function gtotal = dispPoints(points,roll,t,p)
    % display the current game state
        % clear the command window
        if t~=0 clc; end
        
        % displays current points to the user
        slots = {'1: ones';'2: twos';'3: threes';'4: fours';'5: fives';...
            '6: sixes';'-->bonus (35/63)';'-->Top Subtotal';'7: set';'8: quads';...
            '9: boat';'10: 4-straight';'11: 5-straight';'12: Chance';...
            '13: YAHTZEE';'-->Bottom Subtotal';'-->Grand Total'};
        % top subtotal
        top = nansum(points(1:6));
        % do we have a bonus?
        if top<63, b=0; else b=35; end
        % bottom subtotal
        bot = nansum(points(7:13));
        
        gtotal = top+b+bot;
        
        % concatenate all our points information according to the
        % description in the slots variable
        disp(' ')
        disp(['PLAYER: ' num2str(p)])
        disp([slots,num2cell( [points(1:6);b;top+b;points(7:13);bot;gtotal] )])
        disp(' ')
        if t~=0
            disp(['Roll  ' num2str(t) '/3'])
            fprintf('Your Roll:    %d   %d   %d   %d   %d\n',roll)
            fprintf('Die Number:  (1) (2) (3) (4) (5)\n')
        end
    end



    function highScores(total,yah,np)
        dirFiles = dir(strrep(mfilename('fullpath'),mfilename,''));
        if ~any(strcmp({dirFiles.name},'YahtzeeRecords.mat'))
            scores = {'Player' 'Score' 'Date' 'Yahtzees' 'Rank'};
        else
            load('YahtzeeRecords')
        end
        
        name = inputdlg('Enter Your Name For Record Keeping',['Player ' num2str(np(2))]);
        if isempty(name), return; end
        name = name{1};
        if length(name)>15
            name = name(1:15);
        end
        
        if yah~=0, yah = mod(yah,99)-49; end
        
        scores(end+1,:) = {name total datestr(now,1) yah ''};
        
        sc = scores(2:end,2);
        [B IX] = sort([sc{:}]','descend');
        
        scores(2:end,:) = scores(IX+1,:);
        scores(2:end,end) = num2cell(1:size(scores,1)-1);
        save([strrep(mfilename('fullpath'),mfilename,'') 'YahtzeeRecords'],'scores');
        
        if size(scores,1)>10
            dispLengthTop = 10;
        else
            dispLengthTop = size(scores,1);
        end
        curRank = find(B==total,1,'last');
        if curRank>=dispLengthTop
            dispLengthNow = [curRank-3:curRank+3];
            if ~isempty(find(dispLengthNow==size(scores,1)))
                dispLengthNow = dispLengthNow(1:find(dispLengthNow==size(scores,1)));
            end
        else
            dispLengthNow = [];
        end
        
        disp(' ');disp(' ');
        disp(scores(1:dispLengthTop,:));
        if ~isempty(dispLengthNow)
            disp('     ...............................');
            disp(scores([1 dispLengthNow],:));
        end
        disp(' ');
        
        % occasionally do some interesting stats
        if rand>0.85 && size(scores,1)>5 && np(1)==1
            % print stats to screen
            disp(' '); disp('Your Statistics:'); disp(' ')
            tg = size(scores,1)-1;
            fprintf('Total games played: %g, Average games per day: %g\n\n',...
                [tg, tg/(etime(datevec(max(datenum(scores(2:end,3)))),...
                datevec(min(datenum(scores(2:end,3)))))/(60*60*24))]);
            disp('Correlation between scores and Yahtzees')
            rho = corrcoef([scores{2:end,2}],[scores{2:end,4}]);
            disp(rho(2))
            disp('Correlation between scores and the date')
            rho = corrcoef([scores{2:end,2}],datenum(scores(2:end,3)));
            disp(rho(2))
            disp('Correlation between scores and rank')
            rho = corrcoef([scores{2:end,2}],[scores{2:end,end}]);
            disp(rho(2))
            disp(' ')
            disp(['Mean of scores: ' num2str(mean([scores{2:end,2}]))])
            disp(['Standard deviation of scores: ' num2str(std([scores{2:end,2}]))])
            disp(' ')
            ny = repmat(unique([scores{2:end,4}]),[2 1]);
            eval(sprintf('disp([''Mean for %d Yahtzees: '' num2str( mean( [scores{[false %d==[scores{2:end,4}]],2}] ) )]), ',ny(:)'))
            disp(' ')
            eval(sprintf('disp( [''Best score for %d Yahtzees: '' num2str(max([scores{[false scores{2:end,4}]==%d,2}]))] ), ',ny(:)'))
            disp(' ')
            disp(['Average Yahtzee''s per game: ' num2str(sum([scores{2:end,4}])/scores{end,end})]);            
            disp(' ')            
            
            % print stats to figure
            hh = figure;
            subplot(3,1,1)
            hist([scores{2:end,2}])
            xlabel('Scores'); ylabel('Frequency')
            subplot(3,1,2)
            d = datenum(scores(2:end,3));
            du = unique(d);
            x = zeros(1,length(du)); xs=x;
            for i=1:length(du)
                x(i) = mean([scores{[false; d==du(i)],2}]);
                xs(i) = std([scores{[false; d==du(i)],2}]);
            end
            if any(xs)
                [bds ibds] = max(x(xs~=0)); bdd = du(xs~=0);
                fprintf(' Best day (with more than 1 game played), %s, has mean score of %g\n',datestr(bdd(ibds)),bds)
                [bds ibds] = min(x(xs~=0)); bdd = du(xs~=0);
                fprintf('Worst day (with more than 1 game played), %s, has mean score of %g\n',datestr(bdd(ibds)),bds)
            end
            ln = pinv([ones(length(x),1) (1:length(x))'])*x';
            plot(x,'-.sb','linewidth',2), hold on
            line(1:length(x),ln(1)+(1:length(x)).*ln(2),'color','r')
            set(gca,'Xtick',[])
            xlabel('Increasing Date (days) \rightarrow'); ylabel('Mean Score')
            legend('day score','linear fit','location','best')
            subplot(3,1,3)
            plist = unique(scores(2:end,1));
            x = zeros(1,length(plist));
            for i=1:length(plist)
                tmp = [scores{boolean([0; strcmpi(plist{i},scores(2:end,1))]),2}];
                x(i) = mean(tmp);
                plist{i} = [plist{i} ': ' num2str(length(tmp))];
            end
            [xb IX] = sort(x(1,:),'descend');
            if length(plist)<=6, lp=length(plist); else lp = 6; end
            bar(xb(1:lp),'r'); xlim([0.5 lp+0.5])
            set(gca,{'xtick','fontsize','xticklabel'},{1:lp,8,plist(IX(1:lp))})
            ylabel('Mean Score'); xlabel('Player: Games Played')
            disp(' ');
        end
    end

end

Contact us