Code covered by the BSD License  

Highlights from
SudokuUI

from SudokuUI by Mark Hoyle
Assistant for solving Sudoku problems.

SudokuUI(varargin)
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

Contact us at files@mathworks.com