1

I recently wrote this same code in Golang with some help from here.

If you are familiar with go you can see the working code here.

Go Playground

Here is what I am trying to accomplish in python. Computerphile Video

I am now trying to port this to javascript.

I initialize the gameState.

var gameState = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9]
]

This is the grid taken from the sudoku Wikipedia page.

Sudoku Wiki

I wrote the following helper functions to get the row, column and block units of anywhere on the grid. I've tested them all and they all seem to work fine.

function isUnitUnique(unit) {
    for (let value = 1; value <= 9; value++) {
        let tally = 0;
        for (let index = 0; index < 9; index++) {
            if (unit[index] == value) {
                tally++;
            }
        }
        if (tally > 1) {
            return false;
        }
    }
    return true;
}
function getColumnUnit(board, column) {
    let unit = [];
    for (let row = 0; row < 9; row++) {
        unit.push(board[row][column]);
    }
    return unit;
}
function getBlockUnit(board, x, y) {
    let unit = []
    if (x >= 0 && x <= 2) { j = 1; }
    else if (x >= 3 && x <= 5) { j = 4; }
    else if (x >= 6 && x <= 8) { j = 7; }
    if (y >= 0 && y <= 2) { i = 1; }
    else if (y >= 3 && y <= 5) { i = 4; }
    else if (y >= 6 && y <= 8) { i = 7; }
    unit.push(board[i - 1][j - 1]);
    unit.push(board[i - 1][j]);
    unit.push(board[i - 1][j + 1]);
    unit.push(board[i][j - 1]);
    unit.push(board[i][j]);
    unit.push(board[i][j + 1]);
    unit.push(board[i + 1][j - 1]);
    unit.push(board[i + 1][j]);
    unit.push(board[i + 1][j + 1]);
    return unit;
}

I then use the helper functions with the following code to try and solve the puzzle. This is a recursive backtracking algorithm.

function solve() {
    for (let row = 0; row < 9; row++) {
        for (let column = 0; column < 9; column++) {
            if (gameState[row][column] == 0) {
                for (let value = 1; value <= 9; value++) {
                    if (possible(row, column, value)) {
                        gameState[row][column] = value;
                        solve();
                        gameState[row][column] = 0;
                    }
                }
                return;
            }
        }
    }
    console.log(gameState);
    return;
}
function possible(y, x, n) {
    let boardCopy = JSON.parse(JSON.stringify(gameState));
    boardCopy[y][x] = n;
    return isUnitUnique(boardCopy[y]) && isUnitUnique(getColumnUnit(boardCopy, x)) && isUnitUnique(getBlockUnit(boardCopy, x, y))
}

This console logs the initial game state unchanged. From doing some debugging I can see that the algorithm is going through the grid however it doesn't seem to keep the changes that I make to the grid.

Thanks in advance.

As suggested here is a runnable version of my code. It works when I run it in the snippet. When using chrome it doesn't.

var gameState = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9]
]
function solve() {
    for (let row = 0; row < 9; row++) {
        for (let column = 0; column < 9; column++) {
            if (gameState[row][column] == 0) {
                for (let value = 1; value <= 9; value++) {
                    if (possible(row, column, value)) {
                        gameState[row][column] = value;
                        solve();
                        gameState[row][column] = 0;
                    }
                }
                return;
            }
        }
    }
    console.log(gameState);
    return;
}
function possible(y, x, n) {
    let boardCopy = JSON.parse(JSON.stringify(gameState));
    boardCopy[y][x] = n;
    return isUnitUnique(boardCopy[y]) && isUnitUnique(getColumnUnit(boardCopy, x)) && isUnitUnique(getBlockUnit(boardCopy, x, y))
}
function isUnitUnique(unit) {
    for (let value = 1; value <= 9; value++) {
        let tally = 0;
        for (let index = 0; index < 9; index++) {
            if (unit[index] == value) {
                tally++;
            }
        }
        if (tally > 1) {
            return false;
        }
    }
    return true;
}
function getColumnUnit(board, column) {
    let unit = [];
    for (let row = 0; row < 9; row++) {
        unit.push(board[row][column]);
    }
    return unit;
}
function getBlockUnit(board, x, y) {
    let unit = []
    if (x >= 0 && x <= 2) { j = 1; }
    else if (x >= 3 && x <= 5) { j = 4; }
    else if (x >= 6 && x <= 8) { j = 7; }
    if (y >= 0 && y <= 2) { i = 1; }
    else if (y >= 3 && y <= 5) { i = 4; }
    else if (y >= 6 && y <= 8) { i = 7; }
    unit.push(board[i - 1][j - 1]);
    unit.push(board[i - 1][j]);
    unit.push(board[i - 1][j + 1]);
    unit.push(board[i][j - 1]);
    unit.push(board[i][j]);
    unit.push(board[i][j + 1]);
    unit.push(board[i + 1][j - 1]);
    unit.push(board[i + 1][j]);
    unit.push(board[i + 1][j + 1]);
    return unit;
}
solve()
  • What is this line `gameState[row][column] = 0;` supposed to do? – bcr666 Feb 18 '20 at 20:42
  • I see, that is part of the backtracking. What happens when it reaches the answer though, isn't it going to zero out everything on the way back out? – bcr666 Feb 18 '20 at 21:05
  • @bcr666 When it calls solve() directly above that it enters the function with the game state updated to the new potential variable. It keeps doing this until reaches a point where there are no valid values (1-9) and it hits the first return statement. This then reenters the function from prior and changes the value to zero, as the value it was before couldn't find a solution. Eventually, it gets to a point where there are no more cases of 0 values on the grid, it exits the for loops and outputs the grid. I'll add a youtube video that shows it being done in python. –  Feb 18 '20 at 21:35
  • Go links, YouTube videos of Python, Wikipedia links... What's missing is a [mre] which shows the code in action, including the part where it's not working. You can likely use [Stack Snippets](https://meta.stackoverflow.com/q/358992/215552) to create something that runs her on Stack Overflow so people don't need to go to other sites to figure out your question. Thanks. – Heretic Monkey Feb 18 '20 at 22:10
  • @HereticMonkey Thanks for the advice. I added a stack snippet of the code. Interestingly enough it runs and produces the expected output. I wonder why it doesn't locally on my VS Code / chrome environment. –  Feb 18 '20 at 22:48

1 Answers1

0

Thanks to @HereticMonkey for suggesting I use stack snippets to share my code. In doing so I realized that it worked in this environment, and that it had to do with running the code in the browser that was causing the issue (Chrome). Also thanks to @bcr666 who also mentioned that potentially the algorithm was zeroing itself out on the way back.

The code listed in the question was all correct. I added one check to prevent the function from continuing to execute after it reached its exit condition.

let solved = false
function solve() {
    for (let row = 0; row < 9; row++) {
        for (let column = 0; column < 9; column++) {
            if (gameState[row][column] == 0) {
                for (let value = 1; value <= 9; value++) {
                    if (possible(row, column, value)) {
                        gameState[row][column] = value;
                        solve();
                        if (!solved) {
                            gameState[row][column] = 0;
                        }
                    }
                }
                return;
            }
        }
    }
    solved = true;
    console.log(gameState);
    return;
}

Checking if the game has been solved before backtracking fixed this issue. Before it would log the results it was attempting to find more solutions.