image thumbnail
from Towers Of Hanoi by Brian Moore
A MATLAB GUI application of the popular Towers Of Hanoi puzzle game with manual or auto solution.

TowersOfHanoi(varargin)
function TowersOfHanoi(varargin)
%--------------------------------------------------------------------------
% Syntax:       TowersOfHanoi(n);
%               TowersOfHanoi;
%
% Input:        n is the desired number of blocks. The default is
%               defaultNumPegs, and n must be in the range
%               [minNumPegs,maxNumPegs];
%
% Acive Keys:   {1,2,3} number keys are used to move blocks between the
%               corresponding 3 pegs.
%
% Description:  The objective of Towers of Hanoi is to move all n blocks
%               from the far left peg to the far right peg. The only
%               restricitons are that you may only move the top block on a
%               peg, and you may not place a larger block on top of a
%               smaller block. To move the top block on a peg to a
%               different peg, use the 1-3 number keys to select the target
%               and destination pegs. If you want to change the number of
%               blocks in the game, you can use the NUMBER OF BLOCKS
%               pull-down menu. To restart the game, you can push the RESET
%               button. Alternatively, the RANDOM RESET button will
%               randomly generate an initial setup with the specified
%               number of blocks. If you're still stuck, you can use the
%               SOLUTION SPEED slider and the SOLVE button to ask the
%               computer to solve the puzzle. While the computer is
%               solving, you may stop it at any time by pushing the STOP
%               button. Then you can continue the solution yourself. To
%               change the curent color scheme, use the COLOR SCHEME
%               pull-down menu. Finally, to quit the game, push the EXIT
%               button.
%
% Author:       Brian Moore
%               brimoor@umich.edu
%
% Date:         January 26, 2011
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Constants that the user may change
%--------------------------------------------------------------------------
defaultNumPegs = 5;
minNumPegs = 2; % must be at least 2!
maxNumPegs = 15;
if isempty(varargin)
    n = defaultNumPegs;
else
    n = varargin{1};

    if n > maxNumPegs
         n = maxNumPegs;
    end
    if n < minNumPegs
        n = minNumPegs;
    end        
end
k = .2; % curvature of blocks
kPegs = .1; % curvature of pegs

colormaps = {@jet;
             @hsv;
             @hot;
             @cool;
             @spring;
             @summer;
             @autumn;
             @winter;
             @bone;
             @copper};

colors = hsv(n); % default color scheme

numListStr = '';
for ii = minNumPegs:maxNumPegs
    numListStr = strcat(numListStr,num2str(ii),'|');
end
numListStr = strcat(numListStr,'64 (Just Kidding!)');
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Create GUI
%--------------------------------------------------------------------------    
f = figure('ResizeFcn',@setButtonSizes,'WindowKeyPressFcn',@myWindowKeyPressFcn,'name','Towers Of Hanoi - Brian Moore 2011');
set(gca,'Position',[.05 .25 .9 .57]);
figPosition = get(f,'Position');
xDim = .9*figPosition(3);
yDim = .57*figPosition(4);

xlim([1,xDim]);
ylim([1,yDim]);
title('Towers  of  Hanoi','FontUnits','normalized','FontSize',.15,'FontName','Lucida Handwriting');
axis off;    

solveText1 = uicontrol('Style','text','FontUnits','normalized','BackgroundColor',[.8,.8,.8],'String','Solution Speed');
solveText2 = uicontrol('Style','text','FontUnits','normalized','BackgroundColor',[.8,.8,.8],'String','Slow                     Fast');
speedSlider = uicontrol(f,'Style','slider','Min',1,'Max',100,'Value',50);
solveButton = uicontrol(f,'Style', 'pushbutton','FontUnits','normalized','String', 'Solve','Callback',@mySolveFun);
blockText = uicontrol('Style','text','FontUnits','normalized','BackgroundColor',[.8,.8,.8],'String','Number of Blocks');
numList = uicontrol('Style', 'popup','FontUnits','normalized','Callback',@myResetFun,'String',numListStr);
resetButton = uicontrol(f,'Style', 'pushbutton','FontUnits','normalized','String', 'Reset','Callback',@myResetFun);
randomResetButton = uicontrol(f,'Style', 'pushbutton','FontUnits','normalized','String','Random Reset','Callback',@myRandomResetFun);
exitButton = uicontrol(f,'Style', 'pushbutton','FontUnits','normalized','String','Exit','Callback',@myExitFun);
colorList = uicontrol('Style','popup','FontUnits','normalized','Callback',@myColorFun,'String','Jet|HSV|Hot|Cool|Spring|Summer|Autumn|Winter|Bone|Copper|Random');
colorText = uicontrol('Style','text','FontUnits','normalized','BackgroundColor',[.8,.8,.8],'String','Color Scheme');
setButtonSizes;
set(numList,'Value',n-minNumPegs+1);
set(colorList,'Value',2);
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Declare variables
%--------------------------------------------------------------------------
exitNow = 0;
newKey = 0;
solving = 0;
randomBoard = 0;
state = [];
handles = {};
pegDot = [];
width = [];
pegs = [];
pegWidth = [];
to = [];
from = [];
h = [];
pegDotDiameter = [];
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Initialize Board
%--------------------------------------------------------------------------
initBoard;
%--------------------------------------------------------------------------

%--------------------------------------------------------------------------
% Main Loop
%--------------------------------------------------------------------------
while ~exitNow

    % Refresh the board
    drawnow;

    %----------------------------------------------------------------------
    % Get keypress when a key is released
    %----------------------------------------------------------------------
    if newKey == 1
        key = get(f,'CurrentCharacter') - 48;

        if (key > 0) && (key < 4)            
            if isempty(from)
                from = key;
                pegDot = rectangle('Position',[pegs(from) - pegDotDiameter/2,yDim - h/1.25,pegDotDiameter,pegDotDiameter],'Curvature',[1,1],'FaceColor','k');
            elseif isempty(to)
                to = key;
                delete(pegDot);
            end
        end

        newKey = 0;
    end
    %----------------------------------------------------------------------

    %----------------------------------------------------------------------
    % Perform the move
    %----------------------------------------------------------------------
    if ~isempty(from) && ~isempty(to)

        movePiece(from,to);
        checkForSolution;

        to = [];
        from = [];
    end
    %----------------------------------------------------------------------

end
%--------------------------------------------------------------------------

% Delete the GUI
delete(f);

%--------------------------------------------------------------------------
% Nested functions
%--------------------------------------------------------------------------
function setButtonSizes(~,~)
    % Reposition the buttons
    figPosition = get(f,'Position');

    set(solveText1,'Position',[.04*figPosition(3) .11*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(speedSlider,'Position',[.04*figPosition(3) .07*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(solveText2,'Position',[.04*figPosition(3) .02*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);

    set(colorText,'Position',[.28*figPosition(3) .15*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(colorList,'Position',[.28*figPosition(3) .1*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(solveButton,'Position',[.28*figPosition(3) .03*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);

    set(blockText,'Position',[.52*figPosition(3) .15*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(numList,'Position',[.52*figPosition(3) .1*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(exitButton,'Position',[.52*figPosition(3) .03*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);

    set(randomResetButton,'Position',[.76*figPosition(3) .1*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
    set(resetButton,'Position',[.76*figPosition(3) .03*figPosition(4) .2*figPosition(3) .05*figPosition(4)]);
end

function myColorFun(varargin)

    % Get new color scheme
    listNum = get(colorList,'Value');
    if listNum == length(colormaps)+1
        colors = rand(n,3);
    else
        fnHandle = colormaps{listNum};
        colors = fnHandle(n);
        colors = colors(end:-1:1,:);
    end

    % Redraw blocks
    for j = 1:3
        for i = 1:n
            if state(i,j) < n+1
                set(handles{i,j},'FaceColor',colors(state(i,j),:));
            end
        end
    end
end

function checkForSolution
    if sum(state(:,3)) == n*(n+1)/2
        selection = questdlg('Congratulations! You solved the puzzle. Play again?','Great Success!','Yes','No','Yes'); 
        switch selection, 
          case 'Yes',
             myResetFun;
          case 'No'
             exitNow = 1;
        end
    end
end

function movePiece(fromPeg,toPeg)
    fromRung = find(state(:,fromPeg)~=(n+1),1,'first');
    toRung = find(state(:,toPeg)==(n+1),1,'last');
    num = state(fromRung,fromPeg);

    if num < state(min(n,toRung+1),toPeg)
        state(fromRung,fromPeg) = n+1;
        delete(handles{fromRung,fromPeg});

        state(toRung,toPeg) = num;
        handles{toRung,toPeg} = rectangle('Position',[pegs(toPeg) - width(num)/2,1+(n-toRung)*h,width(num),h],'Curvature',[k,k],'FaceColor',colors(num,:));

        % Refresh the board
        drawnow;
    end
end

function initBoard
    pegs = xDim*[1/4 1/2 3/4];
    minWidth = 9*xDim/80;
    maxWidth = 9*xDim/40;
    width = linspace(minWidth,maxWidth,n)';
    pegWidth = minWidth/3;
    h = yDim/(n+2); % height of each block
    pegDotDiameter = h/2; % diameter of marker pegDot

    handles = cell(n,3);
    state = (n+1)*ones(n,3);

    % See if we are randomly generating a board
    if randomBoard == 1;
        % Random setup
        inds = randi(3,n,1);
        if sum(inds) == 3*n
            inds(1) = inds(1) - 1;
        end
        for N = n:-1:1
            state(find(state(:,inds(N)) == n+1,1,'last'),inds(N)) = N;
        end
    else
        % Standard setup
        state(:,1) = (1:n)';
    end

    % Create pegs
    rectangle('Position',[pegs(1) - pegWidth/2,1,pegWidth,yDim-(h+1)],'Curvature',[kPegs,kPegs],'FaceColor',[.5,.5,.5]);
    rectangle('Position',[pegs(2) - pegWidth/2,1,pegWidth,yDim-(h+1)],'Curvature',[kPegs,kPegs],'FaceColor',[.5,.5,.5]);
    rectangle('Position',[pegs(3) - pegWidth/2,1,pegWidth,yDim-(h+1)],'Curvature',[kPegs,kPegs],'FaceColor',[.5,.5,.5]);

    % Create blocks
    myColorFun;
    for j = 1:3
        for i=1:n
            if state(i,j) < n+1
                handles{i,j} = rectangle('Position',[pegs(j) - width(state(i,j))/2,1+(n-i)*h,width(state(i,j)),h],'Curvature',[k,k],'FaceColor',colors(state(i,j),:));
            end
        end
    end

    set(solveButton,'String','Solve');
    set(solveButton,'BackgroundColor',.9294*[1 1 1]);
    solving = 0;
end

function cleanUpBoard
    cla;
    to = [];
    from = [];
end

function myResetFun(varargin)
    candidateN = get(numList,'Value')+minNumPegs-1;
    if candidateN <= maxNumPegs
        n = candidateN;
        randomBoard = 0;
        cleanUpBoard;
        initBoard;
    else
        set(numList,'Value',n-minNumPegs+1);
    end
end

function myRandomResetFun(~,~)
    cleanUpBoard;
    n = get(numList,'Value')+minNumPegs-1;
    randomBoard = 1;
    initBoard;
end

function myExitFun(~,~)
    exitNow = 1;
end

function myWindowKeyPressFcn(~,~)
    if solving == 0
        newKey = 1;
    end
end

function mySolveFun(~,~)
    if solving == 0
        set(solveButton,'String','Stop');
        set(solveButton,'BackgroundColor','r');
        solving = 1;

        % Begin solving the puzzle
        arbitrarySolver(n,3);

        % See if we were successfull
        checkForSolution;
    end

    set(solveButton,'String','Solve');
    set(solveButton,'BackgroundColor',.9294*[1 1 1]);        
    solving = 0;
end

function arbitrarySolver(numBlocks,toPeg)
    if solving == 1
        % Find largest discrepancy
        N = numBlocks;
        while sum(N == state(:,toPeg)) == 1
            N = N - 1;
        end

        % Find where the guilty party resides
        fromPeg = ceil(find(state == N)/n);
        topOfFromPeg = find(state(:,fromPeg) < n+1,1,'first');

        % See if we can fix largest discrepancy in one move
        if (state(topOfFromPeg,fromPeg) ~= N) || (sum(state(:,toPeg) < N) ~= 0)
            % We can't make the move, so align smaller blocks on auxillary peg
            arbitrarySolver(N-1,6-fromPeg-toPeg);
        end

        if solving == 1
            % Now we can make the move!
            movePiece(fromPeg,toPeg);
            pause((100-get(speedSlider,'Value'))/200);
        end

        % Use ideal solver to complete the puzzle
        idealSolver(6-fromPeg-toPeg,toPeg,N-1);
    end
end

function idealSolver(fromPeg,toPeg,numBlocks)
    if (solving == 1) && (exitNow == 0)
        if numBlocks == 0
            return;
        else
            idealSolver(fromPeg,6-toPeg-fromPeg,numBlocks-1);
            if (solving == 1) && (exitNow == 0)
                movePiece(fromPeg,toPeg);
                pause((100-get(speedSlider,'Value'))/200);
            end
            idealSolver(6-toPeg-fromPeg,toPeg,numBlocks-1);
        end
    end
end
%--------------------------------------------------------------------------
end

Contact us