function varargout = SudokuUI(varargin)
% A GUI to facilitate the solving of Su-doku problems.
% SudokuUI - creates a blank GUI for 9x9 Sudoku,
% SudokuUI(16) - creates a blank UI for 16x16 Sudoku
%
% First what size of Su Doku are we looking at? Default is three, however
if nargin > 0
if numel(varargin{1}) == 1
N = varargin{1};
M = -1*ones(N);
else
M = varargin{1};
N = size(M,1);
end
else
M = -1*ones(9);
N = 9;
end
h = findobj('tag',mfilename);
if isempty(h)
ud = [];
nCreate(N);
else
figure(h);
end
if ~isempty(M)
nInitialise(M);
end
%--------------------------------------------------------------------------
function nCreate(N)
% Create the figure in the middle of the screen
spos = get(0,'screensize');
asz = N*40;
fsz = asz+40;
fpos = [(spos(3)-fsz)/2, (spos(4)-fsz)/2 fsz fsz];
f = figure('menubar','none',...
'numbertitle','off',...
'tag',mfilename,...
'position',fpos,...
'name','Su Doku',...
'resize','off');
% Now create the axes
ax = axes(...
'box','on',...
'units','pixels',...
'position',[20 20 asz asz]);
% Now create the grid
for ii = 1:N-1
if rem(ii,sqrt(N)) == 0
% One of the interior squares
wdh = 2;
else
wdh = 1;
end
% Vertical lines
line([ii ii]*40, [0 asz], 'color','k',...
'linewidth',wdh);
% Horizontal lines
line([0 asz],[ii ii]*40, 'color','k',...
'linewidth',wdh);
end
set(ax,'xtick',[],'ytick',[]);
axis(ax,'tight');
% Menus: First undo/redo and reset menus
fm = uimenu('parent',f,...
'Label','Actions');
uimenu('parent',fm,...
'Label','Undo',...
'Accelerator','Z',...
'callback',{@nUndo});
chk = uimenu('parent',fm,...
'Label','Check',...
'Accelerator','C',...
'callback',{@nToggleCheck});
% Now for the uicontextmenu
uic = uicontextmenu('parent',f);
if N == 9
Labels = {'1','2','3','4','5','6','7','8','9'};
elseif N == 16
Labels = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
end
for ii = 1:N
menus(ii) = uimenu('parent',uic,...
'Label',Labels{ii},...
'callback',{@nSetCell,ii});
end
% Now set up the text boxes where we display the numbers
for ii = 1:N
for jj = 1:N
Boxes(ii,jj) = uicontrol(f,...
'position',[(jj-1)*40+21,(N-ii)*40+21,38,38],...
'backgroundcolor','w',...
'style','text',...
'buttondownfcn',{@nBoxDown,ii,jj},...
'fontsize',18,...
'UserData',[],...
'uicontextmenu',uic);
end
end
% Store the handles
ud.Handles.Figure = f;
ud.Handles.Axis = ax;
ud.Handles.Menus = menus;
ud.Handles.Check = chk;
ud.Handles.Boxes = Boxes;
ud.Data.Check = false;
end
%--------------------------------------------------------------------------
function nInitialise(M)
% initialise the display with the contents of the matrix
% store the initial configuration
ud.Data.Matrix = M;
ud.Data.CurrentIndex = [0 0];
N = size(M,1);
% now initialise the grid
for jj = 1:N
for ii = 1:N
if M(ii,jj) >=0
nSetCell([],[],M(ii,jj),ii,jj);
end
end
end
end
%--------------------------------------------------------------------------
function nSetCell(src,evt,I,ii,jj)
% Set the string in the cell to what has just been chosen
Mtx = ud.Data.Matrix;
if nargin == 3
% We aren't providing the indices
ii = ud.Data.CurrentIndex(1);
jj = ud.Data.CurrentIndex(2);
end
if size(Mtx,1) == 9
set(ud.Handles.Boxes(ii,jj),'string',num2str(I));
else
set(ud.Handles.Boxes(ii,jj),'string',dec2hex(I-1));
end
if nargin == 3
% Was triggered by a context menu
% Now analyse for any positions where there is now only one option left
% and fill it in
if ud.Data.Check
M = nCheckForSingles(M);
end
end
M = ud.Data.Matrix(:,:,end);
M(ii,jj) = I;
idx = size(ud.Data.Matrix,3);
ud.Data.Matrix(:,:,idx+1) = M;
end
%--------------------------------------------------------------------------
function nBoxDown(src,evt,ii,jj)
% Is this box empty - in which case, if they are right clicking, turn off
% the relevant menus, otherwise show all the illegal positions for this
% entry
M = ud.Data.Matrix(:,:,end);
% Now get the size of the sub-matrices
n = sqrt(size(M,1));
set(ud.Handles.Boxes,'backgroundcolor','w');
if M(ii,jj) >= 0
% position is taken, turn all the taken positions red
set(ud.Handles.Menus,'visible','off');
Indices = nFindIllegalPlaces(M,M(ii,jj));
set(ud.Handles.Boxes(Indices),'backgroundcolor','r');
else
% Work out which context menus we can show
[nums,left] = nAnalyse(M,ii,jj);
set(ud.Handles.Menus(nums),'visible','off');
set(ud.Handles.Menus(left),'visible','on');
ud.Data.CurrentIndex = [ii,jj];
end
end
%--------------------------------------------------------------------------
function nUndo(src,evt)
% undo last move
if size(ud.Data.Matrix,3) == 1
% nothing to undo
return
end
M = ud.Data.Matrix(:,:,end-1); % matrix to revert to
M1 = ud.Data.Matrix(:,:,end); % current matrix
[I,J] = find(M(:,:) ~= M1(:,:));
set(ud.Handles.Boxes(I,J),'string','');
ud.Data.Matrix(:,:,end) = [];
end
%--------------------------------------------------------------------------
function nToggleCheck(varargin)
% Toggle the checking mode, with this on we check for singles
if ud.Data.Check
ud.Data.Check = false;
set(ud.Handles.Check,'checked','off');
else
ud.Data.Check = true;
set(ud.Handles.Check,'checked','on');
M = ud.Data.Matrix(:,:,end);
nCheckForSingles(M);
end
end
%--------------------------------------------------------------------------
function Indices = nFindIllegalPlaces(M,I)
% Given I, find the places where we can't put any more of the same value
N = size(M,1);
n = sqrt(N);
Indices = false(size(M));
[I,J] = find(M(:,:) == I);
% Deal with rows
Indices(I,:) = true;
% Now columns
Indices(:,J) = true;
% Now block out all taken squares
Indices(M(:,:) ~= -1) = true;
% Now we have to block out the nxn squares that already have a number in
% them
vec = [0:n-1];
for ii = 1:length(I)
idx = n*(floor((I(ii)-1)/n))+1;
jdx = n*(floor((J(ii)-1)/n))+1;
Indices(idx+vec,jdx+vec) = true;
end
end
%--------------------------------------------------------------------------
function [nums,left] = nAnalyse(M,ii,jj)
% Work out which numbers can go in the chosen cell
n = sqrt(size(M,1)); % size of smaller matrices
row = M(ii,:);
col = M(:,jj);
idx = n*(floor((ii-1)/n))+1;
jdx = n*(floor((jj-1)/n))+1;
mat = M(idx+[0:n-1],jdx + [0:n-1]);
nums = unique([row(:); col(:); mat(:)]);
% remove the -1
nums(nums(:) == -1) = [];
left = setdiff([1:size(M,1)]',nums);
end
%--------------------------------------------------------------------------
function M = nCheckForSingles(M)
% Run through the matrix and check for cells with only one option left to
% them
M_old = zeros(size(M));
N = size(M,1);
n = sqrt(N);
while ~isequal(M_old,M)
M_old = M;
for ii = 1:size(M,1)
for jj = 1:size(M,2)
[nums,left] = nAnalyse(M,ii,jj);
if length(left) == 1 & M(ii,jj) == -1
% only one option for this cell
nSetCell([],[],left,ii,jj);
M(ii,jj) = left;
end
end
end
% Now run through all the numbers and see if we have rows/columns/boxes
% with only one place to put the number
for ii = 1:N
% Initialise sub matrix counters
mIdx = 0; mJdx = 0;
Indices = nFindIllegalPlaces(M,ii);
for jj = 1:N
% Check row
ridx = find(~Indices(jj,:));
if length(ridx) == 1
nSetCell([],[],ii,jj,ridx);
M(jj,ridx) = ii;
Indices = nFindIllegalPlaces(M,ii);
end
% Now do same for columns
cidx = find(~Indices(:,jj));
if length(cidx) == 1
nSetCell([],[],ii,cidx,jj);
M(cidx,jj) = ii;
Indices = nFindIllegalPlaces(M,ii);
end
% Now do the same for sub-matrices
[midx,mjdx] = find(~Indices(mIdx+[1:n],mJdx+[1:n]));
if length(midx) == 1
nSetCell([],[],ii,midx+mIdx,mjdx+mJdx);
M(midx+mIdx,mjdx+mJdx) = ii;
end
if mIdx == n*(n-1)
mIdx = 0;
mJdx = mJdx+n;
else
mIdx = mIdx+n;
end
end
end
end
end
% End of main function
end