No BSD License  

Highlights from
Sudoku

image thumbnail
from Sudoku by Stefan Bleeck
Sudoku for MATLAB.

sudoku(params,command)
% sudoku, the game in matlab
% copywrite Stefan Bleeck 2005
% bleeck@gmail.com


% strategy: there are two ways to find out a solution. The first I call the
% "possibles" Each cell is tested which entries it _can_ have. If it only
% has one option, then this is it and it will be displayed in small green
% the other is the "necessary" entry, which is defined by when the other
% squares around it define this as the only possible entry. this is defined
% by crossing out all not possible options and see if there is only one
% left.
% the solver uses both in turn. If no more possible or necessary are found
% then the solver guesses one entry (the one with the least options) and
% goes recursivly through the tree until if ends in an
% impossible situation or with a solution. If it ends impossible, it jumpes
% back up and tries the next option.


% parameter structure:
% sudoku_data.entry_value(1:9,1:9) = state
% sudoku_data.number_possible(1:9,1:9)nr pencilmarks
% sudoku_data.possible_entries(1:9,1:9,1:9)= pencilmarks
% sudoku_data.necessary_entries(1:9,1:9) = necessary entries

function sudoku(params,command)
global sudoku_data;
global fignr;
global data_history;
global current_step;
global max_step;

if nargin<1
    params=parameter('sudoku controls');
    params=add(params,'panel','modify',4);
    params=add(params,'button','empty puzzle','sudoku(params,''make new'')');
    params=add(params,'filename','file name','sudoku_saved');
    params=add(params,'button','save puzzle','sudoku(params,''save'')');
    params=add(params,'button','load puzzle','sudoku(params,''load'')');
    %     params=add(params,'bool','show pencilmarks',1);

    params=add(params,'panel','generate',2);
    params=add(params,'pop-up menu','difficulty',{'difficult','mild','easy'},2);
    params=add(params,'button','generate puzzle','sudoku(params,''generate new'')');

    params=add(params,'panel','help and solve',4);
    params=add(params,'button','check possible','sudoku(params,''check possible'')');
    params=add(params,'button','check necessary','sudoku(params,''check necessary'')');
    params=add(params,'button','hide hints','sudoku(params,''hide hints'')');
    params=add(params,'button','solve','sudoku(params,''solve'')');

    params=add(params,'panel','control',2);
    params=add(params,'button','go forward','sudoku(params,''go forward'')');
    params=add(params,'button','go back','sudoku(params,''go back'')');

    % display an empty one at the beginning
    sudoku(params,'make new');

    params=setposition(params,'northeast');
    parametergui(params);
    movegui('northeast');
    
    return
end

if isequal(command,'save')
    filename=get(params,'file name');
    save(filename,'sudoku_data');
    return
end

if isequal(command,'load')
    clear sudoku_data
    filename=get(params,'file name');
    load(filename,'sudoku_data');

    fignr=234234;
    figure(fignr);
    set(gcf,'keypressfcn',@presskeysudoku);
    set(gcf,'WindowButtonDownFcn',@pressbuttonsudoku);
    set(gcf,'units','normalized');
    current_step=1;
    max_step=1;
    data_history{1}=sudoku_data;
    sudoku(params,'hide hints');
    plot_sudoku(sudoku_data);
    return
end

if isequal(command,'make new')
    sudoku_data.entry_value=zeros(9,9);  % all fixed values
    sudoku_data.number_possible=zeros(9,9);    % number of pencilmarkers
    sudoku_data.possible_entries=zeros(9,9,9);  % pencilmarkers for each square
    sudoku_data.necessary_entries=zeros(9,9);  % necessary (forced) entries for each square
    sudoku_data.current_selected_i=0;
    sudoku_data.current_selected_j=0;
    current_step=1;
    max_step=1;
    data_history{1}=sudoku_data;

    fignr=234234;
    figure(fignr);
    set(gcf,'name','Sudoku');
    set(gcf,'keypressfcn',@presskeysudoku);
    set(gcf,'WindowButtonDownFcn',@pressbuttonsudoku);
    set(gcf,'units','normalized');

    plot_sudoku(sudoku_data);
    return
end


if isequal(command,'generate new')
    % ingenious idea to avoid not unique ones: solve it and transposed. If the
    % same then high probability that they are unique
    global recursion_depth;

    unique=0;
    while ~unique
        recursion_depth=0;
        sudoku_data=fill_random;% generate a filled puzzle
        switch get(params,'difficulty')
            case 'easy'
                sudoku_data=delete_random(sudoku_data,5,2);
            case 'mild'
                sudoku_data=delete_random(sudoku_data,6,2);
            case 'difficult'
                sudoku_data=delete_random(sudoku_data,7,2);
        end

        dr1=solver(sudoku_data);
        dr2=solver(sudoku_data');
        dr2=dr2';

        % if identical then unique
        if sum(sum(dr1.entry_value==dr2.entry_value)) == 81
            unique=1;
%         else
%             sum(sum(dr1.entry_value==dr2.entry_value))
        end
    end
    sudoku(params,'hide hints');
    plot_sudoku(sudoku_data);
    return
end

if isequal(command,'check necessary')
    sudoku_data=check_necessary(sudoku_data);
    plot_sudoku(sudoku_data);
    return
end

if isequal(command,'check possible')
    sudoku_data=check_possible(sudoku_data);
    plot_sudoku(sudoku_data);
end

if isequal(command,'hide hints')
    sudoku_data.number_possible=zeros(9,9);    % number of pencilmarkers
    sudoku_data.possible_entries=zeros(9,9,9);  % pencilmarkers for each square
    sudoku_data.necessary_entries=zeros(9,9);  % necessary (forced) entries for each square
    plot_sudoku(sudoku_data);
end

if isequal(command,'go back')
    if current_step>1
        current_step=current_step-1;
        sudoku_data=data_history{current_step};
        plot_sudoku(sudoku_data);
        sudoku(params,'hide hints');
    end
end

if isequal(command,'go forward')
    if current_step<max_step
        current_step=current_step+1;
        sudoku_data=data_history{current_step};
        plot_sudoku(sudoku_data);
        sudoku(params,'hide hints');
    end
end


if isequal(command,'solve')
    global recursion_depth;
    recursion_depth=0;
    [sudoku_data,solved]=solver(sudoku_data);
    if solved
        plot_sudoku(sudoku_data);
    else
        disp('not solved!');
    end
    sudoku(params,'hide hints');
    plot_sudoku(sudoku_data);
end



function [solve_r,solved] = solver(solve_r)
% Copyright Stefan Bleeck 2005
% Solves the whole thing by going through first checking possibles and then
% checking necessary and filling them in. If a point is reached where it is
% stuck, go recursive.

global recursion_depth, global recursion_saved;

if isempty(recursion_depth) recursion_depth = 0; end

recursion_depth = recursion_depth+1;
solved = 0;

while ~solved
	sumbefore = sum(sum(solve_r.entry_value));
	solve_r = check_possible(solve_r);
	solve_r = check_necessary(solve_r);

	% check if anything is found
	fpos1 = find(solve_r.number_possible == 1);
   if ~isempty(fpos1) % cells with only one possible entry
   	u = solve_r.possible_entries(:,:,1);
      solve_r.entry_value(fpos1) = u(fpos1);
      end
   fnec = find(solve_r.necessary_entries > 0);
   if ~isempty(fnec) % necessary ones
      solve_r.entry_value(fnec) = solve_r.necessary_entries(fnec);
   end
  	
   sumafter = sum(sum(solve_r.entry_value));
   if sumafter == sumbefore % no change - go recursive with one more filled in
 		% find one with the fewest options
      minopt=min(min(solve_r.number_possible(find(solve_r.number_possible>0))));
   	if isempty(minopt) % the solution was not found in this recursion
      	solved = 0;
         recursion_depth = recursion_depth-1;
         return % up one level
		end
     	for i = 1:9
      	for j = 1:9
         	if solve_r.number_possible(i,j) == minopt
            	rn = solve_r; % work in a copy
               for k = 1:minopt % it must be one
               	rn.i = i; rn.j = j; rn.k = k; rn.minopt = minopt;
                  recursion_saved{recursion_depth} = rn;
                  rn.entry_value(i,j)=rn.possible_entries(i,j,k); % try that one
                  [rn,solved] = solver(rn); % find recursively
                  if solved
							solve_r = rn; % that was the solution      
                     return
                  else % carry on ...
							rn = recursion_saved{recursion_depth};  % next try                            
							i = rn.i; j = rn.j; k = rn.k; minopt = rn.minopt;
                  end
           		end                        
             % if here, no solution was found in this branch - go back
               solved = 0;
               recursion_depth = recursion_depth-1;
               return   
				end
        	end
   	end
   end
 	if sum(sum(solve_r.entry_value)) == 405
       solved = 1;
   end
end % - while loop

% ***********************************************************************

function sudoku_data=check_possible(sudoku_data)
% For each cell, counts the number of possible entries by checking the row,
% column and sub-square that contain it. Bleek's function needs an additional
% condition to avoid invalid entries.

sudoku_data.number_possible=zeros(9,9); % clear results
sudoku_data.possible_entries=zeros(9,9,9); % clear results

v = sudoku_data.entry_value; % use a copy

for i = 1:9
	for j = 1:9
      count=1;
      xx = 0;
		for x = 1:9 % the number we are looking at
			if v(i,j) == 0
				is_possible=1;
				if is_possible % check its row
					if ~isempty(find(v(i,:) == x)) is_possible = 0; end
				end
				if is_possible % check its column
					if ~isempty(find(v(:,j) == x)) is_possible = 0; end
				end
				if is_possible % check its sub-square

					ii = 3*fix((i-1)/3)+1;
					jj = 3*fix((j-1)/3)+1;
					if ~isempty(find(v(ii:ii+2,jj:jj+2) == x)) is_possible = 0; end
				end
				if is_possible
               sudoku_data.possible_entries(i,j,count) = x;
               xx = x;
					count=count+1;
				end
			end
      end
      count = count-1;
      sudoku_data.number_possible(i,j) = count;
      if count == 1
  		% update v to avoid invalid entries in later cells
        v(i,j) = xx; 
      end
	end
end


% ***********************************************************************

function sudoku_data = check_necessary(sudoku_data);
% For each number, checks every cell for necessary entries and crosses out all that
% can't contain it. If only one of the cells in its sub-square remains free, that
% cell must contain it.

sudoku_data.necessary_entries = zeros(9,9); % clear results

v = sudoku_data.entry_value; % use a copy

for x = 1:9 % the number we are looking at
	% cross out all the cells that are impossible by entering ones in them
	crossed = zeros(9,9);  % initially none is crossed out
   
   fv = find(v > 0);
	if ~isempty(fv) crossed(fv) = 1; end % cross out those already filled
   
   for i = 1:9
		if ~isempty(find(v(i,:) == x)) crossed(i,:) = 1; end % cross out row
		if ~isempty(find(v(:,i) == x)) crossed(:,i) = 1; end % cross out column
   end

	for i = [1,4,7]
		for j = [1,4,7]
			if ~isempty(find(v(i:i+2,j:j+2) == x))
				crossed(i:i+2,j:j+2) = 1; % cross out sub-square
         else
            % this sub-square will contain it
            if sum(sum(crossed(i:i+2,j:j+2))) == 8 % only one possible cell
					[ii,jj] = find(crossed(i:i+2,j:j+2) == 0); % find it
               sudoku_data.necessary_entries(i+ii-1,j+jj-1) = x;
            end
         end
   	end
   end
end


function pressbuttonsudoku(src,evnt)
% global fignr;
global sudoku_data;

% figure(fignr);
xy=get(gcf,'currentpoint');
px=xy(1);
py=xy(2);
[i,j]=find_pos(px,py);
if i>0 && j>0
    plot_sudoku(sudoku_data);
    [sx,sy,w,h]=position(i,j);
    x=[sx sx+w sx+w sx];
    y=[sy sy sy+h sy+h];
    patch(x,y,'r','facealpha',0.5)
    %     pause(0.1)
    %     drawnow
    sudoku_data.current_selected_i=i;
    sudoku_data.current_selected_j=j;
end
% figure(fignr);
% drawnow


function presskeysudoku(src,evnt)
global sudoku_data;
global lastkey;
global fignr;
global data_history;
global current_step;
global max_step;

i=sudoku_data.current_selected_i;
j=sudoku_data.current_selected_j;
if i>0 && j>0
    nr=get(gcf,'currentcharacter');
    sudoku_data.entry_value(i,j)=str2num(nr);
    % add to history
    current_step=current_step+1;
    data_history{current_step}=sudoku_data;
    max_step=current_step;
    plot_sudoku(sudoku_data);
end




function plot_sudoku(sudoku_data)
global fignr;

figure(fignr);
cla;

hold on
xlabel('');
ylabel('');
set(gca,'xtick',[])
set(gca,'ytick',[])
set(gca,'pos',[0.01 0.01 0.98 0.98]);

for i=1:9
    for j=1:9
        [sx,sy,w,h]=position(i,j);
        line([sx sx],[sy sy+h],'color','k','linewidth',1);
        line([sx+w sx+w],[sy sy+h],'color','k','linewidth',1);
        line([sx sx+w],[sy sy],'color','k','linewidth',1);
        line([sx sx+w],[sy+h sy+h],'color','k','linewidth',1);
    end
end
for i=1:3
    for j=1:3
        [sx,sy,w,h]=bigposition(i,j);
        line([sx sx],[sy sy+h],'color','k','linewidth',3);
        line([sx+w sx+w],[sy sy+h],'color','k','linewidth',3);
        line([sx sx+w],[sy sy],'color','k','linewidth',3);
        line([sx sx+w],[sy+h sy+h],'color','k','linewidth',3);
    end
end

% plot the pencil numbers
% if sudoku_data.show_pencilmarks
for i=1:9
    for j=1:9
        nr_pens=sudoku_data.number_possible(i,j);
        if nr_pens==1
            col='g';
            size=14;
        else
            col='b';
            size=10;
        end

        for k=1:nr_pens
            [sx,sy,w,h]=position(i,j);
            nrp=sudoku_data.possible_entries(i,j,k);
            if nrp>0
                switch k
                    case 1
                        text(sx+0.01,sy+h,num2str(nrp),'fontsize',size,'color',col,'hor','left','vert','top');
                    case 2
                        text(sx+0.01+w/2,sy+h,num2str(nrp),'fontsize',size,'color',col,'hor','center','vert','top');
                    case 3
                        text(sx-0.01+w,sy+h,num2str(nrp),'fontsize',size,'color',col,'hor','right','vert','top');
                    case 4
                        text(sx+0.01,sy+h/2,num2str(nrp),'fontsize',size,'color',col,'hor','left','vert','middle');
                    case 5
                        text(sx+0.01+w/2,sy+h/2,num2str(nrp),'fontsize',size,'color',col,'hor','center','vert','middle');
                    case 6
                        text(sx-0.01+w,sy+h/2,num2str(nrp),'fontsize',size,'color',col,'hor','right','vert','middle');
                    case 7
                        text(sx+0.01,sy,num2str(nrp),'fontsize',size,'color',col,'hor','left','vert','botto');
                    case 8
                        text(sx+0.01+w/2,sy,num2str(nrp),'fontsize',size,'color',col,'hor','center','vert','botto');
                    case 9
                        text(sx-0.01+w,sy,num2str(nrp),'fontsize',size,'color',col,'hor','right','vert','botto');
                end
            end
        end
    end
end
% end

% plot the forced numbers fat
for i=1:9
    for j=1:9
        nr=sudoku_data.necessary_entries(i,j);
        if nr>0
            [sx,sy,w,h]=position(i,j);
            text(sx+w/2,sy+h/2,num2str(nr),'fontsize',20,'color','g');
        end
    end
end


% plot the numbers
% check if finished
if is_solved(sudoku_data.entry_value)
    col='g';
else
    col='k';
end
for i=1:9
    for j=1:9
        nr=sudoku_data.entry_value(i,j);
        if nr>0
            [sx,sy,w,h]=position(i,j);
            text(sx+w/2,sy+h/2,num2str(nr),'fontsize',20,'color',col);
        end
    end
end





% check validity
v=sudoku_data.entry_value;
for i=1:9
    for j=1:9
        if i<4          iii=[1 2 3]; end    % subsquare
        if i>3 && i<7   iii=[4 5 6]; end
        if i>6          iii=[7 8 9]; end
        if j<4          jjj=[1 2 3]; end
        if j>3 && j<7   jjj=[4 5 6]; end
        if j>6          jjj=[7 8 9]; end
        x=v(i,j); % the number
        if v(i,j)~=0
            is_valid=1;
            for ii=1:9   % check the row
                if v(ii,j)==x && ii~=i
                    is_valid=0;
                    break;
                end
            end
            if is_valid
                for jj=1:9   % check the column
                    if v(i,jj)==x && jj~=j
                        is_valid=0;
                        break;
                    end
                end
            end
            if is_valid
                for ii=iii   % check the subsquare
                    for jj=jjj   %
                        if v(ii,jj)==x && ii~=i && jj~=j
                            is_valid=0;
                            break;
                        end
                    end
                end
            end
            if ~is_valid
                [sx,sy,w,h]=position(i,j);
                text(sx+w/2,sy+h/2,num2str(x),'fontsize',20,'color','r');
            end
        end
    end
end



function dr=fill_random

    d=zeros(9,9);
    r=randperm(9);
    d(1,1:end)=r;
    k.entry_value=d; % the original
    dr=solver(k);


function d=delete_random(d,nr,vari)
% delete so many numbers in each square with a variance of var
for i=1:9
    if i==1 iii=[1 2 3]; jjj=[1 2 3]; end
    if i==2 iii=[1 2 3]; jjj=[4 5 6]; end
    if i==3 iii=[1 2 3]; jjj=[7 8 9]; end
    if i==4 iii=[4 5 6]; jjj=[1 2 3]; end
    if i==5 iii=[4 5 6]; jjj=[4 5 6]; end
    if i==6 iii=[4 5 6]; jjj=[7 8 9]; end
    if i==7 iii=[7 8 9]; jjj=[1 2 3]; end
    if i==8 iii=[7 8 9]; jjj=[4 5 6]; end
    if i==9 iii=[7 8 9]; jjj=[7 8 9]; end

    real_nr=nr+round(rand*2*vari-vari);
    real_nr=min(7,real_nr);
    r=randperm(9);
    r=r(1:real_nr);
    for j=1:real_nr
        c1=1;
        for ii=iii
            c2=1;
            for jj=jjj
                if c1+3*(c2-1)==r(j)
                    d.entry_value(ii,jj)=0;
                end
                c2=c2+1;
            end
            c1=c1+1;
        end
    end
end



% find out if the solution is found
function solved=is_solved(vals)
solved=1; % assume found
% shortcut:
if sum(sum(vals))~=405
    solved=0;
    return
end

for i=1:9
    for j=1:9
        if i<4          iii=[1 2 3]; end    % subsquare
        if i>3 && i<7   iii=[4 5 6]; end
        if i>6          iii=[7 8 9]; end
        if j<4          jjj=[1 2 3]; end
        if j>3 && j<7   jjj=[4 5 6]; end
        if j>6          jjj=[7 8 9]; end
        if sum(sum(vals(iii,jjj)))~=45
            solved=0;
            return
        end
    end
end
for i=1:9
    if sum(vals(i,:))~=45 % test all rows
        solved=0;
        return
    end
    if sum(vals(:,i))~=45 % test all columns
        solved=0;
        return
    end
end


function [sx,sy,w,h]=position(i,j)
off_x=0.0;
off_y=0.0;
dx=(1-off_x)/9;
dy=(1-off_y)/9;
sx=(i-1)*dx+off_x;
sy=(j-1)*dy+off_x;
w=dx;
h=dy;
return

function [i,j]=find_pos(sx,sy)
off_x=0.0;
off_y=0.0;
dx=(1-off_x)/9;
dy=(1-off_y)/9;
for i=1:9
    for j=1:9
        sxl=(i-1)*dx+off_x;
        syl=(j-1)*dy+off_x;
        sxh=sxl+dx;
        syh=syl+dy;
        if sxl<=sx && sxh>=sx && syl<=sy && syh>=sy
            return
        end
    end
end
i=0;j=0;
return

function [sx,sy,w,h]=bigposition(i,j)
off_x=0.0;
off_y=0.0;
dx=(1-off_x)/3;
dy=(1-off_y)/3;
sx=(i-1)*dx+off_x;
sy=(j-1)*dy+off_x;
w=dx;
h=dy;
return


Contact us at files@mathworks.com