Рекурсивный решатель судоку Javascript с возвратом

Недавно я написал этот же код на Golang с некоторой помощью отсюда.

Если вы знакомы с go, вы можете увидеть рабочий код здесь.

Площадка для игр

Вот что я пытаюсь сделать в python. Компьютерное видео

Сейчас я пытаюсь перенести это на javascript.

Я инициализирую 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]
]

Это сетка, взятая со страницы судоку в Википедии.

Вики-судоку

Я написал следующие вспомогательные функции для получения единиц строки, столбца и блока в любом месте сетки. Я проверил их все, и все они, кажется, работают нормально.

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;
}

Затем я использую вспомогательные функции со следующим кодом, чтобы попытаться решить головоломку. Это рекурсивный алгоритм поиска с возвратом.

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))
}

Эта консоль записывает начальное состояние игры без изменений. Из некоторой отладки я вижу, что алгоритм проходит через сетку, но, похоже, он не сохраняет изменения, которые я вношу в сетку.

Заранее спасибо.

Как предлагается здесь, это рабочая версия моего кода. Это работает, когда я запускаю его во фрагменте. При использовании хрома это не так.

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()


person Ben Lucier    schedule 18.02.2020    source источник
comment
Что должна делать эта строка gameState[row][column] = 0;?   -  person bcr666    schedule 18.02.2020
comment
Я вижу, это часть отступления. Что происходит, когда он достигает ответа, разве он не обнуляет все на обратном пути?   -  person bcr666    schedule 19.02.2020
comment
@bcr666 bcr666 Когда он вызываетsolve() непосредственно выше, он входит в функцию с обновленным состоянием игры до новой потенциальной переменной. Он продолжает делать это до тех пор, пока не достигнет точки, где нет допустимых значений (1-9), и не попадет в первый оператор возврата. Затем это повторно входит в функцию из предыдущего и изменяет значение на ноль, поскольку значение, которое было раньше, не может найти решение. В конце концов, он достигает точки, когда в сетке больше нет случаев нулевых значений, он выходит из цикла for и выводит сетку. Я добавлю видео на YouTube, показывающее, как это делается на питоне.   -  person Ben Lucier    schedule 19.02.2020
comment
Ссылки на Go, видео на YouTube о Python, ссылки на Википедию... Чего не хватает, так это минимально воспроизводимого примера, который показывает код в действии, включая ту часть, где он не работает. Вероятно, вы можете использовать фрагменты стека, чтобы создать что-то, что запускает ее в Stack Overflow, чтобы людям не нужно было переходить на другие сайты, чтобы выяснить ваш вопрос. Спасибо.   -  person Heretic Monkey    schedule 19.02.2020
comment
@HereticMonkey Спасибо за совет. Я добавил фрагмент кода стека. Интересно, что он работает и выдает ожидаемый результат. Интересно, почему это не работает локально в моей среде VS Code/chrome.   -  person Ben Lucier    schedule 19.02.2020


Ответы (1)


Спасибо @HereticMonkey за предложение использовать фрагменты стека, чтобы поделиться своим кодом. При этом я понял, что это работало в этой среде и что это было связано с запуском кода в браузере, который вызывал проблему (Chrome). Также спасибо @bcr666, который также упомянул, что потенциально алгоритм обнуляется на обратном пути.

Код, указанный в вопросе, был правильным. Я добавил одну проверку, чтобы предотвратить продолжение выполнения функции после того, как она достигла состояния выхода.

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;
}

Проверка того, была ли игра решена перед откатом, устранила эту проблему. Прежде чем он зарегистрирует результаты, он пытался найти больше решений.

person Ben Lucier    schedule 18.02.2020