let row = this.findEmpty(puzzleString)[0];
let col = this.findEmpty(puzzleString)[1];
let i = this.findEmpty(puzzleString)[2];
if(!this.findEmpty(puzzleString)) return puzzleString
for(let num = 1; num < 10; num++){
if(this.checkValue(puzzleString, row, col, num)){
puzzleString[i] = num;
this.solve(puzzleString)
}
}
findEmpty(puzzleString)
iterates over the puzzle string and returns the row (A-I), column (1-9), and index of a blank grid.
checkValue()
contains 3 helper functions returning a boolean ensuring there are no conflicts across row, column, or region.
The loop iterates from 1-9 and the first value from 1-9 that passes checkValue()
is assigned to the current blank grid and then triggers recursion by calling the parent function solve()
.
What I don't understand is the next statement and how that triggers backtracking.
if(this.findEmpty(puzzleString)){
puzzleString[i] = '.';
}
If the current blank grid being checked has no solution then I think the grid remains a blank ('.'
). If this is correct, why is this statement necessary? What about this statement is triggering backtracking?
My initial inclination is that this statement is a psuedo-else statement that runs only if the loop fails to find a solution. It has to be placed outside the loop in order to allow the full iteration of 1 through 9. But then how does the code know to run solve()
afterwards if solve()
is only called if checkValue()
suceeds?
Here's the full code:
solve(puzzleString) {
let row = this.findEmpty(puzzleString)[0];
let col = this.findEmpty(puzzleString)[1];
let i = this.findEmpty(puzzleString)[2];
if(!this.findEmpty(puzzleString)) return puzzleString
for(let num = 1; num < 10; num++){
if(this.checkValue(puzzleString, row, col, num)){
puzzleString[i] = num;
this.solve(puzzleString)
}
}
if(this.findEmpty(puzzleString)){
puzzleString[i] = '.';
}
if(puzzleString.includes('.')) return { error: 'Puzzle cannot be solved' }
return {
solution: puzzleString.join('')
}
}
findEmpty(puzzleString){
for(let i = 0; i < puzzleString.length; i++){
if(puzzleString[i] == '.'){
let row = String.fromCharCode('A'.charCodeAt(0) + Math.floor(i / 9));
let col = (i % 9) + 1;
return [row, col, i];
}
}
return false;
}
checkValue(puzzleString, row, column, value){
if(this.checkRowPlacement(puzzleString, row, column, value)&&
this.checkColPlacement(puzzleString, row, column, value)&&
this.checkRegionPlacement(puzzleString, row, column, value)){
return true;
}
return false;
}
checkRowPlacement(puzzleString, row, column, value) {
let coordinates = [];
let rowLetter;
let colNum;
let temp = [];
if(row){row = row.toUpperCase();}
for(let i = 0; i < puzzleString.length; i++){
rowLetter = String.fromCharCode('A'.charCodeAt(0) + Math.floor(i / 9));
colNum = (i % 9) + 1;
coordinates.push(rowLetter + colNum);
}
for(let i = 0; i < coordinates.length; i++){
if(coordinates[i][0] == row){
temp.push(puzzleString[i]);
}
}
temp = temp.join('');
return !temp.includes(value) ? true : false
}
checkColPlacement(puzzleString, row, column, value) {
let coordinates = [];
let rowLetter;
let colNum;
let temp = [];
if(row){row = row.toUpperCase();}
for(let i = 0; i < puzzleString.length; i++){
rowLetter = String.fromCharCode('A'.charCodeAt(0) + Math.floor(i / 9));
colNum = (i % 9) + 1;
coordinates.push(rowLetter + colNum);
}
for(let i = 0; i < coordinates.length; i++){
if(coordinates[i][1] == column){
temp.push(puzzleString[i]);
}
}
temp = temp.join('');
return !temp.includes(value) ? true : false
}
checkRegionPlacement(puzzleString, row, column, value) {
let coordinates = [];
let rowLetter;
let colNum;
let regions = [];
if(row) row = row.toUpperCase();
for(let i = 0; i < puzzleString.length; i++){
rowLetter = String.fromCharCode('A'.charCodeAt(0) + Math.floor(i / 9));
colNum = (i % 9) + 1;
coordinates.push(rowLetter + colNum);
}
for(let i = 0; i < coordinates.length; i+=27){
for(let k = 0; k < 9; k+=3){
regions.push(
coordinates.slice(i+k,i+k+3) + ',' +
coordinates.slice(i+k+9, i+k+12) + ',' +
coordinates.slice(i+k+18, i+k+21)
)
}
}
let region = regions.filter(x => x.includes(row + column))[0].split(',').map(x => puzzleString[coordinates.indexOf(x)]).join('');
return region.includes(value) ? false : true;
}