Code covered by the BSD License  

Highlights from
Trails

image thumbnail
from Trails by Greg Aloe
Trails is a solitaire card game.

trails(varargin)
function varargout = trails(varargin)
%   TRAILS is a solitaire card game.  All the cards are dealt face-up into
%   four rows of thirteen.  The aces are removed, leaving four empty slots.
%   The object of the game is to re-arrange the cards such that each row
%   starts with a two, and ends with the king of the same suit.  The row
%   would read from two through king, all of the same suit.
%
%   To move a card, click on it.  The only card that can be moved to an
%   empty slot would be the card that follows (in order and suit) the card
%   to the left of the empty slot.  For example, if an empty slot has a 4 of
%   hearts to its left, only the 5 of hearts can be moved to that empty slot.
%
%   For detailed rules, use the Help-->Rules... menu from within the
%   application.
%
%   Greg Aloe
%   Trails - 1.0
%   September 9, 2002

% Copyright 2002, The MathWorks, Inc.

% DEFERRED FEATURES AND TASKS:
% Change structure setting to param/value pairs
% Make waitbar while loading cards, and display the card being loaded
% Add options to change timing of deal, automove, and undo/redo
% Drag and drop cards! (needs to use object's buttondownfcn)
% Allow automove all or undo all to be interrupted.
% Allow ability to save and load
% Put scoring mechanism in?
% Modify UNDO option -- cancels score award, win function, and easter egg
% Allow a redo, but do this before allowing to undo through redeals so that you don't
%   get the chance to get a new redeal if you didn't like it!
%   Don't forget to update the "deals remaining"
% Undeal the cards for undoing through redeals, but keep track of the actual redeal!
% Another easter egg... wam?

if nargin == 0
    
    % initialize the table
    handles = init; 
    handles.ver = '1.0';
    handles.data = 'July 27, 2002';
    guidata(handles.figure,handles);
    
    % start a new game immediately
    newGame(handles)
    
    % Randomize for standalone mode
    rand('state',sum(100*clock));
    
    if nargout
        varargout{1} = handles.figure;
    end
    
elseif ischar(varargin{1}) % INVOKE NAMED SUBFUNCTION OR CALLBACK
    
    if (nargout)
        [varargout{1:nargout}] = feval(varargin{:}); % FEVAL switchyard
    else
        feval(varargin{:}); % FEVAL switchyard
    end
    
end



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  INIT  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function handles = init
% INIT initializes the table by creating all the objects and setting the 
% default values of data passed in the "handles" structure

% Create the figure window

%  First perform calculations to center the window

% Make positions for the cards
cardFormat = '.png';
cardSize = size(imread(['cards/as' cardFormat]));
hspace = 2;
vspace = 5;
virtualCardSize = fliplr(cardSize) + [hspace vspace];
% Find bottom left corner where first card will be placed.
posLeft = 5;
posBottom = 46;
[cardPosX cardPosY] = meshgrid(posLeft:virtualCardSize(1):virtualCardSize(1)*13, ...
    posBottom:virtualCardSize(2):virtualCardSize(2)*4);
[97 73];
screenSize = get(0,'screensize');
boardSize = [(cardSize(2)+hspace)*13-2*hspace+2*posLeft (cardSize(1)+vspace)*4-vspace+posLeft+posBottom];
boardPos = [screenSize(3:4)./2-boardSize./2 boardSize];

% Check that screenSize is greater than boardSize
if any(screenSize(3:4) < boardSize)
    errordlg(sprintf([ ...
            'The card table is larger than the monitor.\n' ...
            '(%ix%i pixels is greater than %ix%i pixels.)\n' ...
            'Try increasing the "Screen area" in the "Display"\n' ...
            'control panel and then restart MATLAB.'], ...
            boardSize,screenSize(3:4)), ...
            'Error starting Trails ...');
    return
end

% Now create the figure
figProp.NumberTitle         = 'off';
figProp.IntegerHandle       = 'off';
figProp.Color               = [0 .6 0];
figProp.HandleVisibility    = 'off';
figProp.Tag                 = 'figure';
figProp.Units               = 'pixels';
figProp.Position            = boardPos;
figProp.Name                = 'Trails';
figProp.Resize              = 'off';
figProp.Menubar             = 'none';
figProp.WindowButtonDownFcn = 'trails(''windowButtonDown'',gcbf)';
figProp.WindowButtonUpFcn   = 'trails(''windowButtonUp'',gcbf)';
figProp.DeleteFcn           = 'trails(''trailsDeleteFcn'',gcbf)';
figProp.DoubleBuffer        = 'on';
fig = figure(figProp);

% set the buttonState application data to 'up'
setappdata(fig,'buttonState','up');

% Make a 'hand' pointer for the figure
setptr(fig,'hand')    

% Graphics
frameProp.Tag               = 'frame';
frameProp.Style             = 'frame';
frameProp.BackgroundColor   = 'b';
frameProp.Position          = [posLeft posBottom-10 boardSize(1)-2*posLeft 5];
frameProp.Parent            = fig;
hframe = uicontrol(frameProp);

% Text    
uiTextProp.Tag              = 'stats';
uiTextProp.Style            = 'text';
uiTextProp.FontWeight       = 'bold';
uiTextProp.BackgroundColor  = get(fig,'color');
uiTextProp.Parent           = fig;
uiTextProp.HorizontalAlign  = 'center';
uiTextProp.String           = {'deals','remaining'};
uiTextProp.Position         = [34 6 60 26];
hstats1 = uicontrol(uiTextProp);

uiTextProp.HorizontalAlign  = 'left';
uiTextProp.String           = ':';
uiTextProp.Position         = [94 6 5 20];
% some other properties are reused from previously created objects
hstats2 = uicontrol(uiTextProp);

uiTextProp.Tag              = 'dealstat';
uiTextProp.String           = '';
uiTextProp.FontSize         = 14;
uiTextProp.Position         = [103 6 25 25];
% some other properties are reused from previously created objects
hdealstat = uicontrol(uiTextProp);

% Buttons
buttonProp.Tag              = 'deal';
buttonProp.String           = 'deal again';
buttonProp.Position         = [192 6 135 25];
buttonProp.Enable           = 'off';
buttonProp.Callback         = 'try,trails(''dealCards'',gcbo,guidata(gcbo));end';
buttonProp.Parent           = fig;
hdeal = uicontrol(buttonProp);

buttonProp.Tag              = 'auto';
buttonProp.String           = 'auto move';
buttonProp.Position(1)      = 338;
buttonProp.Callback         = 'trails(''autoMove'',gcbo,guidata(gcbo),0);';
% some other properties are reused from previously created objects
hauto = uicontrol(buttonProp);

buttonProp.Tag              = 'undo';
buttonProp.String           = 'undo move';
buttonProp.Position(1)      = 776;
buttonProp.Callback         = 'trails(''undoMove'',gcbo,guidata(gcbo),1);';
% some other properties are reused from previously created objects
hundo = uicontrol(buttonProp);

% UIMENUs
menuProp.Label              = '&File';
menuProp.Tag                = 'file';
menuProp.Parent             = fig;
hfile = uimenu(menuProp);

submenuProp.Label           = '&New Game';
submenuProp.Tag             = 'new';
submenuProp.Callback        = 'trails(''newGame'',guidata(gcbo));';
submenuProp.Accelerator     = 'n';
submenuProp.Parent          = hfile;
hnew = uimenu(submenuProp);

submenuProp.Label           = '&Close';
submenuProp.Tag             = 'close';
submenuProp.Callback        = 'closereq';
submenuProp.Separator       = 'on';
submenuProp.Accelerator     = 'w';
% some other properties are reused from previously created objects
hclose = uimenu(submenuProp);

menuProp.Label              = '&Options';
menuProp.tag                = 'options';    
% some other properties are reused from previously created objects
hoptions = uimenu(menuProp);

submenuProp.Label           = 'Show &eligible moves';
submenuProp.Tag             = 'eligible';
submenuProp.Accelerator     = 'e';
submenuProp.Checked         = 'on';
submenuProp.Callback        = 'trails(''eligibleOption'',gcbo,guidata(gcbo));';
submenuProp.Parent          = hoptions;
submenuProp.Separator       = 'off';
heligible = uimenu(submenuProp);

submenuProp.Label           = 'Auto &move';
submenuProp.Tag             = 'auto';
submenuProp.Accelerator     = 'm';
submenuProp.Checked         = 'off';
submenuProp.Callback        = 'trails(''autoMove'',gcbo,guidata(gcbo),0);';
submenuProp.Separator       = 'on';
% some other properties are reused from previously created objects
hautom = uimenu(submenuProp);

submenuProp.Label           = 'Auto move a&ll';
submenuProp.Accelerator     = 'l';
submenuProp.Callback        = 'trails(''autoMove'',gcbo,guidata(gcbo),1);';
submenuProp.Separator       = 'off';
% some other properties are reused from previously created objects
hautoall = uimenu(submenuProp);

submenuProp.Label           = '&Undo';
submenuProp.Tag             = 'undo';
submenuProp.Accelerator     = 'z';
submenuProp.Callback        = 'trails(''undoMove'',gcbo,guidata(gcbo),1);';
submenuProp.Separator       = 'on';
% some other properties are reused from previously created objects
hundom = uimenu(submenuProp);

submenuProp.Label           = 'Undo &all';
submenuProp.Accelerator     = 'a';
submenuProp.Callback        = 'trails(''undoMove'',gcbo,guidata(gcbo),inf);';
submenuProp.Separator       = 'off';    
% some other properties are reused from previously created objects
hundoall = uimenu(submenuProp);

submenuProp.Label           = 'Match color';
submenuProp.Tag             = 'matchColorSwitch';
submenuProp.Separator       = 'on';
submenuProp.Visible         = 'off';
submenuProp.Accelerator     = '';
submenuProp.Callback        = 'trails(''matchGame'',gcbo,guidata(gcbo),''matchColorSwitch'');';
% some other properties are reused from previously created objects
hmatchcolor = uimenu(submenuProp);

menuProp.Label              = '&Help';
menuProp.tag                = 'help';    
% some other properties are reused from previously created objects
hhelp = uimenu(menuProp);

submenuProp.Label           = '&Rules...';
submenuProp.Tag             = 'rules';
submenuProp.Separator       = 'off';
submenuProp.Visible         = 'on';
submenuProp.Callback        = 'trails(''rulesDlg'',guidata(gcbo));';
submenuProp.Parent          = hhelp;    
% some other properties are reused from previously created objects
hrules = uimenu(submenuProp);

submenuProp.Label           = '&About Trails...';
submenuProp.Tag             = 'about';
submenuProp.Callback        = 'trails(''aboutDlg'',guidata(gcbo));';
submenuProp.Separator       = 'on';
% some other properties are reused from previously created objects    
habout = uimenu(submenuProp);

% update graphics so they display during loading of cards
% show some temporary text to inform player that cards are being loaded
tempAxes = axes('parent',fig,'units','normalized','position',[0 0 1 1],'visible','off');
tempText = text('units','normalized','position',[0.5 0.5], ...
    'string','loading cards ...','horizontalalignment','center', ...
    'parent',tempAxes,'fontsize',20);
drawnow

% Make the deck
deckValues = [101:113; ...    % clubs
              201:213; ...    % diamonds
              301:313; ...    % hearts
              401:413]';      % spades    

% Make the table
deckValuesCell = num2cell(deckValues);
deck = struct('value',deckValuesCell);
[table(1:13,1:4).card] = deal(deck(:).value);
% status:   0 = empty
%           1 = occupied
%           2 = occupied with correct card
%           3 = occupied with card eligible to move
%           4 = special (initiate match game callback)
% card:     Cards are initialized with ace through king, but will get shuffled before the game starts          

% Make the frames and cards
framePosMatrix = [cardPosX(:) cardPosY(:) cardSize(2)*ones(52,1) cardSize(1)*ones(52,1)]';
framePosMatrix = reshape(framePosMatrix(:),4*4,13)';
% Convert framePosMatrix into cells of positions for each card
% could use MAT2CELL, but this isn't in core MATLAB until R13
framePosCell = cell(13,4);
rowStart = 0;
for i=1:13
    colStart = 17;
    for j=1:4
        framePosCell{i,j} = framePosMatrix(rowStart+1,colStart-[4:-1:1]);
        colStart = colStart - 4;
    end
    rowStart = rowStart + 1;
end
cardValues = {'a','2','3','4','5','6','7','8','9','t','j','q','k'};
cardSuits = {'c','d','h','s'};
% Define the RGB color shifts to store the different card highlights
redShift  = cat(3, zeros(cardSize),   -70*ones(cardSize), -70*ones(cardSize));
blueShift = cat(3,-70*ones(cardSize), -70*ones(cardSize),  zeros(cardSize));
for n=1:4
    for m=1:13
        % Read each card and store the handles
        [I,map]=imread(['cards/' cardValues{m} cardSuits{n} cardFormat]);
        % store all the card skins to the card's userdata
        cardCDataDouble= 255*ind2rgb(I,map);
        cardCData.base = uint8(cardCDataDouble);
        cardCData.reddish = uint8(cardCDataDouble+redShift);
        cardCData.blueish = uint8(cardCDataDouble+blueShift);
        cardCData.color = 'base';
        deck(m,n).handle = uicontrol('tag','card','parent',fig,'visible','off', ...
            'units','pixels','enable','off','cdata',cardCData.base, ...
            'buttondownfcn','trails(''makeMove'',gcbo,guidata(gcbo))','userdata',cardCData);
    end
end

% delete the text and parent axes that says "loading cards..."
delete([tempAxes; tempText])

% Create and update 'handles' structure
handles = guihandles(fig);

% store some data to the handles structure
handles.framePosCell = framePosCell;
handles.table = table;
handles.deck = deck;
handles.cardSize = cardSize;

% Store a blank card and the card back to the handles structure
[I,map] = imread(['cards/back' cardFormat]);
handles.back = uint8(255*ind2rgb(I,map));
[I,map] = imread(['cards/blank' cardFormat]);
handles.blank = uint8(255*ind2rgb(I,map));
handles.blankBlueish = uint8(double(handles.blank)+blueShift);

% Initialize some states
setappdata(fig,'gameOverState',0)
setappdata(fig,'newGameAttempted',0)
setappdata(fig,'movingState',0)
setappdata(fig,'autoMovingState',0)
setappdata(fig,'flashingState',0)
setappdata(fig,'undoMoveState',0)


% define some application constants and variables
handles.dealDelay = .03;       % delay between cards        
handles.maxDeals = 4;
guidata(fig,handles);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  NEWGAME  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function newGame(handles)

if getappdata(handles.figure,'gameOverState')
    setappdata(handles.figure,'gameOverState',0)
    setappdata(handles.figure,'newGameAttempted',1)
    return
end

set(handles.card,'hittest','on')
setappdata(handles.figure,'newGameAttempted',0)

% In case the stat words or dealstat buttondownfcn have changed , change them back
set(findobj(handles.figure,'string',{'matches','remaining'}),'string',{'deals','remaining'});
set(handles.dealstat,'buttondownfcn','');
set(handles.matchColorSwitch,'visible','off')

% If GAMEOVER axes exists, delete it
try
    delete(findobj(handles.figure,'tag','gameOverAxes'))
end

% Initialize new game
handles.dealNumber = 1;

set(handles.new,'enable','off');

% Reset all eligible to base color
showEligibleCards(handles,[handles.deck(:).handle],0)

% Initialize keeping track of each move
%   first column:   handle to swap card (this would be an "empty location")
%   second column:  handle to chosen card
%   third column:   value of swap card
%   fourth column:  value of chosen card
handles.moves = [];

% Start will all "occupied with incorrect card" value of one since this is the first deal.
% Make aces zero so that they do not get picked up when the first deal looks for status value of one
statusMatrix = ones(13,4);
aces = find(rem([handles.table(:).card],100) == 1);
statusMatrix(aces) = 0;

statusCell = num2cell(statusMatrix);
[handles.table(:).status] = deal(statusCell{:});

% Set handles.oldTableStatus as all zeros to guarantee no free deal on first deal
handles.oldTableStatus = zeros(1,52);

guidata(handles.figure,handles)

try
    dealCards(handles.deal,handles);
    set(handles.new,'enable','on');
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  DEALCARDS  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function dealCards(h,handles)

setappdata(handles.figure,'dealingState',1)

set(h,'enable','off')

% don't allow automove during deal
set(handles.auto,'enable','off')

% Clear all undo's in this version.  Cannot undo after a deal!
set(handles.undo,'enable','off')
handles.moves = [];

% If occupied-with-correct-card status has changed, then increase dealNumber
if ~isequal([handles.oldTableStatus]==2,[handles.table(:).status]==2)
    handles.dealNumber = handles.dealNumber + 1;
    handles.oldTableStatus = [handles.table.status];
end
remDeals = handles.maxDeals - handles.dealNumber;
if remDeals == 0
    dealStatColor = [.75 0 0];
else
    dealStatColor = 'black';
end
set(handles.dealstat,'string',num2str(remDeals))
set([handles.dealstat,handles.stats],'foregroundcolor',dealStatColor)

% Gather the incorrect cards and redeal
tableStatus = [handles.table(:).status];

% Find spaces occupied with incorrect card so that we can deal to them.  This does not include the aces.
spaces2deal = find(tableStatus == 1);

% Gather the cards in the spaces to deal to
cards2deal = [handles.table(spaces2deal).card];

% Replace aces
%     First add empty locations to the spaces to deal
spaces2deal = sort([spaces2deal find(tableStatus == 0)]);
%     Then add the aces to the cards to deal.
cards2deal = [cards2deal cardsuit2value(1,1:4)];
    
% Perform the actual shuffle!
shuffledDeck = cards2deal(randperm(length(cards2deal)));

% Turn off visibility of cards2deal
ind = value2cardsuit(shuffledDeck);
set([handles.deck(ind).handle],'visible','off')

% Deal the shuffled deck to the required spaces
shuffledDeckCell = num2cell(shuffledDeck);
[handles.table(spaces2deal).card] = deal(shuffledDeckCell{:});

% Graphics: set positions of each card in the shuffled deck
for n=1:length(shuffledDeck)
    ind = value2cardsuit(shuffledDeck(n));
    set([handles.deck(ind).handle],'visible','on','position',handles.framePosCell{spaces2deal(n)});
    pause(handles.dealDelay)
end

% Finally remove the aces by making them invisible, and setting statuses to empty (0)
aces = [handles.deck(1,:).handle];
for n=aces
    set(n,'visible','off')
    pause(handles.dealDelay*5)
end

% Update table status
handles.table = updateTableStatus(handles);
% Update GUI data
guidata(handles.figure,handles)

% allow automoves now that redeal is complete
set(handles.auto,'enable','on')

setappdata(handles.figure,'dealingState',0)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  MAKEMOVE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function makeMove(cardHandle,handles)

% If already moving, or if autoMoving and a card-click caused this
% callback, then don't allow the makeMove
if getappdata(handles.figure,'movingState') ...
       | (getappdata(handles.figure,'autoMovingState') & strcmp(get(gcbo,'tag'),'card'))
    return
end

setappdata(handles.figure,'movingState',1)

% Get status of card position
cardDeckIndex = find([handles.deck(:).handle]==cardHandle);
cardValue = handles.deck(cardDeckIndex).value;
cardTableIndex = find([handles.table(:).card]==cardValue);
cardStatus = handles.table(cardTableIndex).status;

switch get(handles.figure,'selectiontype')
case 'alt'
    %% This would be selected if we want to flash the card in the spot that the clicked card wants to move
    % If the selected card is not a 2, and the game is not beat
    if ~(rem(cardValue,100)==2) & ~check4win(handles.table)
        prevCardSuitTableIndex = find([handles.table(:).card]==(cardValue-1));
        % If the card to be flashed is not in the last column
        if rem(prevCardSuitTableIndex,13)~=0
            flashCardHandle = handles.deck(value2cardsuit((handles.table(prevCardSuitTableIndex+1).card))).handle;
            flash(flashCardHandle,handles)
        end
    % If the selected card is a 2, and the game is not beat
    elseif (rem(cardValue,100)==2) & ~check4win(handles.table)
        flashCardHandles = [handles.deck(value2cardsuit([handles.table(1,:).card])).handle];
        twoHandle = handles.deck(find([handles.deck.handle]==gcbo)).handle;
        flashCardHandles(find(flashCardHandles==twoHandle)) = [];
        flash(flashCardHandles,handles)
    end
case 'normal'
    if cardStatus == 3  
        % occupied with card eligible to move 
        % this would be selected for simply moving the card
        
        % Since a call to this case guarantees a move to be made, enable the undo button
        if ~getappdata(handles.figure,'autoMovingState')
            set(handles.undo,'enable','on')
        end
        
        % Reset the card color
        oldEligible = [handles.deck(value2cardsuit([handles.table(find([handles.table(:).status]==3)).card])).handle];
        showEligibleCards(handles,oldEligible,0)
        
        % If the card is a 2, put it in the first available unoccupied 2 position (first row of 'table' variable)
        %     Therefore, we need this alternate method for finding the swap card table index
        if rem(cardValue,100)==2
            % Find all unoccupied 2 positions
            emptyTwoTableCols = find([handles.table(1,:).status]==0);
            emptyTwoTableIndices = 13*(emptyTwoTableCols-1)+1;
            
            % Find next unoccupied 2 index directly after the call back card
            %     so that multiple clicks on this 2 will cycle through the unoccupied 2's
            indices = sort([emptyTwoTableIndices cardTableIndex]);
            indicesIndex = 1+find(indices==cardTableIndex);
            if indicesIndex > length(indices)
                indicesIndex = 1;
            end
            swapCardTableIndex = indices(indicesIndex);
            
            % If the selected card is a 2, and it is in a 2-position, 
            %   set the status of all following cards as occupied (1), not with correct card
            if any(cardTableIndex == [1:13:52])
                [handles.table(cardTableIndex:cardTableIndex+12).status] = deal(1);
            end
            
        else  % If not a 2, use this method to find the swap card table index
            % Find lead card information
            leadCardValue = cardValue - 1;
            leadCardTableIndex = find([handles.table(:).card]==leadCardValue);
            
            % Find swap card information
            swapCardTableIndex = leadCardTableIndex + 1;    % Swap card will be an ace
        end
        swapCardValue = handles.table(swapCardTableIndex).card;
        swapCardDeckIndex = value2cardsuit(swapCardValue);
        swapCardHandle = handles.deck(swapCardDeckIndex).handle;
        
        % Swap the card values and positions
        handles = swap(handles,cardHandle,swapCardHandle,cardValue,swapCardValue);
        
        % Keep track of this move
        handles.moves = [handles.moves; [swapCardHandle cardHandle swapCardValue cardValue]];
        
        % Update table status
        handles.table = updateTableStatus(handles);  

    elseif cardStatus == 4
        % This is a special situation when the MATCHGAME has been initiated
        if ~handles.matchChecking
            handles = matchGame(cardHandle,handles,'cardClicked');
        end
    end
end

if ~getappdata(handles.figure,'gameOverState')
    guidata(cardHandle,handles)
else
    gameOver(handles.figure,handles)
end

setappdata(handles.figure,'movingState',0)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  UPDATETABLESTATUS  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% status:   0 = empty
%           1 = occupied
%           2 = occupied with correct card
%           3 = occupied with card eligible to move
function newTable = updateTableStatus(handles)
deck = handles.deck;
fig = handles.figure;
%%%% 1 %%%%
% If not occupied with correct card from last situation, then set to occupied in new deal.
incorrect = find([handles.table(:).status] ~= 2);
[handles.table(incorrect).status] = deal(1);

%%%% 0 %%%%
% Set aces as empty
aces = find(rem([handles.table(:).card],100) == 1);
[handles.table(aces).status] = deal(0);

%%%% 2 %%%%
% Find each suit's rightmost correctly occupied card, update if necessary
for n=1:4
    % If no correct cards in suit row, check for a 2 in the correct spot
    lastCorrectCard = [];
    if rem([handles.table(1,n).card],100)==2
        handles.table(1,n).status = 2;
        lastCorrectCard = 1;
    end
    % If there is now a lastCorrectCard, update suit row while next card is correct
    if lastCorrectCard 
        while diff([handles.table(lastCorrectCard:lastCorrectCard+1,n).card]) == 1
            lastCorrectCard = lastCorrectCard + 1;
            handles.table(lastCorrectCard,n).status = 2;
        end
    end
end

%%%% 3 %%%%
% Calculate leaders (card before empty space)
%    Use ace locations calculated before, but now it is easier if we convert to subscript form
%    leadRow is the card
%    leadCol is the suit
[leadRow, leadCol] = ind2sub([13 4],aces);
leadRow = leadRow - 1;

% Initialize eligible cards
eligibleCards = [];

% Make 2's lead adRow is zero
leadRowIsZero = find(leadRow==0);
if ~isempty(leadRowIsZero)
    twos = find(rem([handles.table(:).card],100) == 2);
    [handles.table(twos).status] = deal(3);
    eligibleCards = cardsuit2value(2,1:4);
    % Remove the lead rows that are zero, since all the twos are now eligible and accounted for this
    leadRow(leadRowIsZero) = [];
    leadCol(leadRowIsZero) = [];
end

% Find cards following leader, and set status to eligible
%    Get leader indices, which are easier to work with.  We needed subscripts before to check leadCol==0 more easily.
leadInd = sub2ind([13 4],leadRow,leadCol);
%    If a leader has empty status, then remove it from leaders
%    This situation happens when there are two empty slots (dealt aces) in a row, 
%         since leaders are calculated from empty slots
leadInd(find([handles.table(leadInd).status]==0))=[];
%    Get eligible indices
eligibleCards = [eligibleCards [handles.table(leadInd).card]+1];
%    If an eligible card exceeds the king, remove it from eligible cards
eligibleCards(find(rem(eligibleCards,100) > 13)) = [];
% Find eligible spaces
for n=1:length(eligibleCards)
    eligibleSpaces(n) = find([handles.table(:).card]==eligibleCards(n));
end

% Find eligible card handles if there are any
if eligibleCards
    ind = value2cardsuit(eligibleCards);
    eligibleCardHandles = [deck(ind).handle];

    [handles.table(eligibleSpaces).status] = deal(3);

    % Reset card to eligible color
    showEligibleCards(handles,eligibleCardHandles,1);
    
else
    set([handles.undo handles.auto],'enable','off')
    if check4win(handles.table)
        guidata(fig,handles)
        victory(fig,handles)
    elseif handles.dealNumber < handles.maxDeals    % Allow option to redeal cards by enabling the button
        flash(handles.deal,handles)
        pause(0.3)
        flash(handles.deal,handles)
        pause(0.3)
        flash(handles.deal,handles)
        set(handles.deal,'enable','on')
        set(handles.auto,'enable','on')
        if ~isempty(handles.moves)
            set(handles.undo,'enable','on')
        end
    else
        % Completed maximum number of deals --> end the game
        guidata(fig,handles)
        setappdata(fig,'gameOverState',1)
    end 
end

newTable = handles.table;
drawnow


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  CHECK4WIN  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function state = check4win(table)
% CHECK4WIN checks to see if the game has been beaten

% If all 48 cards (not the aces) are in the correct place (status==2), game is beaten
status = [table.status];
state = length(find(status==2))==48;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  VALUE2CARDSUIT  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [card,suit] = value2cardsuit(value)
% Convert value (x01:x13, x=1:4) to associated suit (1:4) and card (1:13)
% If nargout==1, convert to index
if ~isempty(value)
    suit = floor(value/100);
    card = rem(value,100);
    if nargout<=1
        card = sub2ind([13 4],card,suit);
    end
else 
    card = [];
    suit = [];
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  CARDSUIT2VALUE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function value = cardsuit2value(card,suit)
% Convert suit (1:4) and card (1:13) to associated value (x01:x13, x=1:4)
value = suit*100 + card;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  FLASH  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function flash(h,handles)
% FLASH flashes the deal button, or flashes a card in a tint of blue
% Function assumes that all the handles in "h" have the same tag
% Code works for a vector of card handles

% Put in TRY block to ignore error if figure is closed during a flash
try
    switch get(h(1),'tag')
        case 'deal'
            setappdata(handles.figure,'flashingState',1)
            color = get(h,'backgroundcolor');
            flashColor = [1 0.1 0.1];
            set(h,'backgroundcolor',flashColor)
            pause(0.3)
            set(h,'backgroundcolor',color)
            setappdata(handles.figure,'flashingState',0)
        case 'card'
            % If we aren't in some certain states...
            if ~getappdata(handles.figure,'autoMovingState') ...
                    & ~getappdata(handles.figure,'dealingState') ...
                    & ~getappdata(handles.figure,'flashingState') ...
                    & ~getappdata(handles.figure,'undoMoveState')
                
                setappdata(handles.figure,'flashingState',1)
                
                % If the flash card is invisible (an ace), make it visible
                %   We will force it to be blankBlueish now
                hinvis = findobj(h,'visible','off');
                setCardColor(hinvis,'blankBlueish')
                set(hinvis,'visible','on')
                
                for n=1:length(h)
                    currentColor{n} = getCardColor(h(n));
                    % If invisible, then we don't need to change the color
                    if ~any(h(n)==hinvis)
                        setCardColor(h(n),'blueish');
                    end
                end
                pause(0.3)
                while strcmp(getappdata(gcbf,'buttonState'),'down')
                    drawnow
                end
                for n=1:length(h)
                    setCardColor(h(n),currentColor{n});
                end
                drawnow

                % Change invisible cards back to invisible
                setCardColor(hinvis,'base')
                set(hinvis,'visible','off')
                
                setappdata(handles.figure,'flashingState',0)
            end
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  VICTORY  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function victory(fig,handles)

% Put in TRY block to suppress errors if figure is closed
try
    % Disable the game controls
    set([handles.deal, handles.auto, handles.undo],'enable','off');
    drawnow
    
    % Put the aces with their respective suits
    for n=13:13:39
        if diff(floor([handles.table(n-1:n).card]./100))
            goodV = 100*floor(handles.table(n-1).card/100) + 1;
            badV  = 100*floor(handles.table(n).card/100) + 1;
            handles = swap(handles, ...
                handles.deck(1,floor(goodV/100)).handle, ...        % The correct ace
                handles.deck(1,floor(badV/100)).handle, ...         % The misplaced ace
                goodV, ...
                badV);
        end
    end
    guidata(fig,handles)
    
    % Random coloring of cards until the mouse is clicked
    set([handles.card],'visible','on')
    setappdata(fig,'buttonState','up')
    while strcmp('up',getappdata(fig,'buttonState'))
        h = handles.card(floor(52*rand)+1);
        c = floor(rand*4)+1;
        if c==1
            setCardColor(h,'blueish')
        elseif c==2
            setCardColor(h,'reddish')
        elseif c==3
            setCardColor(h,'base')
        elseif c==4
            setCardColor(h,'back')
        end
        pause(.01)
    end
    
    % ... Then color the word "win" across the table, using the cards
    setCardColor([handles.card],'reddish');
    winInd = [1 5 7 9 12 14 18 20 22 23 25 27 29 31 33 35 37 38 41 43 46 48 51];
    indHandles = 0*winInd;
    for n=1:length(winInd)
        indHandles(n) = handles.deck(find([handles.deck.value]==handles.table(winInd(n)).card)).handle;
    end
    setCardColor(indHandles,'blankBlueish');
    
    % Make the match game accessible
    set(handles.dealstat,'enable','inactive', ...
        'buttondownfcn','trails(''matchGame'',gcbo,guidata(gcbo));');
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  GAMEOVER  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function gameOver(fig,handles)
% Game over graphics
% Remove cards one by one

setCardColor([handles.card],'reddish')
set(handles.new,'enable','on')
set(handles.card,'hittest','off')
setappdata(fig,'gameOverState',1)
pause(0.3)
waitfor(fig,'applicationdata')

try
    set([handles.undo, handles.auto],'enable','off')
    
    hcards = [handles.deck.handle];
    hcards = hcards(randperm(length(hcards)));
    for n=hcards
        % If we're not in gameOverState anymore, break out of the TRY/CATCH block
        if ~getappdata(fig,'gameOverState')
            error('dummy')
        end
        set(n,'visible','off')
        pause(0.05)
    end

    hax = axes('parent',handles.figure,'tag','gameOverAxes','visible','off', ...
        'units','normalized','position',[0 0 1 1]);
    handles.gameOverAxes = hax;
    ht = text('string','Game Over','parent',hax,'horizontalalignment','center', ...
        'fontsize',40,'color',[.7 0 .2],'fontweight','bold');
    
    for t=0:0.1:15*pi
        % If we're not in gameOverState anymore, break out of the TRY/CATCH block
        if ~getappdata(fig,'gameOverState')
            error('dummy')
        end
        set(ht,'position',[0.5 0.5*cos(t)*exp(-.1*t)+0.5 0])
        pause(0.02)
    end
end

if ~getappdata(fig,'autoMovingState') & getappdata(fig,'newGameAttempted')
    newGame(handles)
end

setappdata(fig,'gameOverState',0)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  ELIGIBLEOPTION  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function eligibleOption(h,handles)
% Toggle check mark if UIMENU is selected
% Brighten or darken as required

% Find eligible cards, set UIMENU check mark, and show or hide eligible cards
eligibleCards = [handles.deck(value2cardsuit([handles.table(find([handles.table(:).status]==3)).card])).handle];
switch get(h,'checked')
case 'off'
    set(h,'checked','on')
    showEligibleCards(handles,eligibleCards,1);
case 'on'
    set(h,'checked','off')
    showEligibleCards(handles,eligibleCards,0);
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  SHOWELIGIBLECARDS  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function showEligibleCards(handles,cardHandles,newLum)
% Sets cardHandles to show eligiblity (if the option is selected)
% newLum is 0 to shut eligibility off (unset), 1 to turn it on (set)

switch newLum
case 0
    % unset
    setCardColor(cardHandles,'base');
case 1
    % set if eligibleOption allows it
    if strcmp(get(handles.eligible,'checked'),'on')
        setCardColor(cardHandles,'reddish');
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  GETCARDCOLOR  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function color = getCardColor(cardHandle)
color = getfield(get(cardHandle,'userdata'),'color');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  SETCARDCOLOR  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function setCardColor(cardHandles,color)

if isempty(cardHandles)
    return
end

switch color
case {'back','blank','blankBlueish'}
    handles = guidata(cardHandles(1));
    for n=1:length(cardHandles)
        ud = get(cardHandles(n),'userdata');
        ud.color = color;
        set(cardHandles(n),'cdata',getfield(handles,color),'userdata',ud);
    end
otherwise
    for n=1:length(cardHandles)
        ud = get(cardHandles(n),'userdata');
        ud.color = color;
        set(cardHandles(n),'cdata',getfield(ud,color),'userdata',ud);
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  SWAP  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function handles = swap(handles,cardHandle,swapCardHandle,cardValue,swapCardValue)
% Swap positions
cardPosition = get(cardHandle,'position');
set(cardHandle,'position',get(swapCardHandle,'position'));
set(swapCardHandle,'position',cardPosition);
% Swap table values
cardTableIndex = find([handles.table(:).card]==cardValue);
handles.table(find([handles.table(:).card]==swapCardValue)).card = cardValue;
handles.table(cardTableIndex).card = swapCardValue;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  AUTOMOVE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function autoMove(h,handles,allOption)
% AUTOMOVE a card, or all cards if allOption == 1

% Put in TRY block to ignore error if figure is closed during an automove
try
    if movesAreAvailable(handles)
        setappdata(handles.figure,'autoMovingState',1);
        
        % disable undo/automove while automoving
        set(handles.undo,'enable','off')
        set(handles.auto,'enable','off')
        set(handles.eligible,'enable','off')
        set(handles.new,'enable','off')
        
        while movesAreAvailable(handles)
            eligible = find([handles.table(:).status] == 3);    
            card = handles.table(eligible(ceil(length(eligible)*rand))).card;
            cardHandle = handles.deck(value2cardsuit(card)).handle;
            
            % Set figure's 'selectiontype' to 'normal' so that a left-click is
            % simulated, in case the automove was triggered by keyboard and not mouse
            set(handles.figure,'selectiontype','normal')
            makeMove(cardHandle,handles);
            
            handles = guidata(cardHandle);
            % only move once if not moving all or break if a newGame was attempted
            if ~allOption | getappdata(handles.figure,'newGameAttempted')
                break
            end
            pause(0.05)
        end
        
        setappdata(handles.figure,'autoMovingState',0);
        
        % reenable undo and automove
        if ~check4win(handles.table) ...
                & ~getappdata(handles.figure,'gameOverState') ...
                & isempty(findobj(handles.figure,'tag','gameOverAxes'))
            set(handles.undo,'enable','on')
            set(handles.auto,'enable','on')
        end
        set(handles.eligible,'enable','on')
        set(handles.new,'enable','on')
        % if no more moves to undo, don't allow the undo option
        if isempty(handles.moves)
            set(handles.undo,'enable','off')
        end
    end
    
    if getappdata(handles.figure,'newGameAttempted')
        newGame(handles)
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  UNDOMOVE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function undoMove(h,handles,count)
% UNDOMOVE undoes COUNT moves

setappdata(handles.figure,'undoMoveState',1)

% Put in TRY block to ignore error if figure is closed during an undo
try
    % don't allow a redeal since we have now allowed a move
    set(handles.deal,'enable','off')
    
    % disable automove and undo while undoing
    set(handles.auto,'enable','off')
    set(handles.undo,'enable','off')
    set(handles.eligible,'enable','off')
    
    % If called as UNDOMOVE(H,HANDLES,inf), then undo all.
    if isinf(count)
        count=size(handles.moves,1);
    end
    
    for n=1:count
        % form the swap data from the moves data
        swapData = num2cell(handles.moves(end,:));
        handles.moves(end,:) = [];
        handles = swap(handles,swapData{:});
        % discolor the eligible cards, correct coloring will happen in updateTableStatus
        oldEligible = [handles.deck(value2cardsuit([handles.table(find([handles.table(:).status]==3)).card])).handle];
        showEligibleCards(handles,oldEligible,0)
        
        handles.table = updateTableStatus(handles);
        drawnow
    end
    
    guidata(h,handles)
    
    % reenable autmove
    set(handles.auto,'enable','on')
    set(handles.eligible,'enable','on')
    
    % if no more moves to undo, don't allow the undo option, else allow it
    if isempty(handles.moves)
        set(handles.undo,'enable','off')
    else
        set(handles.undo,'enable','on')
    end
end

setappdata(handles.figure,'undoMoveState',0)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  WINDOWBUTTONDOWN  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function windowButtonDown(fig)
setappdata(fig,'buttonState','down');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  WINDOWBUTTONUP  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function windowButtonUp(fig)
setappdata(fig,'buttonState','up');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  MOVESAREAVAILABLE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function state = movesAreAvailable(handles)
eligible = find([handles.table(:).status] == 3);
state = ~isempty(eligible);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  RULESDLG  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function rulesDlg(handles)
% Open dialog box with rules

% First check to see if the dialog is open already
if ~isappdata(handles.figure,'rulesDlg') | ~ishandle(getappdata(handles.figure,'rulesDlg'))

    % Create dialog figure
    boardPos = get(handles.figure,'position');
    
    figProp.Name            = 'Rules...';
    figProp.Tag             = 'rulesDlg';
    figProp.NumberTitle     = 'off';
    figProp.Menubar         = 'none';
    figProp.Resize          = 'off';
    figProp.DoubleBuffer    = 'on';
    figProp.HandleVisibility= 'off';
    figProp.IntegerHandle   = 'off';
    figProp.Position        = boardPos + [boardPos(3)/2+15 -15 -boardPos(3)/2 0];
    hdlg = figure(figProp);
    
    % Store dialog figure handle to the main figure's appdata
    setappdata(handles.figure,'rulesDlg',hdlg);
    
    % Start creating the Axes and Text
    hax = axes('units','normalized','visible','off', ...
        'position',[0 0 1 1],'parent',hdlg);
    
    tcolor = [.7 0 .2];
    
    % Create the strings for the rulesDlg pages
    basic1 = ...
        {'{\bf\fontsize{16}The Basic Rules:}', ...
         '', ...
         '{\bfThe Objective:}', ...
         'Trails is a solitaire card game.  All the cards are dealt face-up into', ...
         'four rows of thirteen.  The aces are removed, leaving four empty slots.', ...
         'The object of the game is to re-arrange the cards such that each row', ...
         'starts with a two, and ends with the king of the same suit.  The row', ...
         'would read from two through king, all of the same suit.', ...
         '', ...
         '{\bfMoving Cards:}', ...
         'To move a card, click on it.  The only card that can be moved to an', ... 
         'empty slot would be the card that follows (in order and suit) the card', ...
         'to the left of the empty slot.  For example, if an empty slot has a 4 of', ...
         'hearts to its left, only the 5 of hearts can be moved to that empty slot.', ...
         '', ...
         '{\bfBuilding a Foundation:}', ...
         'When the first column of cards has an empty slot, any 2 can be moved', ...
         'there, even if that 2 has already been moved.  (If there is more than 1', ...
         'empty slot in the first column, and you want to a move a 2 into the', ...
         '2nd empty slot, then you will need to click on it, follow it, and click', ...
         'on it again.)  When a 2 is moved into place, it starts a foundation.', ...
         'Any card that is in place {\bfand} next to a card in a foundation is also', ...
         'said to be in that foundation.'};
 
    basic2 = ...
        {'{\bf\fontsize{16}The Basic Rules, continued:}', ...
         '', ...
         '{\bfDeals Remaining:}', ...
         'When an empty slot lays to the right-hand side of a king, or to right-', ...
         'hand side of another empty slot, nothing can be moved into it.  If all', ...
         '4 empty slots lay in these positions, then the deal is over.  When this', ...
         'this happens, you have 3 redeals available by clicking the "deal again"', ...
         'button.  Note that if no cards are advanced to a foundation during a deal,', ...
         'then you receive an extra redeal.', ...
         '', ...
         '{\bfEnding the Game:}', ...
         'You lose the game when', ...
         '     - all the empty slots are to the right of kings and other empty slots',...
         '     - there are 0 deals remaining', ...
         '     - not all of the cards are in the foundations.', ...
         'You beat the game if all of the cards are in the foundations.'};

    options =  ...
        {'{\bf\fontsize{16}Other Options and Features:}', ...
         '', ...
         '{\bfShowing Eligible Cards:}', ...
         'When the "Show eligible moves" option is checked, the cards that are', ...
         'eligible to move are highlighted in red.', ...
         '', ...
         '{\bfLocating a "Bump" Card:}', ...
         'An object card''s "bump" card is the card that prevents that object card', ...
         'from being moved, since it occupies the slot needed to move that object', ...
         'card.  When you wish to move any object card, a right-click on it will', ...
         'flash a blue highlight on its associated bump card.  If the objects card''s', ...
         'previous card (in order and suit) is in the last column, no bump card will', ...
         'flash.  For example, if you right-click the 5 of hearts, and the 4 of', ...
         'hearts is in the last column, then there is no bump card to flash.  Also,', ...
         'no bump card will flash if the object card is eligible to move.', ...
         '', ...
         '{\bfUndoing a Move and the Auto Move:}', ...
         'You can undo moves up until the last redeal by clicking the "undo move"', ...
         'button.  There is also an "undo all" option from the menubar.  This will', ...
         'undo all moves up until the last redeal.  By clicking "auto move", the', ...
         'game randomly moves an eligible card.  Also, from the menubar, you can', ...
         '"auto move all" cards up until the next redeal.'};
    
    % Make text objects for the rulesDlg pages.  Show them using visibility.
    hbasic1 = text(.05,.95,basic1,'parent',hax,'color',tcolor, ...
        'verticalalignment','top');
    
    hbasic2 = text(.05,.95,basic2,'parent',hax,'color',tcolor, ...
        'verticalalignment','top','visible','off');
    
    hoptions = text(.05,.95,options,'parent',hax,'color',tcolor, ...
        'verticalalignment','top','visible','off');

    % Make buttons for navigating the rulesDlg, and for closing it.
    hclose = uicontrol('string','Close','parent',hdlg,'units','pixels', ...
        'position',[boardPos(3)/2-80 20 60 20],'callback','closereq');
    
    hnext = uicontrol('string','Next -->','position',get(hclose,'position')-[80 0 0 0], ...
        'callback',{@rulesPage, 1},'tag','rulesNext','parent',hdlg);
    
    hprev = uicontrol('string','<-- Prev','position',get(hclose,'position')-[160 0 0 0], ...
        'callback',{@rulesPage,-1},'tag','rulesPrev','parent',hdlg,'enable','off');
    
    % Make a handles structure for the rulesDlg to keep track of the current page.
    dlgHandles = guihandles(hdlg);
    dlgHandles.page = 1;
    dlgHandles.hpages = [hbasic1; hbasic2; hoptions];
    dlgHandles.pages = length(dlgHandles.hpages);
    
    guidata(hdlg,dlgHandles);
    
    % Make some fun graphics when the rulesDlg is first opened.
    t = linspace(0,1,50);
    x = 0.5*(0.5*cos(2*pi*t)+0.6);
    y = 0.5*(0.5*sin(2.2*pi*t) + 0.5 + 0.7*t)+0.1;
    begc = get(hdlg,'color');
    endc = [0 0 .7];
    map = [linspace(begc(1),endc(1),length(t))', ...
            linspace(begc(2),endc(2),length(t))', ... 
            linspace(begc(3),endc(3),length(t))'];
    fadeTcolor = [linspace(begc(1),tcolor(1),length(t))', ...
            linspace(begc(2),tcolor(2),length(t))', ... 
            linspace(begc(3),tcolor(3),length(t))'];
    
    textProp.String     = 'Rules';
    textProp.FontWeight = 'bold';
    textProp.FontSize   = 40;
    textProp.Parent     = hax;

    lag = 20;
    htext = rand(lag, 1);

    % Animate the fun graphics
    try
        for n = 1:length(t)
            drawnow
            htext(1) = text(textProp,'color',fadeTcolor(n,:),'position',[x(n) y(n)]);
            drawnow
            htext = [htext(2:end); htext(1)];
            try
                set(htext(end-1),'color',map(n,:));
            end
            try
                delete(htext(1));
            end
        end
        for n = 1:length(htext)
            drawnow
            try
                delete(htext(n))
            end
        end
    end
else 
    % Dialog exists, so make it active!
    hdlg = getappdata(handles.figure,'rulesDlg');
    figure(hdlg);
    
    % Also make the first page visible
    rulesPage(hdlg,[],0);
    
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  RULESPAGE  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function rulesPage(h,event,step)
% Chooses which page of the rules to display.  Helper function for RULESDLG
% STEP == -1 denotes previous, 0 denotes the first page, +1 denotes the next page
handles = guidata(h);
hdlg = handles.rulesDlg;
switch step
case 0
    set(handles.hpages,'visible','off');
    set(handles.hpages(1),'visible','on');
    set(handles.rulesNext,'enable','on');
    set(handles.rulesPrev,'enable','off');
    handles.page = 1;
case -1
    if handles.page > 1
        set(handles.hpages(handles.page),'visible','off');
        handles.page = handles.page-1;
        set(handles.hpages(handles.page),'visible','on')
        set(handles.rulesNext,'enable','on');
        if handles.page == 1
            set(handles.rulesPrev,'enable','off')
        end
    end
case 1    
    if handles.page < handles.pages
        set(handles.hpages(handles.page),'visible','off');
        handles.page = handles.page+1;
        set(handles.hpages(handles.page),'visible','on')
        set(handles.rulesPrev,'enable','on')
        if handles.page == handles.pages
            set(handles.rulesNext,'enable','off');
        end
    end
end

guidata(h,handles)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  ABOUTDLG  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function aboutDlg(handles)
% Open dialog box with information about the game

% First check to see if the dialog is open already
if ~isappdata(handles.figure,'aboutDlg') | ~ishandle(getappdata(handles.figure,'aboutDlg'))
    
    % Create dialog figure
    boardPos = get(handles.figure,'position');
    
    figProp.Name            = 'About Trails...';
    figProp.NumberTitle     = 'off';
    figProp.Menubar         = 'none';
    figProp.Resize          = 'off';
    figProp.DoubleBuffer    = 'on';
    figProp.HandleVisibility= 'off';
    figProp.IntegerHandle   = 'off';
    figProp.Position        = boardPos + [15 -15 -boardPos(3)/2 0];
    hdlg = figure(figProp);

    % Store dialog figure handle to the main figure's appdata
    setappdata(handles.figure,'aboutDlg',hdlg);
    
    % Start creating the Axes and Text
    hax = axes('units','normalized','visible','off', ...
        'position',[0 0 1 1],'parent',hdlg);
    
    aboutText = {'{\bf\fontsize{16}A Solitaire Game}', ...
                 ' ', ...
                 '     Greg Aloe', ...
                 ['     Version ' handles.ver], ...
                 ['     ' handles.data], ...
                 '', ...
                 'Adapted from the solitaire game commonly known as Montana.', ...
                 ['Card images are screen captures from Microsoft Solitaire, ', ...
                 'with permission.']};

    textProp.String     = 'Trails';
    textProp.FontWeight = 'bold';
    textProp.FontSize   = 40;
    textProp.Parent     = hax;
    
    % Draw and animate the text and graphics
    try
        % Place the info text
        tcolor = [0.7 0 0.2];
        htext = text(.05,.35,aboutText,'parent',hax,'color',tcolor);
        
        % Place the email text
        hauthor = text(.05,.05,'contact the author: ', ...
            'parent',hax,'color',tcolor);
        hemail = text([get(hauthor,'extent') .05]*[0 0 1 0 1]',.05, ...
            'aloe@mathworks.com','parent',hax,'color',[0 0 .8], ...
            'buttondownfcn','trails(''emailAuthor'')');
        
        % Positions to place the text
        w = 2.5*pi;     a = 0.125;       d = 0.75;
        t = linspace(0,1,32);
        ts = t*0.58+0.05;
        y = a*cos(w*t).*exp(-0.1*w*t)+d;
        begc = get(hdlg,'color');   % Start fade with figure's color
        endc = [0 0 .7];            % End fade with designated color

        % Create a colorma that fades from begc to endc
        map = [linspace(begc(1),endc(1),length(ts))', ...
               linspace(begc(2),endc(2),length(ts))', ... 
               linspace(begc(3),endc(3),length(ts))'];
    
        % Button to close the aboutDlg
        hclose = uicontrol('string','Close','parent',hdlg,'units','pixels', ...
            'position',[boardPos(3)/2-80 20 60 20],'callback','closereq');
        
        % Animate the fun graphics
        for n = 1:length(ts)
            pause(0.04)
            htext = text(textProp,'color',map(n,:),'position',[ts(n) y(n)]);
        end
        m = 100;
        fadeEnd = tcolor;
        fadeMap = [linspace(map(end,1),fadeEnd(1),m)',  ...
               linspace(map(end,2),fadeEnd(2),m)',  ...
               linspace(map(end,3),fadeEnd(3),m)'];
        for n = 1:m
            set(htext,'color',fadeMap(n,:))
            drawnow
        end
        
    end
    
else 
    % Dialog exists, so make it active!
    figure(getappdata(handles.figure,'aboutDlg'));
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  EMAILAUTHOR  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function emailAuthor
% Callback for text object to launch email application to email me
errstr = '';
try
    stat = web('mailto:aloe@mathworks.com','-browser');
    switch stat
    case 1
        errstr = 'Could not find a browser, which is needed to create an email action.';
    case 2
        errstr = 'Could not launch the broswer, which is needed to create an email action.';
    end
catch
    errstr = sprintf('Not sure what happened...');
end

% If we found an error, try to tell the user what's going on.
if ~isempty(errstr)
    errstr = sprintf('%s\n\nTry typing out the email address instead.',errstr);
    errordlg(errstr,'Error with email action:');
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  TRAILSDELETEFCN  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function trailsDeleteFcn(h)
handles = guidata(h);

% If about dialog is open, delete it
if isappdata(handles.figure,'aboutDlg') & ishandle(getappdata(handles.figure,'aboutDlg'))
    delete(getappdata(handles.figure,'aboutDlg'));
end

% If rules dialog is open, delete it
if isappdata(handles.figure,'rulesDlg') & ishandle(getappdata(handles.figure,'rulesDlg'))
    delete(getappdata(handles.figure,'rulesDlg'));
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%  MATCHGAME  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function handles = matchGame(h,handles,varargin)
% Game converted to match game

% If called with only the handles structure, initialize the game
if nargin==2
    % Disable initiating the match game
    set(handles.dealstat,'buttondownfcn','');
    
    % Make the "matchCardColor" state changeable
    set(handles.matchColorSwitch,'visible','on','enable','on')
    
    % Make all cards look like the back of a card
    setCardColor([handles.card],'back')

    % Reshuffle the cards
    % Perform the actual shuffle!
    cards2deal = [handles.deck.value];
    shuffledDeck = cards2deal(randperm(52));

    % Deal the shuffled deck to the required spaces
    shuffledDeckCell = num2cell(shuffledDeck);
    [handles.table(:).card] = deal(shuffledDeckCell{:});

    % Graphics: set positions of each card in the shuffled deck
    for n=1:length(shuffledDeck)
        ind = value2cardsuit(shuffledDeck(n));
        set([handles.deck(ind).handle],'position',handles.framePosCell{n});
    end

    % Change the stat text and dealstat to track the match game instead
    set(findall(handles.figure,'string',{'deals','remaining'}), ...
        'string',{'matches','remaining'});
    set(handles.stats,'foregroundcolor',[0 0 0]);
    set(handles.dealstat,'string','26','foregroundcolor',[0 0 0])
    
    
    % Set all the card statuses such that they force MAKEMOVE to call MATCHGAME
    [handles.table.status] = deal(4);
    
    % Keep track of whether a match try has been started
    %   0 would mean no cards are visible, 1 would mean 1 card is flipped
    handles.matchStarted = 0;
    handles.matchCardValue = NaN;
    handles.matchChecking = 0;
    
    % Update GUI data
    guidata(h,handles)
else
    % If another argument was passed in, perform the specified action
    switch varargin{1}
    case 'cardClicked'
        handles.matchChecking = 1;
        guidata(h,handles)        
        
        % If the card is already matched, just return
        if strcmp(getCardColor(h),'blueish')
            handles.matchChecking = 0;
            guidata(h,handles)    
            return
        end

        % Show the card
        setCardColor(h,'base')
        
        % Store the first card's value to old, and replace it with the new one
        oldMatchCardValue = handles.matchCardValue;
        handles.matchCardValue = handles.deck(find([handles.deck(:).handle]==h)).value;
        
        if handles.matchStarted
            % If it's the second card of a match guess

            % If it's the same exact card as the first selection, return
            if h==handles.matchCardHandle
                handles.matchChecking = 0;
                guidata(h,handles)
                return
            end
            
            handles.matchStarted = 0;
            guidata(h,handles)
            
            % Check if the cards are both black or both red
            if getappdata(handles.figure,'matchColorState')
                isSameColor = ...
                    isempty(setdiff(floor(sort( ...
                        [handles.matchCardValue,oldMatchCardValue])/100),[1 4;2 3],'rows'));
            else
                isSameColor = 1;
            end
            % Check if the card values are equal regardless of suit
            isSameValue = (rem(oldMatchCardValue,100)==rem(handles.matchCardValue,100));
            % Check if we have a match!
            if isSameColor & isSameValue
                % Now that a match was made, we can't allow matchColorSwitch anymore
                set(handles.matchColorSwitch,'enable','off')
                matchesRem = str2num(get(handles.dealstat,'string'))-1;
                set(handles.dealstat,'string',num2str(matchesRem));
                newColor = 'blueish';
            else
                pause(1)
                newColor = 'back';
            end
            setCardColor([handles.matchCardHandle, h],newColor)
            
        else
            % If it's the first card of a match guess
            handles.matchStarted = 1;
            handles.matchCardHandle = h;
        end
        handles.matchChecking = 0;
    case 'matchColorSwitch'
        switch get(h,'checked')
            case 'off'
                set(h,'checked','on')
                setappdata(handles.figure,'matchColorState',1)
            case 'on'
                set(h,'checked','off')
                setappdata(handles.figure,'matchColorState',0)                
        end
end
    
    guidata(h,handles)
    
    if ~str2num(get(handles.dealstat,'string'))
        victory(handles.figure,handles)
    end
end

Contact us at files@mathworks.com