% 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 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