/*
SuDoku Solver by Logic v1.4
Copyright (C) Peter Wake 2005

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

The GNU General Public License can be seen at
  http://www.gnu.org/licenses/gpl.html

Or contact the author via the website link at
  http://www.sudokusolver.co.uk


This is all the Javascript files [apart from jstr_2.js] compacted into one to improve web caching efficiency, with comments removed. If you want to see how the Javascript is working please download the individual files.

Files in this volume as follows:
utilities_ml_2.js
sudoku_ml_2.js
chainfinder2.js

*/

Date.prototype.addDays=function(d)
{ with(this) setTime(getTime()+d*86400000);}
String.prototype.trim=function()
{ return this.replace(/(^\s*)|(\s*$)/g, "");}
String.prototype.anyMatch=function(compareString)
{ var i,c; for(i=0;i<compareString.length;i++)
{ c=compareString.charAt(i); if(this.indexOf(c)>=0) return true;}
return false;}
String.prototype.firstMatch=function(compareString)
{ var i,c; for(i=0;i<compareString.length;i++)
{ c=compareString.charAt(i); if(this.indexOf(c)>=0) return c;}
return false;}
Object.prototype.complexCopyOf=function(copyTo)
{ var el; for(el in this)
{ copyTo[el]=this[el].copyOf();}
return copyTo;}
Object.prototype.copyOf=function()
{ if(this.constructor==Function) return this.constructor; if(this.constructor==Boolean || this.constructor==Number || this.constructor==String) return this.valueOf(); if(this.constructor==Array) return this.complexCopyOf(new Array()); if(this.constructor==Date) return new Date(this.getTime()); return this.complexCopyOf(new this.constructor);}
Array.prototype.showHTML=function()
{ var i,a0; a0=""; for(i=0;i<this.length;i++)
{ a0+=this[i].showHTML();}
return a0;}
function setCookie(name,value,daysToExpiry)
{ var expDate; expDate=new Date(); expDate.addDays(daysToExpiry); document.cookie = name+"="+escape(value)+"; expires=" + expDate.toGMTString() + ";"
}
function getCookie(cookieName)
{ var dc,valuePairStringArray,i,valuePairArray; if(document.cookie)
{ dc=document.cookie; valuePairStringArray=dc.split(";"); for(i=0;i<valuePairStringArray.length;i++)
{ valuePairArray=valuePairStringArray[i].split("="); if(valuePairArray[0].trim()==cookieName) return valuePairArray[1].trim();}
}
return null;}
function getSudokuString(delimeter)
{ var a0,x,y,formEl,chr; a0=""; for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ formEl=document.getElementById("sgd_"+x+"_"+y); chr=formEl.value+" "; chr=chr.charAt(0); if(chr==" ") chr="_"
a0+=chr.charAt(0);}
if(y!=8) a0+=delimeter;}
return a0;}
function isValidSudokuString(a0,delimeter)
{ var a1,sArr,x,y,chr; a1="123456789_."; sArr=a0.split(delimeter); if(sArr.length!=9) return false; for(y=0;y<9;y++)
{ if(sArr[y].length!=9) return false; for(x=0;x<9;x++)
{ chr=sArr[y].charAt(x); if(a1.indexOf(chr)==-1) return false;}
}
return true;}
function setSudokuString(a0,delimeter)
{ var sArr,x,y,formEl,chr; if(!isValidSudokuString(a0,delimeter)) return false; sArr=a0.split(delimeter); for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ chr=sArr[y].charAt(x); if(chr=="_") chr=""; if(chr==".") chr=""; formEl=document.getElementById("sgd_"+x+"_"+y); formEl.value=chr;}
}
return true;}
function saveSudokuCookie()
{ var a0; a0=getSudokuString("+"); setCookie("sudoku",a0,365); if(a0!=getCookie("sudoku")) alert(mlText.cookie_not_set);}
function loadSudokuCookie()
{ var a0; a0=getCookie("sudoku"); if(a0==null) return alert(mlText.cookie_unavailable); var set = setSudokuString(a0,"+"); if(set==false) alert(mlText.cookie_invalid);}
function saveCurrentGrid()
{ var a0; a0=getSudokuString("+"); setCookie("currentgrid",a0,0.1);}
function currentGridWatch()
{ saveCurrentGrid(); setTimeout("currentGridWatch()", 2000)
}
function loadCurrentGrid()
{ var a0; a0=getCookie("currentgrid"); if(a0==null) return false; setSudokuString(a0,"+"); return true;}
function enterSudoku()
{ var a0,done,promptStr; done=false; a0=getSudokuString("+"); promptStr=mlText.string_format_message; while(done==false)
{ a0=prompt(promptStr, a0); if (a0==null) return; done=setSudokuString(a0,"+"); if(done==false)
{ done=setSudokuString(a0,"x"); if(done==false) promptStr=mlText.string_error_in_format;}
}
}
function createSudokuString()
{ var a0; a0=prompt(mlText.copy_string_to_clipboard, getSudokuString("+"));}
function emailer()
{ var a0="mailto:comments"; a0+="@"; a0+="sudokusolver.co.uk?body=%0a%0aStart Sudoku:%0a%0a"+getSudokuString("%0a")+"%0a%0aCookie String:%0a"+getSudokuString("+")+"%0a"; window.location=a0;}
var mostPopularGrid; var sudokuLoaded=false; var mostPopularGridLoaded=false; function setTodaysMostPopularGrid()
{ var popFrameEl=document.getElementById("mostPopularGridFrame"); var oDoc = (popFrameEl.contentWindow || popFrameEl.contentDocument); if (oDoc.document) oDoc = oDoc.document; mostPopularGrid=oDoc.body.innerHTML; if(sudokuLoaded==true) setSudokuString(mostPopularGrid,"x"); if(isValidSudokuString(mostPopularGrid,"x")) mostPopularGridLoaded=true;}
function submitGridString(a0,d,g,s)
{ strPage = "submitGrid2.php?grid="+a0+"&difficulty="+d+"&guesses="+g+"&solutions="+s; var submitFrameEl=document.getElementById("submitGridFrame"); if(submitFrameEl) submitFrameEl.src=strPage;}
function submitGrid(d,g,s)
{ var a0=getSudokuString("x"); submitGridString(a0,d,g,s);}
function goToPage(pageURL)
{ document.location=mlText.language_precursor+pageURL;}
border1="#303090"; border2="#9090FF"; shading1="#FFFFFF"; function Sudoku(startGridDiv,workingGridDiv,notesName)
{ this.xName=mlText.xName; this.yName=mlText.yName; this.startVals="_3___1___x__6____5_x5_____983x_8___63_2x____5____x9_38___6_x714_____9x_2____8__x___4___3_"; this.lastSubmittedGrid=this.startVals; this.matrix=new Array(); this.matrixEl=new Array(); this.matrixColor=new Array(); this.startMatrix=new Array(); this.hasChangedFlag=false; this.solvedCount=0; this.startGridDiv=startGridDiv; this.workingGridDiv=workingGridDiv; if(document.frames) this.notesDoc=document.frames(notesName).document; if(document.getElementById(notesName).contentDocument) this.notesDoc=document.getElementById(notesName).contentDocument; this.found=null; this.lastFoundX=0; this.lastFoundY=0; this.step=false; this.breakOut=false; this.lastHighlightedX=null; this.lastHighlightedY=null; this.solutions=0; this.difficultyScore=0; this.guesses=0; this.latestSolution=""; this.doAriadne=true; this.returnFirstAnswer=true; this.stopAfterTwoSolutions=true; this.doAlert=true; this.lsEl=null; var x,y; for(y=0;y<9;y++)
{ this.matrix[y]=new Array(); this.matrixEl[y]=new Array(); this.matrixColor[y]=new Array(); this.startMatrix[y]=new Array();}
this.initialiseDivs(); this.bufferNotes=""; this.lastNote=""; this.lastNoteButOne=""; this.notesDoc.body.innerHTML=mlText.notes+"<br>";}
Sudoku.prototype.cellString=function(x,y)
{ return "["+this.yName[y]+this.xName[x]+"]";}
Sudoku.prototype.parseString=function(str,replaceObj)
{ var el,myReg; for(el in replaceObj)
{ if(typeof replaceObj[el]=='function') continue; myReg=new RegExp("%"+el+"%","g"); str=str.replace(myReg,replaceObj[el]);}
return str;}
Sudoku.prototype.addNote=function(str,replaceObj)
{ str=this.parseString(str,replaceObj); this.lastNoteButOne=this.lastNote; if(this.lastNoteButOne && this.lastNoteButOne.indexOf("<pre>")!=-1) this.lastNoteButOne=""; this.lastNote=str; this.bufferNotes+=str+"<br>";}
Sudoku.prototype.echoNotes=function()
{ this.notesDoc.body.innerHTML+=this.bufferNotes; this.bufferNotes="";}
Sudoku.prototype.clearNoteBuffer=function()
{ this.bufferNotes="";}
Sudoku.prototype.statusNote=function()
{ this.addNote("<pre>"+this.statusNoteText("<br>")+"</pre>");}
Sudoku.prototype.statusNoteText=function(eolTxt)
{ var x,y,a0,sps; sps="          "; a0="   |  "; for(x=0;x<9;x++)
{ a0+=this.xName[x]+"         ";}
a0+=eolTxt; a0+="---+--"; for(x=0;x<9;x++)
{ a0+="----------";}
a0+=eolTxt; for(y=0;y<9;y++)
{ a0 += this.yName[y]+"  |  "; for(x=0;x<9;x++)
{ a0+=this.matrix[y][x]; a0+=sps.substring(0,10-this.matrix[y][x].length);}
a0+=eolTxt;}
return a0;}
Sudoku.prototype.startMethodNote=function(methodCode)
{ this.addNote("=============================="); this.addNote(mlText.startingSolveMethod,{methodCode:methodCode}); this.statusNote();}
Sudoku.prototype.endMethodNote=function(methodCode)
{ if(this.step==true) this.refreshWorkingGrid(); this.addNote(mlText.endingSolveMethod,{methodCode:methodCode}); this.addNote("==============================");}
Sudoku.prototype.resetWorkingGrid=function()
{ var x,y; for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ this.matrixColor[y][x]='#FFFFFF'; this.matrixEl[y][x].style.fontWeight='normal'; this.matrixEl[y][x].style.color='#404040'; this.matrix[y][x]='123456789'; this.hasChangedFlag=true;}
}
this.lastHighlightedX=null; this.lastHighlightedY=null; this.refreshWorkingGrid(); this.notesDoc.body.innerHTML=mlText.notes+"<br>"; this.addNote(mlText.resetWorkingGrid); this.solvedCount=0; this.difficultyScore=0; this.guesses=0;}
Sudoku.prototype.clearStartGrid=function()
{ var csg=confirm(mlText.sureClearStartGrid); if(csg!=true) return; var x,y; for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ formEl=document.getElementById("sgd_"+x+"_"+y); formEl.value="";}
}
}
Sudoku.prototype.clearWorkingGrid=function()
{ var x,y; for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ formEl=document.getElementById("sgw_"+x+"_"+y); formEl.value="";}
}
}
Sudoku.prototype.initialiseDivs=function()
{ this.startGridDiv.innerHTML=this.showHTML("\"<input type='text' id='sgd_\"+x+\"_\"+y+\"' value='' size='1' maxlength='1' class='startGrid'>\""); this.workingGridDiv.innerHTML=this.showHTML("\"<input type='text' id='sgw_\"+x+\"_\"+y+\"' value='' size='9' maxlength='9' class='workingGrid'>\""); for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ this.matrixEl[y][x]=document.getElementById("sgw_"+x+"_"+y);}
}
this.resetWorkingGrid(); if(loadCurrentGrid()==true) return; if(mostPopularGridLoaded==true)
{ setSudokuString(mostPopularGrid,"x"); this.lastSubmittedGrid=mostPopularGrid; return;}
setSudokuString(this.startVals,"x"); sudokuLoaded=true;}
Sudoku.prototype.refreshWorkingGrid=function()
{ for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ this.matrixEl[y][x].value=this.matrix[y][x]; this.matrixEl[y][x].style.backgroundColor=this.matrixColor[y][x];}
}
}
Sudoku.prototype.showHTML=function(evalStr)
{ var a0,styleStr; a0="<table cellpadding=0 cellspacing=0>"; var x,y; a0+="<tr>"; a0+="<td></td>"; for(x=0;x<9;x++)
{ a0+="<td>"+this.xName[x]+"</td>";}
a0+="</tr>"; for(y=0;y<9;y++)
{ a0+="<tr>"; a0+="<td>"+this.yName[y]+"</td>"; for(x=0;x<9;x++)
{ styleStr=" style='border-color:"; styleStr+=(y%3==0)?border1:border2; styleStr+=" "; styleStr+=(x%3==2)?border1:border2; styleStr+=" "; styleStr+=(y%3==2)?border1:border2; styleStr+=" "; styleStr+=(x%3==0)?border1:border2; styleStr+="'"; a0+="<td"+styleStr+">"+eval(evalStr)+"</td>";}
a0+="</tr>";}
a0+="</table>"; return a0;}
Sudoku.prototype.runStartGrid=function()
{ var x,y; this.breakOut=false; this.addNote("=============================="); this.addNote("<b>"+mlText.runningStartGrid+"</b>"); for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ formEl=document.getElementById("sgd_"+x+"_"+y); if(formEl.value==" ") formEl.value=""; if(formEl.value!="")
{ this.startMatrix[y][x]=formEl.value; this.set(x,y,formEl.value,true,true);}
if(this.breakOut) return this.refreshWorkingGrid();}
}
this.refreshWorkingGrid();}
Sudoku.prototype.getMatrixString=function()
{ var a0,x,y; a0=""; for(y=0;y<9;y++)
{ for(x=0;x<9;x++)
{ a0+=this.matrix[y][x]; if(x!=8) a0+=",";}
if(y!=8) a0+="+";}
return a0;}
Sudoku.prototype.setMatrixString=function(a0)
{ var x,y,sArr,tArr; sArr=a0.split("+"); for(y=0;y<9;y++)
{ tArr=sArr[y].split(","); for(x=0;x<9;x++)
{ this.matrix[y][x]=tArr[x];}
}
return;}
Sudoku.prototype.isInvalidSolutionSubset=function(setArray)
{ setArray.sort(); for(i=0;i<setArray.length;i++)
{ if(setArray[i].toString()!=(i+1).toString()) return true;}
return false;}
Sudoku.prototype.checkSolution=function()
{ var ix1,ix2,ix3,ix4,row,col,block; for(ix1=0;ix1<3;ix1++)
{ for(ix2=0;ix2<3;ix2++)
{ row=new Array(); col=new Array(); block=new Array(); for(ix3=0;ix3<3;ix3++)
{ for(ix4=0;ix4<3;ix4++)
{ row[ix3*3+ix4]=this.matrix[ix1*3+ix2][ix3*3+ix4]; col[ix3*3+ix4]=this.matrix[ix3*3+ix4][ix1*3+ix2]; block[ix3*3+ix4]=this.matrix[ix1*3+ix3][ix2*3+ix4];}
}
if(this.isInvalidSolutionSubset(row) || this.isInvalidSolutionSubset(col) || this.isInvalidSolutionSubset(block)) return false;}
}
return true;}
Sudoku.prototype.explainAndPause=function(highlightArray)
{ var i,y,x; this.refreshWorkingGrid(); for(i=0;i<highlightArray.length;i++)
{ if(highlightArray[i][0]=='row')
{ for(x=0;x<9;x++)
{ this.matrixEl[highlightArray[i][1]][x].style.backgroundColor=highlightArray[i][2];}
}
if(highlightArray[i][0]=='col')
{ for(y=0;y<9;y++)
{ this.matrixEl[y][highlightArray[i][1]].style.backgroundColor=highlightArray[i][2];}
}
if(highlightArray[i][0]=='block')
{ for(x=0;x<3;x++)
{ for(y=0;y<3;y++)
{ this.matrixEl[y+highlightArray[i][2]][x+highlightArray[i][1]].style.backgroundColor=highlightArray[i][3];}
}
}
if(highlightArray[i][0]=='cell')
{ this.matrixEl[highlightArray[i][2]][highlightArray[i][1]].style.backgroundColor=highlightArray[i][3];}
}
var a0=prompt(this.lastNoteButOne,this.lastNote); if (a0==null)
{ this.breakOut=true;}
}
Sudoku.prototype.shade=function(x,y)
{ this.matrixColor[y][x]=shading1; this.matrixEl[y][x].style.backgroundColor=this.matrixColor[y][x]; this.matrixEl[y][x].style.color='#000000';}
Sudoku.prototype.set=function(x,y,val,test,dontReport)
{ var ix,iy,xBlockStart,yBlockStart; if(test && this.matrix[y][x]==val.toString())
{ if(dontReport==null || dontReport!=true) this.addNote(mlText.wantToSetCell,{cell:this.cellString(x,y),val:val}); return;}
this.solvedCount++; this.addNote(mlText.setCell,{cell:this.cellString(x,y),val:val,togo:(81-this.solvedCount)}); if(this.solvedCount==81)
{ this.shade(x,y); this.addNote(mlText.checkValid); this.statusNote(); if(this.checkSolution()!=true)
{ throw mlText.invalidSolution;}
this.solutions++; this.latestSolution=this.getMatrixString(); this.addNote(mlText.validSolution); this.addNote(mlText.difficultyScore,{difficultyScore:this.difficultyScore}); this.addNote(mlText.numberOfGuesses,{guesses:this.guesses}); return;}
xBlockStart=3*parseInt(x/3); yBlockStart=3*parseInt(y/3); if(this.step) this.explainAndPause(new Array(new Array('block',xBlockStart,yBlockStart,'#8470FF'),new Array('row',y,'#98FB98'),new Array('col',x,'pink'),new Array('cell',x,y,'#FFD700'))); this.matrix[y][x]=val.toString(); this.shade(x,y); this.hasChangedFlag=true; for(ix=0;ix<9;ix++)
{ if(ix!=x) this.remove(ix,y,val);}
for(iy=0;iy<9;iy++)
{ if(iy!=y) this.remove(x,iy,val);}
for(ix=xBlockStart;ix<xBlockStart+3;ix++)
{ for(iy=yBlockStart;iy<yBlockStart+3;iy++)
{ if(ix!=x && iy!=y) this.remove(ix,iy,val);}
}
}
Sudoku.prototype.isValAtXY=function(x,y,val)
{ var vpos=this.matrix[y][x].indexOf(val); if(vpos>=0) return true; return false;}
Sudoku.prototype.remove=function(x,y,val)
{ var vpos=this.matrix[y][x].indexOf(val); if(vpos>=0)
{ if(this.matrix[y][x].length==1)
{ throw this.parseString(mlText.invalidSolutionCantDelete,{val:val,cell:this.cellString(x,y)});}
this.matrix[y][x]=this.matrix[y][x].substr(0,vpos)+this.matrix[y][x].substr(vpos+1); this.hasChangedFlag=true; if(this.matrix[y][x].length==1)
{ this.addNote(mlText.removing1,{val:val,cell:this.cellString(x,y),matrixValue:this.matrix[y][x]}); if(document.getElementById("sgd_"+x+"_"+y)!=this.matrix[y][x]) this.difficultyScore+=1; this.set(x,y,this.matrix[y][x],false);}
}
}
Sudoku.prototype.columnCount=function(x,vals)
{ this.found=null; this.found=new Array(); vals=vals.toString(); var vp,vc; var y; var ct=0; for(y=0;y<9;y++)
{ vc=0; for(vp=0;vp<vals.length;vp++)
{ if(this.matrix[y][x].indexOf(vals.charAt(vp))>=0)
{ vc++;}
}
if(vc==vals.length)
{ ct++; this.lastFoundY=y; this.found.push(new Array(x,y));}
}
return ct;}
Sudoku.prototype.rowCount=function(y,vals)
{ this.found=null; this.found=new Array(); vals=vals.toString(); var vp,vc; var x; var ct=0; for(x=0;x<9;x++)
{ vc=0; for(vp=0;vp<vals.length;vp++)
{ if(this.matrix[y][x].indexOf(vals.charAt(vp))>=0)
{ vc++;}
}
if(vc==vals.length)
{ ct++; this.lastFoundX=x; this.found.push(new Array(x,y));}
}
return ct;}
Sudoku.prototype.blockCount=function(bx,by,vals)
{ this.found=null; this.found=new Array(); vals=vals.toString(); var vp,vc; var x,y; var ct=0; for(x=bx*3;x<3+bx*3;x++)
{ for(y=by*3;y<3+by*3;y++)
{ vc=0; for(vp=0;vp<vals.length;vp++)
{ if(this.matrix[y][x].indexOf(vals.charAt(vp))>=0)
{ vc++;}
}
if(vc==vals.length)
{ ct++; this.lastFoundX=x; this.lastFoundY=y; this.found.push(new Array(x,y));}
}
}
return ct;}
Sudoku.prototype.findUnique=function(foundCountOf,c)
{ var curEl,compareEl,matches,i,j,matchedEl,elimOne,newSet; elimOne=false; while(foundCountOf.length>=c)
{ curEl = foundCountOf.shift(); matches=null; matches=new Array(); for(i=0;i<foundCountOf.length;i++)
{ compareEl=foundCountOf[i]; matchedEl=true; for(j=0;j<c;j++)
{ if(curEl[1][j][0]!=compareEl[1][j][0] || curEl[1][j][1]!=compareEl[1][j][1])
{ matchedEl=false; break;}
}
if(matchedEl)
{ matches.push(compareEl);}
}
if(matches.length==c-1)
{ newSet=curEl[0].toString(); for(j=0;j<c-1;j++)
{ newSet+=matches[j][0].toString();}
elimOne=false; for(j=0;j<c;j++)
{ if(this.matrix[curEl[1][j][1]][curEl[1][j][0]]!=newSet) elimOne=true;}
if(elimOne)
{ this.addNote(mlText.foundMulticountSet,{newSet:newSet}); this.difficultyScore+=16; if(this.step)
{ multiCountSet=new Array(); for(j=0;j<c;j++)
{ multiCountSet[j]=new Array('cell',curEl[1][j][0],curEl[1][j][1],'#8470FF');}
this.explainAndPause(multiCountSet);}
for(j=0;j<c;j++)
{ this.addNote(mlText.setToNewSet,{cell:this.cellString(curEl[1][j][0],curEl[1][j][1]),newSet:newSet}); this.matrix[curEl[1][j][1]][curEl[1][j][0]]=newSet; this.hasChangedFlag=true;}
}
}
}
return elimOne;}
Sudoku.prototype.logicSolve=function()
{ this.hasChangedFlag=true; while(this.breakOut==false && this.hasChangedFlag==true && this.solvedCount<81)
{ this.echoNotes(); this.hasChangedFlag=false; this.solveMethodA(); if(this.hasChangedFlag==true) this.echoNotes(); if(this.solvedCount==81 || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodB(); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(2); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(3); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(4); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(5); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(6); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(7); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.solveMethodC(8); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodD2(2,32); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodD2(3,48); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodD2(4,64); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodF3(2,48); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodF3(3,64); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodF3(4,64); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodF3(5,64); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer(); this.hasChangedFlag=false; this.solveMethodF3(6,64); if(this.hasChangedFlag==true || this.breakOut==true) continue; this.clearNoteBuffer();}
this.echoNotes();}
Sudoku.prototype.ariadnesThread=function()
{ var x,y,n,val,tempSolvedCount,savedMatrix,solvedAtAll; this.logicSolve(); if(this.solvedCount==81)
{ return true;}
found=false; for(x=0;x<9;x++)
{ for(y=0;y<9;y++)
{ if(this.matrix[y][x].length>1)
{ found=true; break;}
}
if(found==true) break;}
if(found==false) return false; this.addNote("<br><b>"+mlText.endLogic+"</b></br>"); this.statusNote(); tempSolvedCount=this.solvedCount; savedMatrix=this.getMatrixString(); solvedAtAll=false; for(n=0;n<this.matrix[y][x].length;n++)
{ val=this.matrix[y][x].charAt(n); this.addNote("<br><b>"+mlText.guessing+"</b><br>",{val:val,cell:this.cellString(x,y)}); this.statusNote(); try
{ this.difficultyScore+=1000; this.guesses++; this.set(x,y,val,true); solved=this.ariadnesThread();}
catch (e)
{ this.addNote(e); solved=false;}
solvedAtAll|=solved; if(solved)
{ this.addNote("<br><b>"+mlText.guessingSucceeded+"</b><br>",{val:val,cell:this.cellString(x,y)});}
else
{ this.addNote("<br><b>"+mlText.guessingFailed+"</b><br>",{val:val,cell:this.cellString(x,y)});}
if(solved && this.returnFirstAnswer) return true; if(solved && this.solutions>=2 && this.stopAfterTwoSolutions) return true; this.solvedCount=tempSolvedCount; this.setMatrixString(savedMatrix);}
this.addNote("<br><b>"+mlText.endOfAriadne+"</b></br>"); return solvedAtAll;}
Sudoku.prototype.solveStartGrid=function()
{ shading1="#C0C0C0"; this.step=false; this.breakOut=false; this.solutions=0; this.resetWorkingGrid(); this.hasChangedFlag=false; this.runStartGrid(); this.echoNotes();}
Sudoku.prototype.solveBySteps=function()
{ shading1="#C0C0C0"; this.step=true; this.breakOut=false; this.hasChangedFlag=false; this.runStartGrid(); this.logicSolve(); if(this.breakOut==true) this.addNote(mlText.stepSolveCancelled); if(this.solvedCount==81)
{ this.addNote(mlText.checkingGridValid); this.statusNote(); if(this.checkSolution()!=true)
{ this.addNote(mlText.invalidSolution);}
else
{ this.addNote(mlText.validSolution); if(this.doAlert) alert(mlText.validSolution);}
}
else if(this.breakOut==false)
{ this.addNote(mlText.couldNotSolveBySteps); if(this.doAlert) alert(mlText.couldNotSolveBySteps);}
this.echoNotes(); this.refreshWorkingGrid();}
Sudoku.prototype.suggestAMove=function()
{ shading1="#C0C0C0"; this.solveStartGrid(); this.solveBySteps();}
Sudoku.prototype.solveFromScratch=function()
{ this.step=false; this.breakOut=false; var startTime=new Date(); if(document.getElementById("ariadneCheckBox")) this.doAriadne=document.getElementById("ariadneCheckBox").checked; if(document.getElementById("returnFirstAnswerCheckBox")) this.returnFirstAnswer=document.getElementById("returnFirstAnswerCheckBox").checked; var ss0=getSudokuString("x"); saveCurrentGrid(); this.solutions=0; var trySubmit=false; try
{ this.resetWorkingGrid(); this.hasChangedFlag=false; this.runStartGrid(); if(this.doAriadne)
{ this.ariadnesThread();}
else
{ this.logicSolve();}
this.refreshWorkingGrid(); var endTime=new Date()
var elapsedTime=endTime.getTime()-startTime.getTime(); this.addNote(mlText.timeElapsed,{secs:(elapsedTime/1000)}); this.echoNotes(); if(this.solutions>0)
{ this.setMatrixString(this.latestSolution); this.refreshWorkingGrid(); if(this.solutions==1 && this.guesses==0 )
{ if(this.doAlert) alert(mlText.solvedByLogicOnly); trySubmit=true;}
else if(this.solutions==1 && this.guesses>0 && !this.returnFirstAnswer)
{ if(this.doAlert) alert(mlText.guessedBut1Solution); trySubmit=true;}
else if(this.solutions==1 && this.guesses>0 && this.returnFirstAnswer)
{ if(this.doAlert) alert(mlText.guessedMaybeMoreThan1Solution);}
else if(this.solutions==2 && this.stopAfterTwoSolutions)
{ if(this.doAlert) alert(mlText.solvedButMoreThan1Solution); trySubmit=true;}
else
{ if(this.doAlert) alert(this.parseString(mlText.solvedButNSolutions,{solutions:this.solutions}));}
}
else
{ if(this.doAriadne)
{ if(this.doAlert) alert(mlText.didntSolve+"\n"+mlText.noSolution);}
else
{ if(this.doAlert) alert(mlText.didntSolve+"\n"+mlText.noSolveOptions);}
}
}
catch (e)
{ this.echoNotes(); this.refreshWorkingGrid(); if(this.doAlert) alert(e);}
if(trySubmit && this.lastSubmittedGrid!=ss0)
{ this.addNote(mlText.submittingGrid); this.echoNotes(); submitGrid(this.difficultyScore,this.guesses,this.solutions); this.lastSubmittedGrid=ss0;}
}
Sudoku.prototype.solveMethodA=function()
{ var x,y,bx,by,n, foundOne; this.startMethodNote("A"); do
{ foundOne=false; for(n=1;n<=9;n++)
{ for(x=0;x<9;x++)
{ if(this.columnCount(x,n)==1 && this.matrix[this.lastFoundY][x]!=n)
{ this.addNote(mlText.methodA_column,{col:this.xName[x],val:n,row:this.yName[this.lastFoundY]}); if(this.step) this.explainAndPause(new Array(new Array('col',x,'#98FB98'),new Array('cell',x,this.lastFoundY,'#FFD700'))); this.difficultyScore+=4; this.set(x,this.lastFoundY,n,true); foundOne=true; if(this.breakOut==true) return this.endMethodNote("A");}
}
for(y=0;y<9;y++)
{ if(this.rowCount(y,n)==1 && this.matrix[y][this.lastFoundX]!=n)
{ this.addNote(mlText.methodA_row,{row:this.yName[y],val:n,col:this.xName[this.lastFoundX]}); if(this.step) this.explainAndPause(new Array(new Array('row',y,'#98FB98'),new Array('cell',this.lastFoundX,y,'#FFD700'))); this.difficultyScore+=4; this.set(this.lastFoundX,y,n,true); foundOne=true; if(this.breakOut==true) return this.endMethodNote("A");}
}
for(bx=0;bx<3;bx++)
{ for(by=0;by<3;by++)
{ if(this.blockCount(bx,by,n)==1 && this.matrix[this.lastFoundY][this.lastFoundX]!=n)
{ this.addNote(mlText.methodA_block,{block:this.cellString(bx*3,by*3),val:n,cell:this.cellString(this.lastFoundX,this.lastFoundY)}); if(this.step) this.explainAndPause(new Array(new Array('block',bx*3,by*3,'#98FB98'),new Array('cell',this.lastFoundX,this.lastFoundY,'#FFD700'))); this.difficultyScore+=4; this.set(this.lastFoundX,this.lastFoundY,n,true); foundOne=true; if(this.breakOut==true) return this.endMethodNote("A");}
}
}
}
} while(foundOne==true && this.breakOut==false)
this.endMethodNote("A");}
Sudoku.prototype.solveMethodB=function()
{ var x,y,bx,by,sx,sy,i,n, foundOne,b0,b1,b2,bix; var c,cc; var allSameRow,allSameCol; this.startMethodNote("B"); do
{ foundOne=false; for(x=0;x<9;x++)
{ for(n=1;n<=9;n++)
{ cc=this.columnCount(x,n); if(cc<=3 && cc>1)
{ b0=1;b1=1;b2=1; for(i=0;i<this.found.length;i++)
{ bix=this.found[i][1]; if(bix<=2) b0=2; else if(bix>=6) b2=2; else b1=2;}
if(b0*b1*b2==2)
{ bx=3*parseInt(x/3); by=b1*3+b2*6-9; if(this.blockCount(bx/3,by/3,n)>cc)
{ foundOne=true; this.addNote(mlText.methodB_block_col,{val:n,block:this.cellString(bx,by),col:this.xName[x]}); if(this.step) this.explainAndPause(new Array(new Array('block',bx,by,'#8470FF'),new Array('col',x,'#98FB98'),new Array('cell',x,by,'pink'),new Array('cell',x,by+1,'pink'),new Array('cell',x,by+2,'pink'))); for(i=0;i<this.found.length;i++)
{ if(this.found[i][0]!=x)
{ this.difficultyScore+=8; this.remove(this.found[i][0],this.found[i][1],n);}
}
if(this.breakOut==true) return this.endMethodNote("B");}
}
}
}
}
for(y=0;y<9;y++)
{ for(n=1;n<=9;n++)
{ cc=this.rowCount(y,n); if(cc<=3 && cc>1)
{ b0=1;b1=1;b2=1; for(i=0;i<this.found.length;i++)
{ bix=this.found[i][0]; if(bix<=2) b0=2; else if(bix>=6) b2=2; else b1=2;}
if(b0*b1*b2==2)
{ by=3*parseInt(y/3); bx=b1*3+b2*6-9; if(this.blockCount(bx/3,by/3,n)>cc)
{ foundOne=true; this.addNote(mlText.methodB_block_row,{val:n,block:this.cellString(bx,by),row:this.yName[y]}); if(this.step) this.explainAndPause(new Array(new Array('block',bx,by,'#8470FF'),new Array('row',y,'#98FB98'),new Array('cell',bx,y,'pink'),new Array('cell',bx+1,y,'pink'),new Array('cell',bx+2,y,'pink'))); for(i=0;i<this.found.length;i++)
{ if(this.found[i][1]!=y)
{ this.difficultyScore+=8; this.remove(this.found[i][0],this.found[i][1],n);}
}
if(this.breakOut==true) return this.endMethodNote("B");}
}
}
}
}
for(bx=0;bx<3;bx++)
{ for(by=0;by<3;by++)
{ for(n=1;n<=9;n++)
{ cc=this.blockCount(bx,by,n); if(cc<=3 && cc>1)
{ allSameRow=true; allSameCol=true; for(i=1;i<this.found.length;i++)
{ if(this.found[0][1]!=this.found[i][1]) allSameRow=false; if(this.found[0][0]!=this.found[i][0]) allSameCol=false;}
if(allSameRow)
{ if(this.rowCount(this.found[0][1],n)>cc)
{ foundOne=true; this.addNote(mlText.methodB_row_block,{val:n,row:this.yName[this.found[0][1]],block:this.cellString(bx*3,by*3)}); if(this.step) this.explainAndPause(new Array(new Array('block',bx*3,by*3,'#8470FF'),new Array('row',this.found[0][1],'#98FB98'),new Array('cell',bx*3,this.found[0][1],'pink'),new Array('cell',bx*3+1,this.found[0][1],'pink'),new Array('cell',bx*3+2,this.found[0][1],'pink'))); for(i=0;i<this.found.length;i++)
{ if(this.found[i][0]<bx*3 || this.found[i][0]>2+bx*3)
{ this.difficultyScore+=8; this.remove(this.found[i][0],this.found[i][1],n);}
}
if(this.breakOut==true) return this.endMethodNote("B");}
}
if(allSameCol)
{ if(this.columnCount(this.found[0][0],n)>cc)
{ foundOne=true; this.addNote(mlText.methodB_col_block,{val:n,col:this.xName[this.found[0][0]],block:this.cellString(bx*3,by*3)}); if(this.step) this.explainAndPause(new Array(new Array('block',bx*3,by*3,'#8470FF'),new Array('col',this.found[0][0],'#98FB98'),new Array('cell',this.found[0][0],by*3,'pink'),new Array('cell',this.found[0][0],by*3+1,'pink'),new Array('cell',this.found[0][0],by*3+2,'pink'))); for(i=0;i<this.found.length;i++)
{ if(this.found[i][1]<by*3 || this.found[i][1]>2+by*3)
{ this.difficultyScore+=8; this.remove(this.found[i][0],this.found[i][1],n);}
}
if(this.breakOut==true) return this.endMethodNote("B");}
}
}
}
}
}
} while(foundOne==true)
this.endMethodNote("B");}
Sudoku.prototype.solveMethodC=function(c)
{ var x,y,bx,by,n, foundOne,foundCountOf; this.startMethodNote("C"); do
{ foundOne=false; for(x=0;x<9;x++)
{ foundCountOf=null; foundCountOf=new Array(); for(n=1;n<=9;n++)
{ if(this.columnCount(x,n)==c)
{ foundCountOf.push(new Array(n,this.found.copyOf()));}
}
foundOne|=this.findUnique(foundCountOf,c)
if(this.breakOut==true) return this.endMethodNote("C");}
for(y=0;y<9;y++)
{ foundCountOf=null; foundCountOf=new Array(); for(n=1;n<=9;n++)
{ if(this.rowCount(y,n)==c)
{ foundCountOf.push(new Array(n,this.found.copyOf()));}
}
foundOne|=this.findUnique(foundCountOf,c)
if(this.breakOut==true) return this.endMethodNote("C");}
for(bx=0;bx<3;bx++)
{ for(by=0;by<3;by++)
{ foundCountOf=null; foundCountOf=new Array(); for(n=1;n<=9;n++)
{ if(this.blockCount(bx,by,n)==c)
{ foundCountOf.push(new Array(n,this.found.copyOf()));}
}
foundOne|=this.findUnique(foundCountOf,c)
if(this.breakOut==true) return this.endMethodNote("C");}
}
} while(foundOne==true)
return this.endMethodNote("C");}
function CellArrays()
{ this.ROW_INDICES = new Array(9)
this.ROW_INDICES[0] = new Array( 0, 1, 2, 3, 4, 5, 6, 7, 8); this.ROW_INDICES[1] = new Array( 9, 10, 11, 12, 13, 14, 15, 16, 17); this.ROW_INDICES[2] = new Array(18, 19, 20, 21, 22, 23, 24, 25, 26); this.ROW_INDICES[3] = new Array(27, 28, 29, 30, 31, 32, 33, 34, 35); this.ROW_INDICES[4] = new Array(36, 37, 38, 39, 40, 41, 42, 43, 44); this.ROW_INDICES[5] = new Array(45, 46, 47, 48, 49, 50, 51, 52, 53); this.ROW_INDICES[6] = new Array(54, 55, 56, 57, 58, 59, 60, 61, 62); this.ROW_INDICES[7] = new Array(63, 64, 65, 66, 67, 68, 69, 70, 71); this.ROW_INDICES[8] = new Array(72, 73, 74, 75, 76, 77, 78, 79, 80); this.COLUMN_INDICES = new Array(9); this.COLUMN_INDICES[0] = new Array( 0, 9, 18, 27, 36, 45, 54, 63, 72); this.COLUMN_INDICES[1] = new Array( 1, 10, 19, 28, 37, 46, 55, 64, 73); this.COLUMN_INDICES[2] = new Array( 2, 11, 20, 29, 38, 47, 56, 65, 74); this.COLUMN_INDICES[3] = new Array( 3, 12, 21, 30, 39, 48, 57, 66, 75); this.COLUMN_INDICES[4] = new Array( 4, 13, 22, 31, 40, 49, 58, 67, 76); this.COLUMN_INDICES[5] = new Array( 5, 14, 23, 32, 41, 50, 59, 68, 77); this.COLUMN_INDICES[6] = new Array( 6, 15, 24, 33, 42, 51, 60, 69, 78); this.COLUMN_INDICES[7] = new Array( 7, 16, 25, 34, 43, 52, 61, 70, 79); this.COLUMN_INDICES[8] = new Array( 8, 17, 26, 35, 44, 53, 62, 71, 80); this.BLOCK_INDICES = new Array(9); this.BLOCK_INDICES[0] = new Array( 0, 1, 2, 9, 10, 11, 18, 19, 20); this.BLOCK_INDICES[1] = new Array( 3, 4, 5, 12, 13, 14, 21, 22, 23); this.BLOCK_INDICES[2] = new Array( 6, 7, 8, 15, 16, 17, 24, 25, 26); this.BLOCK_INDICES[3] = new Array(27, 28, 29, 36, 37, 38, 45, 46, 47); this.BLOCK_INDICES[4] = new Array(30, 31, 32, 39, 40, 41, 48, 49, 50); this.BLOCK_INDICES[5] = new Array(33, 34, 35, 42, 43, 44, 51, 52, 53); this.BLOCK_INDICES[6] = new Array(54, 55, 56, 63, 64, 65, 72, 73, 74); this.BLOCK_INDICES[7] = new Array(57, 58, 59, 66, 67, 68, 75, 76, 77); this.BLOCK_INDICES[8] = new Array(60, 61, 62, 69, 70, 71, 78, 79, 80);}
function VectorIterator(size, range)
{ this.range = range; this.indices = new Array(); for (var i = 0; i < size; ++i)
{ this.indices[i] = i;}
}
VectorIterator.prototype.increment = function()
{ var size = this.indices.length; while (true)
{ for (var i = size - 1; i >= 0; --i)
{ ++this.indices[i]; for (var j = i + 1; j < size; ++j)
this.indices[j] = this.indices[j - 1] + 1; if (this.indices[i] < this.range + i + 1 - size)
return true;}
if (this.indices[0] >= this.range + 1 - size)
return false;}
}
function Subset()
{ this.reference = 0; this.items = new Array();}
function createUnionOfSubsets(iter, subsets, items, references)
{ var size = iter.indices.length; for (var i = 0; i < size; ++i)
{ var index = iter.indices[i]; var subset = subsets[index]; references.push(subset.reference); for (var j = 0; j < subset.items.length; ++j)
{ var already_present = false; for (var k = 0; k < items.length; ++k)
{ if (items[k] == subset.items[j])
{ already_present = true; break;}
}
if (!already_present)
items.push(subset.items[j]);}
}
items.sort(); return items.length;}
Sudoku.prototype.getCandidates = function(cell, candidates)
{ var row = Math.floor(cell/9); var col = cell%9; for (var candidate = 1; candidate <= 9; ++candidate)
{ if (this.matrix[row][col].indexOf(candidate) >= 0)
candidates.push(candidate);}
return candidates.length;}
Sudoku.prototype.solveMethodD2 = function(size, score)
{ this.startMethodNote(mlText.methodD_name); var cells = new CellArrays(); this.addNote(""); this.addNote(mlText.methodD_info,{size:size}); { var removed = 0; this.addNote(mlText.methodD_check_col); for (var unit = 0; unit < 9 && !this.breakOut; ++unit)
{ var name = this.parseString(mlText.column,{col:this.xName[unit]}); removed += this.solveSubsetsByCandidate(cells.COLUMN_INDICES[unit], size, name, score, new Array('col',unit,'#8470FF'));}
}
{ var removed = 0; this.addNote(mlText.methodD_check_row); for (var unit = 0; unit < 9 && !this.breakOut; ++unit)
{ var name = this.parseString(mlText.row,{row:this.yName[unit]}); this.solveSubsetsByCandidate(cells.ROW_INDICES[unit], size, name, score, new Array('row',unit,'#8470FF'));}
}
{ var removed = 0; this.addNote(mlText.methodD_check_block); for (var unit = 0; unit < 9 && !this.breakOut; ++unit)
{ var row = Math.floor(cells.BLOCK_INDICES[unit][0]/9); var col = cells.BLOCK_INDICES[unit][0]%9; var name = this.parseString(mlText.block,{block:this.cellString(col,row)}); this.solveSubsetsByCandidate(cells.BLOCK_INDICES[unit], size, name, score, new Array('block',col,row,'#8470FF'));}
}
this.addNote(""); this.endMethodNote("D");}
Sudoku.prototype.solveSubsetsByCandidate = function(cells, subsetSize, name, score, baseHighlightEl)
{ var highlightArray; var subsets = new Array(); for (var index = 0; index < 9; ++index)
{ var subset = new Subset; var size = this.getCandidates(cells[index], subset.items); if (size > 1 && size <= subsetSize)
{ subset.reference = index; subsets.push(subset);}
}
if (subsets.length < subsetSize)
return 0; var removedTotal = 0; var iter = new VectorIterator(subsetSize, subsets.length); do
{ var items = new Array(); var references = new Array(); if (createUnionOfSubsets(iter, subsets, items, references) == subsetSize)
{ var chain = " "; if(this.step) highlightArray=new Array(baseHighlightEl); for (var i = 0; i < references.length; ++i)
{ var cell = cells[references[i]]; var row = Math.floor(cell/9); var col = cell%9; chain = chain + " " + this.cellString(col,row) + " "; if(this.step) highlightArray.push(new Array('cell',col,row,'#98FB98'));}
this.addNote(mlText.methodD2_found,{name:name,items:items.join(),chain:chain}); var removed = 0; for (var index = 0; index < 9; ++index)
{ var reset_allowed = true; for (var i = 0; i < references.length; ++i)
{ if (references[i] == index)
{ reset_allowed = false; break;}
}
if (reset_allowed)
{ for (var j = 0; j < items.length; ++j)
{ var row = Math.floor(cells[index]/9); var col = cells[index]%9; if (this.isValAtXY(col, row, items[j]))
{ this.addNote(mlText.methodD2_removing,{item:items[j],cell:this.cellString(col,row)}); if(this.step)
{ highlightArray.push(new Array('cell',col,row,'pink')); this.explainAndPause(highlightArray); highlightArray.pop();}
this.remove(col, row, items[j]); ++removed;}
}
}
}
if (removed > 0)
{ this.difficultyScore += score; removedTotal += removed;}
else
{ this.addNote(mlText.methodD2_nothing_removed);}
}
}
while (iter.increment() && !this.breakOut); return removedTotal;}
function BlockArrays()
{ this.BLOCK_ROW_INDICES = new Array(9); this.BLOCK_ROW_INDICES[0] = new Array(0, 1, 2); this.BLOCK_ROW_INDICES[1] = new Array(1, 2, 0); this.BLOCK_ROW_INDICES[2] = new Array(2, 0, 1); this.BLOCK_ROW_INDICES[3] = new Array(3, 4, 5); this.BLOCK_ROW_INDICES[4] = new Array(4, 5, 3); this.BLOCK_ROW_INDICES[5] = new Array(5, 3, 4); this.BLOCK_ROW_INDICES[6] = new Array(6, 7, 8); this.BLOCK_ROW_INDICES[7] = new Array(7, 8, 6); this.BLOCK_ROW_INDICES[8] = new Array(8, 6, 7); this.BLOCK_COLUMN_INDICES = new Array(9); this.BLOCK_COLUMN_INDICES[0] = new Array(0, 3, 6); this.BLOCK_COLUMN_INDICES[1] = new Array(3, 6, 0); this.BLOCK_COLUMN_INDICES[2] = new Array(6, 0, 3); this.BLOCK_COLUMN_INDICES[3] = new Array(1, 4, 7); this.BLOCK_COLUMN_INDICES[4] = new Array(4, 7, 1); this.BLOCK_COLUMN_INDICES[5] = new Array(7, 1, 4); this.BLOCK_COLUMN_INDICES[6] = new Array(2, 5, 8); this.BLOCK_COLUMN_INDICES[7] = new Array(5, 8, 2); this.BLOCK_COLUMN_INDICES[8] = new Array(8, 2, 4);}
Sudoku.prototype.getUnitIndicesInBlock = function(block, candidate, calc, units)
{ var indices = new Array(); this.getIndicesInUnit(block, candidate, indices); for (var i = 0; i < indices.length; ++i)
{ var unit = calc.func(block[indices[i]]); if (!arrayContains(units, unit))
{ units.push(unit);}
}
units.sort(); return units.length;}
Sudoku.prototype.getIndicesInUnit = function(cells, candidate, indices)
{ for (var i = 0; i < 9; ++i)
{ var cell = cells[i]; var row = Math.floor(cell/9); var col = cell%9; if (this.matrix[row][col].indexOf(candidate) >= 0)
indices.push(i);}
return indices.length;}
function arrayContains(container, value)
{ var result = false; for (var i = 0; i < container.length; ++i)
{ if (container[i] == value)
{ result = true; break;}
}
return result
}
Sudoku.prototype.solveMethodF3 = function(size,score)
{ switch (size)
{ case 2: this.startMethodNote(mlText.methodF3_name2); break; case 3: this.startMethodNote(mlText.methodF3_name3); break; case 4: this.startMethodNote(mlText.methodF3_name4); break; case 5: this.startMethodNote(mlText.methodF3_name5); break; default: this.startMethodNote(this.parseString(mlText.methodF3_nameN,{n:size}));}
this.cells = new CellArrays(); var removed=0; for (var candidate = 1; candidate <= 9 && !this.breakOut; ++candidate)
{ removed+=this.solveNxNSubgridsImpl(this.cells.COLUMN_INDICES, candidate, size, 1, score);}
for (var candidate = 1; candidate <= 9 && !this.breakOut; ++candidate)
{ removed+=this.solveNxNSubgridsImpl(this.cells.ROW_INDICES, candidate, size, 0, score);}
if (size == 2)
{ removed+=this.solveBlocksWithSharedUnits(score);}
this.endMethodNote("F");}
Sudoku.prototype.solveNxNSubgridsImpl = function(cells, candidate, subsetSize, type, score)
{ var subsets = new Array(); var totalRemoved=0; for (var unit = 0; unit < 9; ++unit)
{ var subset = new Subset; var size = this.getIndicesInUnit(cells[unit], candidate, subset.items); if (size > 1 && size <= subsetSize)
{ subset.reference = unit; subsets.push(subset);}
}
if (subsets.length < subsetSize)
return 0; var iter = new VectorIterator(subsetSize, subsets.length); do
{ var indices = new Array(); var units = new Array(); var highlightArray = []; var removed=0; if (createUnionOfSubsets(iter, subsets, indices, units) == subsetSize)
{ var row_chain = " "; var row_name = (type == 1) ? mlText.columns : mlText.rows; for (var i = 0; i < units.length; ++i)
row_chain = row_chain + "[" + ((type == 0) ? this.yName[units[i]] : this.xName[units[i]]) + "] "; var col_chain = " "; var col_name = (type == 0) ? mlText.columns : mlText.rows; for (var i = 0; i < indices.length; ++i)
col_chain = col_chain + "[" + ((type == 1) ? this.yName[indices[i]] : this.xName[indices[i]]) + "] "; for (var i = 0; i < units.length; ++i)
{ for (var j = 0; j < indices.length; ++j)
{ if(type==0) highlightArray.push(new Array('cell',indices[j],units[i],'#98FB98')); else highlightArray.push(new Array('cell',units[i],indices[j],'#98FB98'));}
}
this.addNote(mlText.methodF3_found,{val:candidate,row_name:row_name,row_chain:row_chain,col_name:col_name,col_chain:col_chain}); for (var unit = 0; unit < 9; ++unit)
{ if (!arrayContains(units, unit))
{ for (var i = 0; i < subsetSize; ++i)
{ var index = indices[i]; removed+=this.removeSubgridCandidate(cells[unit][index], candidate, highlightArray);}
}
}
if (subsetSize == 2)
{ var unit1 = units[0]; var unit2 = units[1]; if (Math.floor(unit1/3) == Math.floor(unit2/3))
{ removed+=this.clearCandidateFromBlock(cells, cells[unit1][indices[0]], cells[unit2][indices[0]], candidate); removed+=this.clearCandidateFromBlock(cells, cells[unit1][indices[1]], cells[unit2][indices[1]], candidate);}
}
if(removed>0)
{ this.difficultyScore+=score; totalRemoved+=removed;}
}
}
while (iter.increment() && !this.breakOut); return totalRemoved;}
Sudoku.prototype.clearCandidateFromBlock = function(cells, cellA, cellB, candidate, highlightArray)
{ var block = Math.floor(cellA/27)*3 + Math.floor((cellA%9)/3); var blockCells = this.cells.BLOCK_INDICES[block]; var removed=0; for (var index = 0; index < 9; ++index)
{ if (blockCells[index] != cellA && blockCells[index] != cellB)
{ removed+=this.removeSubgridCandidate(blockCells[index], candidate, highlightArray);}
}
return removed;}
Sudoku.prototype.removeSubgridCandidate = function(cell, candidate, highlightArray)
{ var row = Math.floor(cell/9); var col = cell%9; var removed=0; if (this.isValAtXY(col, row, candidate))
{ removed++; this.addNote(mlText.methodF3_removing,{val:candidate,cell:this.cellString(col,row)}); if(highlightArray)
{ highlightArray.push(new Array('cell',col,row,'pink')); if(this.step) this.explainAndPause(highlightArray); highlightArray.pop();}
this.remove(col, row, candidate);}
return removed;}
function ColFromCell()
{ this.dummy = 1;}
ColFromCell.prototype.func = function(cell) { return cell%9;}
function RowFromCell()
{ this.dummy = 1;}
RowFromCell.prototype.func = function(cell) { return Math.floor(cell/9);}
Sudoku.prototype.solveBlocksWithSharedUnits = function(score)
{ this.cells = new CellArrays(); var blocks = new BlockArrays(); var rowFunc = new RowFromCell(); var colFunc = new ColFromCell(); var removed = 0; for (var candidate = 1; candidate <= 9 ; ++candidate)
{ for (var index = 0; index < 9 ; ++index)
{ removed+=this.solveBlocksWithSharedUnitsImpl(blocks.BLOCK_ROW_INDICES[index], candidate, rowFunc);}
for (var index = 0; index < 9 ; ++index)
{ removed+=this.solveBlocksWithSharedUnitsImpl(blocks.BLOCK_COLUMN_INDICES[index], candidate, colFunc);}
}
if(removed>0) this.difficultyScore+=score; return removed;}
Sudoku.prototype.solveBlocksWithSharedUnitsImpl = function(blocks, candidate, calc)
{ var units1 = new Array(); var removed = 0; if (this.getUnitIndicesInBlock(this.cells.BLOCK_INDICES[blocks[0]], candidate, calc, units1) == 2)
{ var units2 = new Array(); if (this.getUnitIndicesInBlock(this.cells.BLOCK_INDICES[blocks[1]], candidate, calc, units2) == 2)
{ if (units1[0] == units2[0] && units1[1] == units2[1])
{ for (var index = 0; index < 9; ++index)
{ var cell = this.cells.BLOCK_INDICES[blocks[2]][index]; var unit = calc.func(cell); if (unit == units1[0] || unit == units1[1])
{ removed+=this.removeSubgridCandidate(cell, candidate);}
}
}
}
}
return removed;}
function indexLink(index,cvs)
{ this.index=index; this.cvs=cvs;}
indexLink.prototype.showHTML=function()
{ return "["+this.index+" - "+this.cvs+" ]"
}
function ChainFinder(chainArray)
{ this.chainArray=chainArray; this.foundChainSet=new Array(); this.foundChainSetNumbers=new Array(); this.inChain=new Array();}
ChainFinder.prototype.findChains=function()
{ var i,linkSet; this.foundChainSet=null; this.foundChainSet=new Array(); linkSet=new Array(); for(i=0;i<this.chainArray.length;i++)
{ linkSet[i]=new indexLink(i,this.chainArray[i]); this.inChain[i]=null;}
this.getChain(linkSet);}
ChainFinder.prototype.debug=function(str)
{ }
ChainFinder.prototype.showHTML=function()
{ var i,j,a0; a0="<table><tr><td>Index</td><td>Chain</td></tr>"; for(i=0;i<this.foundChainSet.length;i++)
{ a0+="<tr><td>"+i+":</td><td>"
for(j=0;j<this.foundChainSet[i].length;j++)
{ a0+="["+this.foundChainSet[i][j].index+" - "+this.foundChainSet[i][j].cvs+" ]";}
a0+="</td></tr>";}
a0+="</table>"; return a0;}
ChainFinder.prototype.isChain=function(linkedSet,link)
{ var i,j,ct,cArr,cv; cArr=new Array(); for(i=1;i<=9;i++)
{ cArr[i]=0;}
ct=link.cvs.length; for(i=0;i<linkedSet.length;i++)
{ if(linkedSet[i].cvs.length!=ct) return false;}
for(i=0;i<linkedSet.length;i++)
{ for(j=0;j<linkedSet[i].cvs.length;j++)
{ cv=linkedSet[i].cvs.charCodeAt(j)-48; cArr[cv]++;}
}
for(j=0;j<link.cvs.length;j++)
{ cv=link.cvs.charCodeAt(j)-48; cArr[cv]++;}
for(i=1;i<=9;i++)
{ if(cArr[i]!=0 && cArr[i]!=ct) return false;}
return true;}
ChainFinder.prototype.areLinked=function(linkA,linkB)
{ var i,c; if(linkA.cvs.length!=linkB.cvs.length) return false; return linkA.cvs.anyMatch(linkB.cvs);}
ChainFinder.prototype.chainElements=function(chain)
{ var a0,c,i,j; a0=""; for(i=0;i<chain.length;i++)
{ for(j=0;j<chain[i].cvs.length;j++)
{ c=chain[i].cvs.charAt(j)
if(a0.indexOf(c)<0) a0+=c;}
}
return a0;}
ChainFinder.prototype.saveChain=function(chain)
{ var i,copyChain; copyChain=new Array(); for(i=0;i<chain.length;i++)
{ copyChain[i]=new indexLink(chain[i].index,chain[i].cvs)
this.inChain[chain[i].index]=this.foundChainSet.length;}
this.foundChainSet.push(copyChain); this.foundChainSetNumbers.push(this.chainElements(copyChain));}
ChainFinder.prototype.getChain=function(remainingLinks,link,linkedSet,unLinkedSet)
{ var i,testLink; this.debug("<br>Getting chain length "+remainingLinks.length); this.debug(remainingLinks.showHTML()); if(link==null || linkedSet==null || unLinkedSet==null)
{ while(remainingLinks.length>0)
{ link=remainingLinks.shift(); this.getChain(remainingLinks,link,new Array(),new Array());}
}
else
{ this.debug(link.showHTML()); this.debug(linkedSet.showHTML()); this.debug(unLinkedSet.showHTML());}
if(link.cvs.length==1) return; while(remainingLinks.length>0)
{ testLink=remainingLinks.shift(); if(this.areLinked(link, testLink))
{ this.debug(link.showHTML()+" and "+testLink.showHTML()+" are linked."); linkedSet.push(link); link=testLink; if(this.isChain(linkedSet,link))
{ this.debug("Found set "+linkedSet.showHTML()); linkedSet.push(link); this.saveChain(linkedSet); while(remainingLinks.length>0)
{ unLinkedSet.push(remainingLinks.shift());}
break;}
else
{ this.debug("Not a chain though - recursing.."); for(i=0;i<unLinkedSet.length;i++)
{ if(unLinkedSet[i].cvs.length==link.cvs.length)
{ spliceArr=unLinkedSet.splice(i,1); if(spliceArr[0]) {spliceItem=spliceArr[0];} else {spliceItem=spliceArr;}
remainingLinks.push(spliceItem);}
}
this.getChain(remainingLinks,testLink,linkedSet,unLinkedSet); link=linkedSet.pop();}
}
else
{ unLinkedSet.push(testLink);}
}
if(unLinkedSet!=null && unLinkedSet.length>0) this.getChain(unLinkedSet)
}



