Code covered by the BSD License  

Highlights from
sudokon

image thumbnail
from sudokon by Carl de Boor
A GUI for helping you to solve sudoku puzzles onscreen

sudokon(g)
function  sudokon(g)
%SUDOKON play sudoku onscreen, starting with the g , if given
%
%   SUDOKON(G) generates a sudoku grid from the given matrix G which is 
%   expected to be a 9-by-9 matrix with entries from {0,1,2,...,9}.
%
%   The object of the game is to select, for each cell not already containing
%   a single, bold, number, a number from 1:9 in such a way that, in each row,
%   in each column, and in each of the nine 3-by-3 cell-boxes, each of the
%   numbers 1:9 appears, hence appears exactly once.
%   In the default mode, each of the cells still to be filled in this way shows 
%   all the choices still possible for it under these constraints.
%   
%   To select one of these numbers for a cell, left-click on it.
%
%   If there is only one choice showing, then left-clicking anywhere in that
%   cell will select that number for the cell.
%
%   If there are no such cells, you need to use reasoning to reduce the number
%   of choices. When you come to the conclusion that one of the choices showing
%   is impossible, delete it by right-clicking on it.
%   
%   To undo the most recent change in a cell, click on it (making sure not to
%   click on any of the choices showing). This will bring up an 'undo' button,
%   and clicking it will undo the most recent change in that cell as well as
%   all other changes made since then.
%   To undo the most recent change in a cell that left it with just one choice
%   you must right-click it (since a left-click would make that single choice
%   your choice for this cell).
%
%   If the cell is already settled, then a (left or right) click on it brings 
%   up the 'undo' button.
%
%   If you are in the clueless mode (see the 'hint?' button described below),
%   then the cells still not settled are blank and clicking one such generates
%   a keyboard of all nine numbers. Clicking on one makes the corresponding 
%   number your choice for this cell provided the choice isn't obviously wrong.
%  
%   SUDOKON(N) with N from 1:12 supplies a puzzle from a list of puzzles, with
%   the same N providing seemingly different puzzles, but their difficulty 
%   increasing with N.
%
%   SUDOKON without any input expects you to enter a puzzle directly into the
%   sudoku board, by left-clicking on available choices.
%   
%   The GUI has the following permanent buttons:
%
%   'hint?':  cycles through the following three modes:
%             (1) showing nothing in cells not yet settled (the clueless mode);
%             (2) showing the choices still possible in cells not yet settled;
%             (3) showing (if left-clicked) the choice to be selected at some 
%               cells based on the present state of the board, or 
%               (if right-clicked) some choices that can safely be deleted from
%               some cells; either way,  detailed hints are printed in the 
%               command window, provided '...choices; print' (see 'check...')
%               is set.
%   'check...':  checks whatever is indicated on the button to the right of
%             it, and that button cycles through the following four modes:
%             (1) '...choices made' tells you whether or not all selections
%               (and deletions) made so far are correct;
%             (2) '...choices; print' checks the same thing but also prints in
%               the command window various details (such as the order in which
%               choices were found, and by what rules, to obtain a complete
%               solution, starting at the current board);
%             (3) '...# solutions' tells you the number of solutions;
%             (4) '...all solutions' prints, in the command window, all
%             solutions (if any).
%   'save':   saves the current board in a file.
%   'load':   brings back a board saved earlier.
%   'print':  sends the sudoku board in its current state to the printer,
%             to an eps file, or to the command window.
%   'permute':  permutes the current problem into one of the same difficulty,
%              thus providing a large source of sudoku problems.

%  Copyright 2006-2010 Carl de Boor
%  All commercial rights and uses, including the name `sudokon', reserved for
%  Carl de Boor.
%   8-29jan06, 8-13feb06, 22feb06, 24-25feb06, 1-2mar06, 3-7mar06, 23mar06,
%   13jun06, 14jul06, 19sep06, 16oct06, 26nov06, 06feb07, 14feb07, 23feb07,
%   15mar07, 15apr07, 22apr07, 12jun07, 13feb08, 04jun08, 26jul08, 17-22jan09,
%   27-31jul10, 08-13dec10

if ~nargin||~ischar(g)
   action = 'start';
else
   action = g;
   temp = get(gcf,'Userdata'); 
   if ~isempty(temp)
      [hf,p,dg,gr,hc,hst] = deal(temp{:}); 
   end
end

w = [.09,.0866666666];
switch action

case 'start'

   % set up the display
   
   running = findobj(allchild(0),'name','sudokon');
   if ~isempty(running), delete(running), end

   hf = figure('Toolbar','none','menubar','none',...
   'name','sudokon',...
   'numbertitle','off',...
   'DockControls','off',...
   'tag','sudoku',...
   'Units','characters',...
   'Resize', 'off',...
   'DeleteFcn','sudokon ''closefigure''',...
   'Position',[10,10,135,40]);
   set(hf,'Units', 'normalized') %temporary use of character units ensures
                                 %same appearance over platforms and screens
       
   hc = zeros(85,4);

   ismall = .01; ilarge = .02;
   w3 = 3*(w+ismall)+ilarge;
   xoff = .03; yoff=.94; % 3*w3(2) + yoff = 1
   A = ['1 2 3';'4 5 6';'7 8 9'];
   k = 0;
   for j1=0:2
    for j2=1:3
     for i1=0:2
      for i2=1:3
          k = k+1;
          x = xoff+j1*w3(1)+(j2-1)*(w(1)+ismall); 
          y = yoff-i1*w3(2)-i2*(w(2)+ismall);
         hc(k,[2 3]) = [x,y]+w/2;
         hc(k,1) = uicontrol('Parent',hf,...
          'BackgroundColor',[1 1 1],...
          'ForegroundColor','k',...
          'FontName','FixedWidth',...
          'Fontsize',10,'Fontweight','normal','Style','text',...
          'ButtonDownFcn','sudokon ''works''',...
          'Enable','inactive',...
          'Units','normalized',...
          'Position', [x+.006,y,w(1)-.012,w(2)],...
          'string',A,'Userdata',k);
      end
     end
    end
   end

      % set up an axes, to plot the sudoku grid when printing
   axes('Parent',hf,...
              'ButtonDownFcn','sudokon ''works''',...
              'Position',[0 0 1 1]);
      % it seems best to pick the grid directly from the cells just set up
   temp = 1:12; temp([1 5 9 12]) = [];
   xx = zeros(12,1); xx(temp) = (hc(10:9:73,2)+hc(1:9:64,2))/2; 
   ofn = [1 5 9]; ofs = [1 4 7];
   xx(ofn) = 2*hc(ofs*9-8,2) - xx(ofn+1);
   xx(ofn+3) = 2*hc(ofs*9+10,2) - xx(ofn+2);
   yy = zeros(12,1); yy(temp) = (hc(2:9,3)+hc(1:8,3))/2; 
   yy(ofn) = 2*hc(ofs,3) - yy(ofn+1);
   yy(ofn+3) = 2*hc(ofs+2,3) - yy(ofn+2);
   temp = reshape(repmat(1:12,3,1),1,36);
   hc(83,4) = line('Xdata',[repmat([xx([1 end]);NaN],12,1); xx(temp)],...
                   'Ydata',[yy(temp);repmat([yy([1 end]);NaN],12,1)],...
                   'Linewidth',.5,'Visible','off');
   axis off

   % set up the background button for doing nothing
   hc(82,4) = uicontrol('Parent',hf,...
       'Style','Pushbutton',...
       'Tooltipstring','don''t do anything',...
       'Units','normalized',...
       'Callback','sudokon ''donot''',...
       'Visible','off');

   % set up the button for undo'ing the most recent change in a cell
   % and all other changes since then elsewhere
   % ( hc(82,1:2) have become unused )
   hc(82,3) = uicontrol('Parent',hf,...
      'Style','Pushbutton',...
      'Tooltipstring',...
      'return to situation just before the last action on this cell',...
      'Units','normalized',...
      'String','undo',...
      'Callback','sudokon ''undo''',...
      'Visible','off');
       % 'FontName','FixedWidth',...

   % start the list of possibilities
   
   p = ones(82,9);

   % ... and the 3x3 key board
   for j=1:9
      p(82,j) = uicontrol('Parent',hf,...
         'Units','normalized',...
         'Visible','off',...
         'String',num2str(j),...
         'Tooltipstring','click to delete',...
         'Style','Pushbutton');
   end

   % ... and the 7 buttons
   for j=1:4
      if j<4, hc(83,j) = uicontrol('Parent',hf,...
         'Units','normalized',...
         'BackgroundColor',[1 1 1],...
         'FontName','FixedWidth',...
         'Style','Pushbutton');
      end
      hc(84,j) = uicontrol('Parent',hf,...
         'Units','normalized',...
         'BackgroundColor',[1 1 1],...
         'FontName','FixedWidth',...
         'Style','Pushbutton');
   end

   set(hc(83,1),'ButtonDownFcn','sudokon ''hints''',...
          'Enable','inactive',...
          'Tooltipstring','cycles through levels of help',...
          'Position', [hc(1,2)-w(1)/2,.97,w(1),.029],...
          'string','hint?');
   set(hc(83,2),'Callback','sudokon ''prints''',...
          'Tooltipstring','prints current sudoku board',...
          'Position', [hc(19,2)-w(1)/2,.9375,w(1),.029],...
          'string','print');
   set(hc(83,3),'Callback','sudokon ''checks''',...
          'Tooltipstring','checks consistency of choices made so far',...
          'Userdata','click on empty cell for selection',...
          'Position', [hc(10,2)-w(1)/2,.97,w(1),.029],...
          'string','check...');
   set(hc(84,1),'Callback','sudokon ''save''',...
          'Userdata',0,...
          'Position', [hc(1,2)-w(1)/2,.9375,w(1),.029],...
          'Tooltipstring','saves current board in a file','String','save')
   set(hc(84,2),'Callback','sudokon ''load''',...
          'Userdata','left/right-click to select/delete',...
          'Position', [hc(10,2)-w(1)/2,.9375,w(1),.029],...
          'Tooltipstring','brings back a board saved earlier','String','load')
   set(hc(84,3),'Callback','sudokon ''prefer''',...
          'Userdata',1,...
          'Position', [hc(19,2)-w(1)*.51,.97,w(1)*2.4,.029],...
          'Tooltipstring','sets what to check for',...
          'HorizontalAlignment','left',...
          'String','...choices made')
          % Horizontal Alignment will not work in a Windows environment
 set(hc(84,4),'Callback','sudokon ''permute''',...
          'Tooltipstring','permutes the current problem',...
          'Position', [hc(28,2)-w(1)/2,.9375,w(1),.029],...
          'string','permute');
   
   hc(85,1) = uicontrol('Parent',hf,...
          'BackgroundColor',[1 1 1],...
          'FontName','FixedWidth',...
          'Style','Text',...
          'Visible','on',...
          'Fontweight','bold',...
          'Units','normalized',...
          'Position', [(hc(46,2)+hc(55,2))/2-.2,.94,.5,.057],...
          'Userdata','The current selection is inconsistent',...
          'String',get(hc(84,2),'Userdata'));
          % 'Position', [(hc(46,2)+hc(55,2))/2-.3,.94,.6,.057],...
          
      % if hc(85,2), then choices to be removed rather than singleton
      %              suggestions are shown when hints are asked for and,
      %              if there is printing, more details are printed.

   % initiate the solution matrix
   
   dg = zeros(9,9);
   
   % construct the 27 groups
   
   gr2 = repmat((1:9).',1,9) + repmat(0:9:80,9,1); % its columns are the columns
   gr1 = gr2.'; % its columns are the rows
   gr3 = repmat([1:3, 10:12, 19:21].',1,9) + ...
         repmat([0 3 6 27 30 33 54 57 60],9,1);
                % its columns are the 3-by-3 cells
   
   gr = [gr1 gr2 gr3]; % to be used as i, j+9, c+18
   
   % initiate history

   hst = zeros(0,3);

   % generate the list of given singletons
   
   if nargin
      if numel(g)==1
              g = examples(g);
      else % check that the given input is 9x9 with range 1:9
         if ~isequal(size(g),[9,9])
            delete(findobj(allchild(0),'name','sudokon'))
            error('the input matrix should be of size [9,9]')
         end
         if issparse(g)
            g = full(g);
         end
         d = unique(g);
         d(d==0|d==1|d==2|d==3|d==4|d==5|d==6|d==7|d==8|d==9)=[];
         if ~isempty(d)
            delete(findobj(allchild(0),'name','sudokon'))
            error('the entries of the input matrix should all be from 0:9')
         end
      end

      s = find(g);
      if hc(85,3), fprintf('Insert the given values\n')
      end
      [dg,p,hc] = rm(s,g(s),dg,p,gr,hc);
      hst = [zeros(length(s),1),s,g(s)];
   end
   
   % prevent access to uicontrols and figure by others
   set(hf,'HandleVisibility','callback')

case 'checks'
               % get(hc(84,3),'Userdata')          action
               %        1                check correctness
               %        2                check correctness and print detail
               %        3                check number of solutions
               %        4                print all solutions (if any)

   set(hc(85,1),'Visible','off')
   switch get(hc(84,3),'Userdata')
   case {1,2}
      ok = checkit(dg,p,gr,hc,action);
      if ~ok, set(hc(85,1),'String',get(hc(85,1),'Userdata'))
      else 
         set(hc(85,1),'String','All choices made so far are correct')
      end
   case 3
      pp = p; pp(82,1) = 0;
      %[~,~,pp] = checkit(dg,pp,gr,hc,'allsols');
      [ignored,ignored,pp] = checkit(dg,pp,gr,hc,'allsols');
      if pp(82,1)==1, mess = sprintf('There is exactly one solution.');
      elseif pp(82,1)<0
         mess = sprintf('There are at least %g solutions.',-pp(82,1));
      else  mess = sprintf('There are exactly %g solutions.',pp(82,1));
      end
      set(hc(85,1),'String',mess)
   case 4
      pp = p; pp(82,1) = 0; checkit(dg,pp,gr,hc,'ap'); 
   end
   set(hc(85,1),'Visible','on')

case 'hints'

   if hc(85,4)>0  % we are in the end game, so, simply turn on the cells not
                  % yet selected.
      if hc(85,4)>1  % if this is the second request for hints, finish all
                % selections.
         kk = find(~hc(1:81,4)); dd = kk; hc(kk,4) = 1; lkk = numel(kk);
         for j=1:lkk
            dd(j) = find(p(kk(j),:)); 
            set(hc(kk(j),1),'Visible','on','Fontsize',30,'Fontweight','bold',...
            'String',dd(j))
         end
         hst = [hst; ones(lkk,1),kk,dd];
         set(hc(85,1),'Visible','on', 'String','Congratulations!')
      else
         hc(85,4) = 0; update(hc,p), hc(85,4) = 2;
         set(hc(85,1),'Visible','on', 'String',get(hc(84,2),'Userdata'))
      end
      
   else

      if hc(85,4)<0
         hc(85,4) = 0; update(hc,p)
         set(hc(85,1),'Visible','on','String',get(hc(84,2),'Userdata'))
         if isequal(get(p(82,1),'Visible'),'on')
            set([p(82,:), hc(82,4)],'Visible','off');
         end

      else
         strings = get(hc(85,1),'String');
         if iscell(strings), strings = strings{1}; end
         if (strings(1)=='E'||strings(1)=='N')
            update(hc,p)
            set(hc(~hc(1:81,4),1),'String',''), hc(85,4) = -1;
            set(hc(85,1),'Visible','on','String',get(hc(83,3),'Userdata'))
         else
            hc(85,2) = 0;
            if isequal(get(gcf,'SelectionType'),'alt')
               hc(85,2) = 1;
            end
            switch checkit(dg,p,gr,hc,action)
            case 0
               messge = get(hc(85,1),'Userdata');
            case 2
                   messge = {'Every circle indicates';'a removable choice'};
            case 1
               messge = 'Every star indicates a correct choice';
            case -1
               messge = 'No obvious correct choices';
            end
            set(hc(85,1),'String',messge,'Visible','on')
         end
      end
   end

case 'prints'
   button = questdlg('Where should the board be sent to?','Send to ...',...
           'Printer','File','Command Window','Printer');
   if isempty(button), button = 'N'; end
   switch button(1)
   case {'P','F'}
      set([reshape(hc(83:84,1:4),1,8),hc(85,1)],'Visible','off')
      set(hc(83,4),'Visible','on')
      if button(1)=='F'
         curbd = get(hc(84,1),'Userdata');
         [pn,mfname,coru] = getfilename('.eps',curbd);
         if ~isempty(mfname)
            eval(['print -deps ',pn,mfname,'.eps'])
            set(hc(84,1),'Userdata',curbd+1);
            fprintf(['sudokon ',coru,'ated the eps-file  ',mfname,...
                      '  in the directory ',strrep(pn(1:end-1),'\','\\'),'\n']);
         end
      else
         print
      end
      set([reshape(hc(83:84,1:4),1,8),hc(85,1)],'Visible','on')
      set(hc(83,4),'Visible','off')

   case 'C'
      border = ' +-------+-------+-------+';
      tline   = repmat(' | . . . | . . . | . . . |',3,1);
      printg = [border;repmat([tline;border],3,1)]; dotsp = find(printg=='.');
      numbers = find(dg); strdg = num2str(dg); dotsd = find(strdg~=' ');
      printg(dotsp(numbers)) = strdg(dotsd(numbers));
      disp(printg)
   
   end

case 'save'   % saves the current history in the current directory
              % for later recall via the command load
   curbd = get(hc(84,1),'Userdata');
   [pn,mfname,coru] = getfilename('.mat',curbd);
   if isempty(mfname), return, end

   save([pn mfname],'hst')
   set(hc(84,1),'Userdata',curbd+1);
   fprintf(['sudokon ',coru,'ated the MAT-file  ',mfname,...
                '  in the directory ',strrep(pn(1:end-1),'\','\\'),'\n']);

case 'load'   % restores the board to the end of the history brought
              % back from 

      % get a file name

   curbd = get(hc(84,1),'Userdata'); % used in serializing exported data
   mfname = ['board',num2str(curbd),'.mat']; curdir = pwd;
   if ~(curdir(end)==filesep), curdir = [curdir filesep]; end
   getfiletitle = 'Which saved board would you like to load?';
   [mfname,pn] = uigetfile([curdir mfname], getfiletitle);
   if isequal(mfname,0)||isequal(pn,0)
        % the user hit Cancel, so give up on this
      return
   end
   load([pn mfname])

         % check out that this is actually a workable history
   accepted = 0;    
   q = unique(hst(:,3));
   q(q==0|q==1|q==2|q==3|q==4|q==5|q==6|q==7|q==8|q==9)=[];
   if isempty(q)
      q = unique(hst(:,1));
      q(q==0|q==1|q==2)=[];
      if isempty(q)
         q = unique(hst(:,2));
         if ~any(q-fix(q))&&q(1)>0&&q(end)<82
            accepted = 1;
            [dg,p,hc] = bringback(hst,p,gr,hc);
            mfname(end-3:end) = [];
            set(hc(85,1),'Visible','on','String',['back to ',mfname])
         end
      end
   end

   if ~accepted
      set(hc(85,1),'Visible','on','String',['the file ''',...
               mfname,''' does not appear to be generated by sudokon.'])
   end

case 'prefer'  % makes choices for what exactly check? does
               % Userdata          action
               %    1         check correctness
               %    2         check correctness and print detail
               %    3         check number of solutions
               %    4         print all solutions (if any)
   temp = get(hc(84,3),'Userdata') + 1; if temp>4, temp = 1; end
   hc(85,3) = 0;
   switch temp
   case 1
      set(hc(83,3),'Tooltipstring','checks consistency of choices made so far')
      set(hc(84,3),'String','...choices made')
   case 2
      set(hc(84,3),'String','...choices; print')
      set(hc(83,3),'Tooltipstring',...
      'checks consistency of choices made so far and prints details')
      hc(85,3) = 1;
   case 3
      set(hc(84,3),'String','...# solutions')
      set(hc(83,3),'Tooltipstring','checks number of solutions')
   case 4
      set(hc(84,3),'String','...all solutions')
      set(hc(83,3),'Tooltipstring','prints out all solutions (if any)')
   end
   set(hc(84,3),'Userdata',temp)

case 'works'  % act on the selected cell

   answer = get(gcf,{'CurrentPoint','SelectionType'});
   k = get(gco,'Userdata');
   
   if hc(k,4) % the cell has been settled, so, show the undo button
      setdonot(hc(82,4),hc(k,2:3))
      set(hc(82,3),'Position',[hc(k,2:3)-w/2,w(1),w(2)/3],...
               'Visible','on','Userdata',k)
      set(hc(85,1),'visible','off')
   else
      switch hc(85,4)
         % hc(85,4) gives the 5 modes: clueless (-1), some help (0),
         % no help but only singletons (1), help with singletons (2), done (3).

      case {0,2}  % some help, hence this is a click on digit or else undo wish

         set(hc(85,1),'String',get(hc(84,2),'Userdata'))

         d = find(p(k,:)); 
         if length(d)==1
                % we are in the help mode and are looking at a singleton cell
                % that has not yet been settled.
            if isequal(answer{2},'normal') 
               hst(end+1,:) = [1,k,d];
               set(hc(82,3:4),'Visible','off')
               [dg,p,hc] = rm(k,d,dg,p,gr,hc);
            else   % anything other than a left click brings up the undo button
               setdonot(hc(82,4),hc(k,2:3))
               set(hc(82,3),'Visible','on',...
                     'Position',[hc(k,2:3)-w/2,w(1),w(2)/3],'Userdata',k)
               set(hc(85,1),'Visible','off')
            end
         else
          % check which digit was clicked
            dc = ...
           find(histc(answer{1}(1), [-inf, hc(k,2)+[-w(1)/6,w(2)/6], inf]))+...
           3*(3-...
             find(histc(answer{1}(2), [-inf, hc(k,3)+[-w(1)/6,w(2)/6], inf])));
               % ... and act accordingly
            if p(k,dc) % the digit is one of the possible choices
               switch answer{2}  % which should be 'normal' or 'alt'
               case 'normal'     % a left click, hence select this digit
                  hst(end+1,:) = [1,k,dc];
                  [dg,p,hc] = rm(k,dc,dg,p,gr,hc);
               case 'alt'        % a right click, hence delete this digit
                  p(k,dc) = 0; hst(end+1,:) = [2,k,dc];
                  text = get(hc(k,1),'string');
                  temp = [1 7 13 2 8 14 3 9 15]; text(temp(dc))=' ';
                  set(hc(k,1),'string',text,...
                              'Fontweight','normal','Foregroundcolor','k')
               otherwise % put up the undo button
                  setdonot(hc(82,4),hc(k,2:3))
                  set(hc(82,3),'Visible','on',...
                          'Position',[hc(k,2:3)-w/2,w(1),w(2)/3],'Userdata',k)
                  set(hc(85,1),'Visible','off')
               end

            else       % an empty space was clicked, hence put up undo button
               setdonot(hc(82,4),hc(k,2:3))
               set(hc(82,3),'Visible','on','Position',...
                  [hc(k,2:3)-w/2,w(1),w(2)/3],'Userdata',k)
               set(hc(85,1),'Visible','off')
            end
         end


      case {-1,1} % no help, hence show the keyboard
         setdonot(hc(82,4),hc(k,2:3))
         keyboard(k,hc,p,w); 
         set(hc(85,1),'String','click digit to select','Visible','on')
         set(p(82,:),'Tooltipstring','click to select',...
               'Callback','sudokon ''finis''');
      otherwise
         error('something wrong in sudokon')
      end
   end

case 'finis'  % make the selected digit the choice for this cell

   stored = get(gco,'Userdata');
   k = stored(1); d = stored(2);
   if ~p(k,d) % we are in the clueless state
               % but will refuse any inconsistent choice
      set(hc(85,1),'Visible','on',...
         'String',['your choice of ',num2str(d),' is inconsistent'])
   else
      set([p(82,:),hc(82,3:4)],'Visible','off')
      hst(end+1,:) = [1,k,d];
      [dg,p,hc] = rm(k,d,dg,p,gr,hc);
      set(hc(85,1),'String',get(hc(83,3),'Userdata'))
   end


case 'undo' % return to the stage before the last change made to this cell

   set(hc(82,3:4),'Visible','off')
   k = get(gco,'Userdata'); 
   last = find(hst(:,2)==k,1,'last');
   if ~hst(last,1) % this is one of the original givens, hence merely start
                   % from scratch
      last = find(hst(:,1),1);
   end
   hst(last:end,:) = [];

   [dg,p,hc] = bringback(hst,p,gr,hc);

case 'donot' % do nothing

   set([p(82,:),hc(82,3:4)],'Visible','off')
   set(hc(85,1),'Visible','on')

case 'closefigure'

   delete(findobj('Tag','sudokon Message Box'))
   return

case 'permute'

   sudokon(permute_it(dg))
   return

end

if hc(85,4)<3&&all(hc(1:81,4)) % we just finished
   hc(85,4) = 3;
   set(hc(85,1),'Visible','on', 'String','Congratulations!')
   for j=1:6
      set(hc(85,1),'Visible','off'), pause(.1)
      set(hc(85,1),'Visible','on'), pause(.1)
   end
end

set(hf,'Userdata',{hf,p,dg,gr,hc,hst})

function [ok,dg,p] = checkit(dg,p,gr,hc,action)
%CHECKIT check for inconsistencies and/or new singletons

switch action(1)
case 'h'
    ok = -1; ntries = 3; nrep = 4; pstart = p;
case {'c','a'}
   ntries = numel(find(dg==0)); nrep = 1; hc(85,2) = 0;
        % made sure that an earlier right click on hints doesn't generate
        % detailed printout now
end

if hc(85,3)
   if action(1)=='h', fprintf('\n detailed hints\n')
   else counts = zeros(1,6);
   fprintf(['\n',action,'\n rcb, bc, br, two, three   ',...
   'count occurrences of the following:\n rare digit ',...
   'removed from box(rcb), column(bc), row(br);\n all other choices removed',...
    ' from only pair(two)/triple(three)\n',...
    ' that can contain a certain pair/triple of digits.\n']);
   end
end
for tries=1:ntries

   for rep=1:nrep
    for r=1:27
       tt = sum(p(gr(:,r),:));
       if ~all(tt), ok = 0; return, end %inconcistency: some digit is excluded
                  
       sd = find(tt==1); %These are the digits that occur exactly once in this
                      % group, hence the corresponding k should be a singleton.
        for d = sd
          k = gr(p(gr(:,r),d)==1,r);
          if ~dg(k) % this should be a singleton
             if hc(85,3)&&action(1)=='h'
                dd = 1:9; dd(d) = [];
                if any(p(k,dd))||r<10
                   fprintf(' ** select cell %g|%g for digit %g\n',cellid(k),d)
                end
             end
             p(k,:) = 0;
             % also remove this digit from all the other cells in the two
             % other groups containing this cell
             j = ceil(k/9); i = k - 9*(j-1); % get corresp. col and row index
             c = ceil(i/3)+ 3*(ceil(j/3)-1); % get corresp. box index
             % recall that gr = [gr1 gr2 gr3], to be used as i, j+9, c+18
             p(unique(gr(:,[i j+9 c+18])),d) = 0;   p(k,d) = 1;
          end
       end
    
       tt = sum(p(gr(:,r),:));
       sp2 = find(tt==2); % these are the 'rare' digits in this group;
                         % if their sites also belong to another group,
                     % then this digit should be removed from any other site
                     % in that other group
       sp = [sp2 find(tt==3)]; lsp = numel(sp);
       kk = cell(1,lsp);
       for l = 1:lsp
             d = sp(l);
          k = gr(p(gr(:,r),d)==1,r); kk{1,l} = k;
          j = ceil(k/9); i = k - 9*(j-1); % get corresp. col and row index
          c = ceil(i/3)+ 3*(ceil(j/3)-1); % get corresp. box index
 
          p(k,d) = 0;
          if r<19, % the group is a column or row; the only possible other 
                   % group is a box
             if ~diff(c)
                if any(p(gr(:,18+c),d))
                   if hc(85,3)
                      if action(1)=='h'
                         if r<10
                            fprintf([' ** %g occurs in row %g only ',...
                           'in box %g|%g; remove it elsewhere in box\n'],...
                            d,r,ceil(i(1)/3),ceil(j(1)/3))
                         else
                            fprintf([' ** %g occurs in col %g only ',...
                           'in box %g|%g; remove it elsewhere in box\n'],...
                            d,r-9,ceil(i(1)/3),ceil(j(1)/3))
                         end
                      else  counts(2) = counts(2)+1;
                      end
                   end
                   p(gr(:,18+c),d) = 0;
                end
             end
              
          else     % the group is a box; depending on the exact location of
                   % the sites, the other group may be a row or a column
             if ~diff(i),      % the group is a row
                if any(p(gr(:,i),d))
                   if hc(85,3)
                      if action(1)=='h'
                         fprintf([' ** %g occurs in box %g|%g only',...
                         ' in row %g; remove it elsewhere in row\n'],...
                            d,ceil(i(1)/3),ceil(j(1)/3),i(1))
                      else counts(4) = counts(4)+1;
                      end
                   end
                   p(gr(:,i),d) = 0;
                end
             elseif ~diff(j), % the group is a column
                if any(any(p(gr(:,j+9),d)))
                   if hc(85,3)
                      if action(1)=='h'
                         fprintf([' ** %g occurs in box %g|%g only',...
                         ' in col %g; remove it elsewhere in col\n'],...
                            d,ceil(i(1)/3),ceil(j(1)/3),j(1))
                      else counts(3) = counts(3)+1;
                      end
                   end
                   p(gr(:,j+9),d) = 0;
                end   
             end        
          end
          p(k,d) = 1;
       end
                 % if two share the two same sites (or three the
                 % same three), remove all others from those sites.
       for i=1:lsp-1
          for j=i+1:lsp
             kkk = unique([kk{i}(:);kk{j}(:)]);
             switch numel(kkk)
             case 2
                dd = 1:9; dd(sp([i j])) = [];
                if any(any(p(kkk,dd)))
                   if hc(85,3)
                      if action(1)=='h', cid = cellid(kkk).';  
                      fprintf([' ** remove all but %g and %g from cells',...
                      ' %g|%g and %g|%g\n'],sort(sp([i j])),cid(:))
                      else counts(5)=counts(5)+1;
                      end
                   end
                   p(kkk,dd) = 0; 
                end
             case 3 
                for jj=j+1:lsp
                   kkkk = unique([kkk;kk{jj}(:)]);
                   if numel(kkkk)==3
                      dd = 1:9; dd(sp([i j jj])) = [];
                      if any(any(p(kkkk,dd)))
                         if hc(85,3)
                            if action(1)=='h', cid = cellid(kkkk).';  
                               fprintf([' ** remove all but %g, %g, ',...
                               'and %g from cells %g|%g, %g|%g, and ',...
                               '%g|%g\n'],sort(sp([i j jj])),cid(:))
                            else counts(6)=counts(6)+1;
                            end
                         end
                         p(kkkk,dd) = 0; 
                      end
                   end
                end
             end
          end
       end
 
       tc = sum(p(gr(:,r),:),2);
       if ~all(tc), ok = 0; return, end %inconcistency: some cell is now empty

       sc = tc==1&dg(gr(:,r))==0; %these are the singleton cells in the
                                    % group not yet settled.
       for k=gr(sc,r).'
          d = find(p(k,:)==1); 
            % remove this digit from all the other cells in the three
            % groups containing this cell
          j = ceil(k/9); i = k - 9*(j-1); % get corresp. col and row index
          c = ceil(i/3)+ 3*(ceil(j/3)-1); % get corresp. box index
            % recall that gr = [gr1 gr2 gr3], to be used as i, j+9, c+18
          kk = unique(gr(:,[i j+9 c+18])); p(k,d) = 0;
          if any(any(p(kk,d)))
             p(kk,d) = 0; 
             if hc(85,3)&&action(1)=='h'
                fprintf(' ** select cell %g|%g for digit %g\n',cellid(k),d)
             end
          end
          p(k,d) = 1;
       end
 
       sc = find(tc==2); % these are the cells in the group having exactly
                         % two choices left. If these choices are the same
                         % for two such cells, then the two choices should 
                         % be removed from all other cells of any group
                         % containing these two cells.
               % If we are looking for good hints, append the cells in the
               % group having exactly three choices left.
       if action(1)=='h', sc = [sc; find(tc==3)]; end
       lsc3 = numel(sc); kk = gr(sc,r);
       for i=1:lsc3-1
          for j=i+1:lsc3
             ddd = unique([find(p(kk(i),:)),find(p(kk(j),:))]);
             if numel(ddd)==2
                kkk = 1:9; kkk(sc([i j])) = [];
                if hc(85,3)&&action(1)=='h'&&any(any(p(gr(kkk,r),ddd)))
                   fprintf([' ** %g and %g are the only choices in two ',...
                       'cells of ', getgroup(r),';\n'],ddd)
                   fprintf(['   remove these digits from all other cells',...
                         ' in ', getgroup(r),'.\n'])
                end
                p(gr(kkk,r),ddd) = 0;
             elseif action(1)=='h'&&numel(ddd)<5
                for jj=j+1:lsc3
                   dddd = unique([ddd,find(p(kk(jj),:))]);
                   if numel(dddd)==3
                      kkk = 1:9; kkk(sc([i j jj])) = [];
                      if hc(85,3)&&action(1)=='h'&&any(any(p(gr(kkk,r),dddd)))
                         fprintf([' ** %g, %g and %g are the only choices ',...
                                  'in three cells of ',getgroup(r),';\n'],dddd)
                         fprintf(['   remove these digits from all other',...
                                  ' cells in ', getgroup(r),'.\n'])
                      end
                      p(gr(kkk,r),dddd) = 0;
                   elseif numel(dddd)==4
                      for jjj=jj+1:lsc3
                         if all(ismember(find(p(kk(jjj),:)),dddd))
                            kkk = 1:9; kkk(sc([i j jj jjj])) = [];
                            if hc(85,3)&&action(1)=='h'&&any(any(p(gr(kkk,r),dddd)))
                              fprintf([' ** %g, %g, %g, and %g are the only '...
                            'choices in four cells of ',getgroup(r),';\n'],dddd)
                              fprintf(['   remove these digits from all ',...
                                       'other cells in ',getgroup(r),'.\n'])
                            end
                            p(gr(kkk,r),dddd) = 0;
                        end
                      end
                   end 
                end
             end
          end
       end
    end % for r

    % here is the list of newly found singletons
    ns = find(dg(:)==0&sum(p(1:81,:),2)==1); 
    % terminate this loop as soon as there are newly found singletons (since
    % this loop is repeated only in case we are looking for hints and, in that
    % case, it is easier to understand a suggestion resulting from just the
    % first pass through this loop).
    if ~isempty(ns), break, end
   end % for rep
   if hc(85,3)&&~(action(1)=='h')
      counts(1) = counts(1)+1;
      fprintf('%g:  %grcb, %gbc, %gbr, %gtwo, %gthree',counts),
   end

   if isempty(ns) % try for x-wings
     rbeg = 1; rend = 9; kadd = 9;
     for rs=1:2
      for r=rbeg:rend-1
         sp2 = find(sum(p(gr(:,r),:))==2);
          % these are the 'rare' digits in this group;
         for l=1:numel(sp2)
            d = sp2(l); k = find(p(gr(:,r),d)==1);
            for s=r+1:rend
               if p(gr(k(1),s),d) && p(gr(k(2),s),d)
                  temp = 1:9; temp(k) = [];
                  if ~any(p(gr(temp,s),d)) % can erase d from columns k
                     if hc(85,3), temp1 = p(gr(:,kadd+k),d); end
                     p(gr(:,kadd+k),d) = 0; p(gr(k,[r,s]),d) = 1;
                     if hc(85,3)&&any(temp1-p(gr(:,kadd+k),d)) 
                        if rs==1  
                           fprintf('\n  X-wing for %g at (%g,%g),(%g,%g)',...
                               d,r,k(1),s,k(2))
                           fprintf(['; remove the digit %g elsewhere in',...
                           '\n   columns %g and %g'],...
                               d,k)
                        else 
                           fprintf('\n  X-wing for %g at (%g,%g),(%g,%g)',...
                               d,k(1),r-9,k(2),s-9)
                           fprintf(['; remove the digit %g elsewhere in',...
                           '\n    in rows %g and %g.'],...
                              d,k)
                        end
                     end
                     break
                  end
               end
            end
         end
      end
      rbeg = 10; rend = 18; kadd = 0;
     end
     ns = find(dg(:)==0&sum(p(1:81,:),2)==1); 
     if ~isempty(ns)&&action(1)=='h'&&hc(85,3)
        for k=ns.'
           fprintf('\n ** select cell %g|%g for digit %g',...
                    cellid(k),find(p(k,:)==1))
        end
        fprintf('\n')
     end
   end % for Xwings

   switch action(1)
 
   case 'h'
      isemptyns=false; if isempty(ns), isemptyns=true; end           
      if hc(85,3), fprintf('\n'), end
      nc = find(sum(pstart-p,2));
      if isempty(nc)&&isemptyns, break, end
      ok = 1;
      if isemptyns||hc(85,2), ok = 2; ns = nc; hc(85,2) = 1; end
      for k=ns(:)'  % replace each new singleton by a star
                    % or each deletable choice by a circle
         A = get(hc(k,1),'String');
         temp = [1 7 13 2 8 14 3 9 15]; 
         if hc(85,2), A(temp(p(k,:)~=pstart(k,:))) = 'o';
         else A(temp(p(k,:)==1)) = '*';
         end
         set(hc(k,1),'String',A,'Fontweight','bold','Foregroundcolor','r')
      end
      if ~isemptyns, break, end
   
   case {'c','a'}
      if isempty(ns), break, end
      lns = length(ns); dns = zeros(lns,1);
      for r=1:lns
         dns(r) = find(p(ns(r),:));
      end
      if hc(85,3)
         j = ceil(ns/9); i = ns - 9*(j-1); % get corresp. col and row index
         ij = [i(:)'; j(:)'];
          
         fprintf(['\n      insert singleton at%2g|%1g%2g|%1g%2g|%1g%2g|',...
                       '%1g%2g|%1g%2g|%1g%2g|%1g%2g|%1g%2g|%1g%2g|%1g'],ij(:))
         fprintf(['\n        with value       %3g%4g%4g%4g%4g%4g%4g%4g',...
                                 '%4g%4g'],dns)
         fprintf('\n')
      end
      [dg,p,hc] = rm(ns,dns,dg,p,gr,hc,action);
   end % for switch action(1)
   if all(dg), break, end
end % for tries

if action(1)=='c' || action(1)=='a'
   sz = find(dg==0);
   if isempty(sz), ok = 1;
      if action(1)=='a'
         p(82,1) = p(82,1)+1;
         if p(82,1)==1
            set(hc(85,1),'Visible','On',...
            'String', 'There is one solution.'); 
         else
            set(hc(85,1),'Visible','On',...
            'String', sprintf('There are %g solutions.',p(82,1))); 
         end 
         pause(.01)
         if action(2)=='p', fprintf('\n solution %g :\n',p(82,1)), disp(dg),
         end 
         ok = 0;
         if p(82,1)==50
            button = questdlg('Continue?',...
            '50 solutions found so far; continue?',...
            'No','Yes','No');
            if button(1)=='N', ok = 1; p(82,1) = -p(82,1); return, end
         end
      end
   else % at some locations, we still don't have a singleton
        % so, find a nonsingleton solution with the smallest number
        % of possibilities and run through each of them until the one
        % that works is found. Note that this could lead to further
        % branchings (and should be set up that way). The trick is to
        % interpret any inconsistency found merely as a wrong choice
        % made along the way, calling for a different choice at an 
        % earlier point.
      
      % find the first digit appearing the least in settled cells,
      % [nm,dig] = min(sum(p(dg>0,:))); sz(~p(sz,dig)) = [];
      % find the first digit appearing the most in unsettled cells,
      %[~,dig] = max(sum(p(sz,:))); sz(~p(sz,dig)) = [];
      [ignored,dig] = max(sum(p(sz,:))); sz(~p(sz,dig)) = [];
      % then determine the first shortest nonsingleton containing it:
      [nm,ni] = min(sum(p(sz,:),2)); k = sz(ni);
      j = ceil(k/9); i = k - 9*(j-1); cc = find(p(k,:)>0);
      %  cc(cc==dig)=[]; cc = [dig,cc]; % this attempt didn't work at all
      if hc(85,3)
           fprintf(['\ntry the %g remaining possibilities at location %g|%g:'...
                    '  %g,%g,%g,%g'],nm,i,j,cc)
      end
      for nn=1:nm
         if hc(85,3)
            fprintf('\n   try possibility %g, namely %g, at location %g|%g',...
               nn,cc(nn),i,j)
         end
         [ndg,np,hc] = rm(k,cc(nn),dg,p,gr,hc,action);
         [ok,ndg,np] = checkit(ndg,np,gr,hc,action);
         if action(1)=='a', p(82,1) = np(82,1); end
         if ok, dg = ndg; p = np; return, end
      end
   end
end

function g = examples(n)
%EXAMPLES provide a sample puzzle

switch n
case 1
   mssg = 'this is a simple puzzle, rating 2';
   g = [
   8 0 0 5 0 0 3 2 0 
   7 0 3 1 0 0 4 0 0 
   1 2 0 0 0 9 0 0 8 
   0 9 0 0 0 0 0 1 0 
   6 5 0 0 9 3 0 8 0 
   0 8 0 6 2 0 0 9 3 
   0 0 6 0 0 8 1 0 2 
   2 0 0 4 0 0 0 6 7 
   0 7 4 0 0 6 0 0 5 ] ; % 2 inserts, 1|0|0|2|4; 0
case 2
   mssg = 'this is a randomly made up puzzle, rating 3';
   g=[0 0 0 0 0 5 3 0 2 
   7 1 0 3 0 0 8 0 0 
   0 6 0 0 0 2 0 0 0 
   0 3 0 0 0 0 0 4 0 
   2 0 0 4 0 3 0 0 9 
   0 0 9 0 7 0 0 5 0 
   9 0 0 6 0 0 0 0 0 
   0 0 1 0 3 7 0 0 8 
   0 4 0 0 8 0 0 0 1 ];  % 3 inserts: 2two, 9|4|5|4|6; 0
case 3
   mssg = 'this is a medium-simple puzzle, rating 4';
   g = [
   0 7 8 0 1 0 9 0 4 
   0 0 0 0 0 0 0 0 0 
   3 0 2 0 0 0 6 7 0 
   0 4 0 1 0 9 0 0 7 
   0 0 6 2 0 8 4 0 0 
   0 2 0 5 0 3 0 0 8 
   0 0 1 0 0 0 0 0 0 
   9 0 0 0 0 0 2 1 0 
   0 6 7 0 9 0 5 0 3 ]; % 4 inserts: 2two, 8|2|2|4|2;
case 4
   mssg = 'this is a medium-hard puzzle, rating 5';
   g=[3 9 0 2 0 0 8 0 0
   0 0 0 7 0 0 0 2 9
   7 0 0 3 0 0 0 5 0
   0 0 0 4 1 2 0 0 0
   0 0 0 0 0 0 9 3 0
   0 5 6 0 0 0 0 0 0
   4 1 0 0 0 6 0 0 0
   0 7 0 0 0 4 0 0 2
   0 0 3 0 0 7 0 8 4]; % 5 inserts: 2two, 12|2|1|3|1;
case 5
   mssg = 'this is a medium-hard puzzle, rating 5';
   g=[0 8 3 1 0 0 0 5 0 
      0 0 0 0 0 0 2 6 0 
      5 0 0 9 4 0 0 0 0 
      0 0 0 2 0 3 0 0 0 
      9 7 0 0 0 0 0 0 0 
      0 0 0 0 0 0 0 7 8 
      0 4 6 0 0 0 0 0 0 
      0 0 0 0 8 1 0 0 9 
      0 3 0 0 0 4 6 1 0 ] ; % 5 inserts, 11|3|2|5|3
case 6
   mssg = 'this is a hard puzzle, rating 7';
   g=[8 0 0 0 9 0 0 0 0 
   0 0 0 6 2 0 0 0 0 
   0 0 0 0 0 1 0 3 9 
   0 0 0 1 3 0 0 8 0 
   2 9 7 0 5 0 0 0 0 
   0 8 0 0 0 0 5 0 0 
   0 2 0 0 0 0 0 0 0 
   0 0 0 0 0 0 7 0 1 
   0 0 3 0 0 7 6 0 5 ] ; % 7 inserts: 2two, 10|5|4|4|2
case 7
   mssg = 'this is a hard puzzle, rating 7';
   g=[4 6 0 0 0 1 0 0 0 
     0 0 2 0 9 6 0 0 0 
     0 3 0 0 0 0 0 6 8 
     0 0 0 6 0 7 0 0 0 
     0 0 0 0 0 0 0 3 7 
     5 1 0 0 0 0 0 0 0 
     0 0 0 7 1 0 9 0 0 
     8 4 0 0 0 0 0 5 0 
     0 0 0 3 0 0 0 2 4 ]; % 7 inserts: 2two, 12|5|4|3|3
case 8
   mssg = 'this is a very hard puzzle, rating 8';
   g=[0 1 0 0 2 0 0 0 4 
   0 9 0 0 0 0 5 0 0 
   0 0 2 0 0 3 0 0 0 
   2 0 7 1 0 0 8 0 0 
   0 0 0 0 9 0 2 0 5 
   0 0 0 7 3 0 0 0 1 
   1 0 0 0 0 7 0 0 0 
   0 0 4 0 0 0 6 0 0 
   0 0 8 0 0 6 0 9 0 ] ;  % 8 inserts: 3two, 11|2|1|5|5
case 9
   mssg = 'this is a very, very hard puzzle (one fourer), rating 9';
   g=[ 3 5 0 0 0 0 6 0 1 
   2 0 0 0 0 0 5 0 8 
   0 0 9 0 0 0 0 7 0 
   5 0 0 2 0 0 0 0 0 
   0 0 0 0 3 4 0 0 0 
   8 1 0 0 0 5 0 0 9 
   0 0 0 0 0 1 8 0 0 
   0 0 0 0 2 0 0 0 7 
   1 0 6 3 9 0 0 0 0 ];  % 9 inserts: 1two, 6|3|3|5|5
case 10
   mssg = 'this puzzle requires use of the X-wing, rating 9';
  g=[0 6 5 0 2 0 0 0 9 
   0 0 0 4 0 0 0 0 0 
   7 2 0 0 0 5 0 3 0 
   0 0 9 0 0 0 3 8 0 
   2 0 0 0 0 0 0 0 7 
   0 5 8 0 0 0 2 0 0 
   0 1 0 7 0 0 0 9 6 
   0 0 0 0 0 6 0 0 0 
   6 0 0 0 9 0 8 5 0 ] ; % 9|2|1|4|6
case 11
   mssg = 'this puzzle is surprisingly hard, yet rating 5';
   g=[0 0 0 0 0 8 0 0 0 
   6 4 1 3 0 0 0 8 0 
   0 8 0 0 7 0 1 3 0 
   4 2 0 0 6 7 0 0 0 
   0 0 0 2 0 0 4 0 0 
   0 0 7 0 0 0 0 0 6 
   0 9 0 0 0 0 0 0 0 
   5 0 0 0 0 1 0 4 9 
   0 0 3 4 8 9 0 1 0 ]; % 5 inserts: 12|2|0|5|8
case 12
   mssg = 'this puzzle is the hardest here; must guess with only 34 cells settled; rating 12';
   g=[0 0 0 1 0 2 3 0 4
   0 0 0 0 0 0 0 0 0
   0 0 0 5 0 6 1 0 7
   6 0 1 0 0 0 7 0 8
   0 0 0 0 0 0 0 0 0
   8 0 5 0 0 0 4 0 3
   5 0 6 2 0 1 0 0 0
   0 0 0 0 0 0 0 0 0
   2 0 4 6 0 3 0 0 0]; % 3+3+2+1+1+5 inserts: 2two
otherwise
   mssg = 'interesting puzzle, rating 7';
   g=[0 0 0 0 2 7 0 0 0
   1 0 5 0 0 0 0 0 0
   0 0 0 0 0 0 0 4 2
   0 0 0 0 3 4 0 0 0
   9 0 3 7 8 6 0 0 0
   0 8 0 0 0 0 4 6 0
   0 3 2 0 0 0 5 0 0
   0 0 0 3 4 2 0 7 8
   0 0 0 0 1 9 0 0 0]; % 7 inserts: 1two, 14|2|3|6|8
end

         % now randomize the puzzle, for greater variety. 
g = permute_it(g);

temp = msgbox(mssg); set(temp,'Tag','sudokon Message Box')

function keyboard(k,hc,p,w)
%KEYBOARD generate a keyboard on cell k

set([hc(82,3),p(82,:)],'Visible','off')
x = [-1 0 1 -1 0 1 -1 0 1]*(w(1)/3)+(hc(k,2)-w(1)/6);
y = [1 1 1 0 0 0 -1 -1 -1]*(w(2)/3)+(hc(k,3)-w(2)/6);
hh = 1:9; if ~hc(85,4), hh(~p(k,:)) = []; end
for j=hh
   set(p(82,j),'Visible','on',...
     'Position',[x(j),y(j),w/3],...
     'Userdata',[k,j]);
end

function g = permute_it(g)
%PERMUTE_IT permutes g while preserving all groups
% This means choosing, for  both rows and columns, a permutation that maps a 
% 3-group to a 3-group: 
rows = [randperm(3) randperm(3) randperm(3)] + ...
        reshape(repmat(3*(randperm(3)-1),3,1),1,9);
cols = [randperm(3) randperm(3) randperm(3)] + ...
        reshape(repmat(3*(randperm(3)-1),3,1),1,9);
% ... as well as an arbitrary 9-permutation for permuting the 9 digits
perm9 = [1, randperm(9)+1];
% ... to give the acceptable variation 
g(rows,cols) = perm9(g+1)-1;
% ... further `changed' by, possibly, switching to the transposed
perm2 = randperm(2)-1; if perm2(1), g = g.'; end

%function [dg,p,hc] = rm(s,ds,dg,p,gr,hc,~)
function [dg,p,hc] = rm(s,ds,dg,p,gr,hc,action)
%RM remove the value ds(j) of the singleton s(j) from all of its groups

for r=1:length(s)
   k = s(r);
   j = ceil(k/9); i = k - 9*(j-1); % get corresp. col and row index
   c = ceil(i/3)+ 3*(ceil(j/3)-1); % get corresp. cell index
   d = ds(r);                      % get the given digit

   % zero out that digit in the three groups that k belongs to

   p(unique(gr(:,[i j+9 c+18])),d) = 0;

   % also zero out all other digits at k, but reintroduce d at k

   p(k,:) = 0; p(k,d) = 1;
 
   % update dg
   dg(k) = d; hc(k,4) = 1;

   if nargin==6 % update the display
      set(hc(k,1),'Foregroundcolor','k','Fontsize',30,'Fontweight','bold',...
                  'String',d)
      update(hc,p)

      % if this is the ninth of this digit being settled, change their color to
      % blue
      cds = find(p(1:81,d));
      if numel(cds)==9&&all(hc(cds,4)==1)
         set(hc(cds,1),'ForegroundColor','b'), end

      if ~hc(85,4)&&all(sum(p(1:81,:),2)==1) % there are only singletons left,
                 % so, for greater pleasure, turn off all cells not yet settled,
                 % being prepared to turn them on again when a hint is asked for
         hc(85,4) = 1;
         set(hc(85,1),'Visible','on',...
                         'String',{'Only one choice left for each cell;'; ...
                             get(hc(83,3),'Userdata')})
         set(hc(~hc(1:81,4),1),'String','')
      end

   end
        
end

function update(hc,p)
%UPDATE update display

if ~hc(85,4) % if we are in help mode
   C = repmat(' ',3,5);
   for k=1:81
      if ~hc(k,4) % only those not yet settled
         A = '123456789'; A(~p(k,:)) = ' '; C(:,[1 3 5]) = reshape(A,3,3)';
         set(hc(k,1),'String',C,'Fontweight','normal','Foregroundcolor','k')
      end
   end
end

function [dg,p,hc] = bringback(hst,p,gr,hc)
%BRINGBACK restore to the end of the given HST

p(1:81,:) = 1;
dg = zeros(9,9);
A = reshape('147258369',3,3);
set(hc(1:81,1),'Visible','on','Fontsize',10,'Fontweight','normal',...
'ForegroundColor','k','String',A)


hc([1:81,85],4) = 0;
   % enter the givens, if any
s = find(~hst(:,1));
if ~isempty(s)
   [dg,p,hc] = rm(hst(s,2),hst(s,3),dg,p,gr,hc);
end
s = find(hst(:,1)==1);
if ~isempty(s)
   [dg,p,hc] = rm(hst(s,2),hst(s,3),dg,p,gr,hc);
end
s = find(hst(:,1)==2);
p(hst(s,2)+(hst(s,3)-1)*82) = 0; 

update(hc,p)

if ~hc(85,4)
   set(hc(85,1),'Visible','on','String',get(hc(84,2),'Userdata'))
end

function setdonot(handle,xy)
%SETDONOT set the do-nothing uicontrol

W = .11;
set(handle,'Position',[xy-W/2,W,W],'Visible','on');

function [pn,mfname,coru] = getfilename(suffix,curbd)
%GETFILENAME get a filename (including directory) for storing stuff

mfname = ['board',num2str(curbd+1),suffix]; coru = ''; curdir = pwd;
if ~(curdir(end)==filesep), curdir = [curdir filesep]; end
getfiletitle = ['Choose a Name for the ',suffix,'-File'];
while isempty(coru)
   [mfname,pn] = uiputfile([curdir mfname], getfiletitle);
   if isequal(mfname,0)||isequal(pn,0)
        % the user hit Cancel, so give up on this
      mfname = ''; return
   else
      checked = 0;
      if length(mfname)>2&&isequal(mfname(end-3:end),suffix) 
         % strip off terminal suffix
         mfname(end-3:end) = []; checked = 1;
      end
      fullfilename = [pn mfname,suffix];
      if ~exist(fullfilename, 'file')
            coru = 'cre';
      else
         if checked
            anss = 'Yes';
         else        
            anss = questdlg([which(fullfilename),' already exists.';...
               '  Do you want to replace it?'],...
               [suffix,'-file already exists ...'], 'No','Yes','No');
         end
         if isequal(anss,'Yes')
               coru = 'upd';
         end
      end
   end
end

function groupr = getgroup(r)
%GETGROUP gets text as to what group exactly r represents

if r<10, groupr = ['row ',num2str(r)];
elseif r<19, groupr = ['column ',num2str(r-9)];
else r = r-18; j = ceil(r/3); i = r-3*(j-1);
     groupr = ['box ',num2str(i),'|',num2str(j)];
end

function cij = cellid(k)
%CIJ returns the row(s) and column(s) of the given cell(s) k

j = ceil(k/9); i = k - 9*(j-1); % get corresp. col and row index
cij = [i,j];

Contact us at files@mathworks.com