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