首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >数独游戏,自动生成数独

数独游戏,自动生成数独
EN

Code Review用户
提问于 2020-11-23 12:15:58
回答 2查看 551关注 0票数 3

项目

我用自动生成的sudokus创建了一个益智网站。

有关Sudokus的更多信息,请参见维基百科文章

代码

代码语言:javascript
复制
const GRID_SIZE = 9;
const REMOVE = 50;

let solution = createArray(9, 9);

let textIsVisible = false;
window.addEventListener('load', function() {
    initialize();
});

document.getElementById('submit').addEventListener('click', function() {
    let elements = document.getElementsByTagName('input');
    let k = 0;
    for(let i = 0; i < 9; i++) {
        for(let j = 0; j < 9; j++) {
            let value = parseInt(elements[k].value);
            if(value !== solution[i][j]) {
                textIsVisible = true;
                $('#text').text('That\'s not the correct solution!');
                return;
            }
            k++;
        }
    }
    textIsVisible = true;
    $('#text').text('Correct!');
});

document.getElementById('reset').addEventListener('click', function() {
    initialize();
});

function initialize() {
    textIsVisible = false;
    $('#text').text('');
    let elements = document.getElementsByTagName('input');
    for(let i = 0; i < elements.length; i++) {
        elements[i].classList.add('focus');
        elements[i].removeAttribute('readonly');
        elements[i].classList.remove('bold');
        elements[i].addEventListener('click', function() {
            $('#text').text('');
        });
    }
    let sudoku = generate();
    solveSudoku(sudoku, 0);
    showSudoku(elements, sudoku);
}

function generate() {
    //Fill with zeros
    let sudoku = createArray(9, 9);
    for(let i = 0; i < 9; i++) {
        for (let j = 0; j < 9; j++) {
            sudoku[i][j] = 0;
        }
    }

    //Add ten random numbers
    for(let i = 0; i < 10; i++) {
        sudoku = addNumber(sudoku);
    }

    /*
     * Add random numbers in the first row in addition to random numbers
     * in addition to random numbers on random positions
     */
    sudoku[0][1] = legalNumbers(sudoku, 0, 1);
    sudoku[0][4] = legalNumbers(sudoku, 0, 4);
    sudoku[0][7] = legalNumbers(sudoku, 0, 7);


    //Solve sudoku
    solveSudoku(sudoku, 0);
    for(let i = 0; i < 9; i++) {
        for (let j = 0; j < 9; j++) {
            sudoku[i][j] = solution[i][j];
        }
    }

    /*
     * Remove elements so that sudoku still has unique solution
     */
    let i = 0;
    while(i < REMOVE) {
        let row = Math.floor(Math.random() * 9);
        let col = Math.floor(Math.random() * 9);
        if(sudoku[row][col] !== 0) {
            let cache = sudoku[row][col];
            sudoku[row][col] = 0;
            if(solveSudoku(sudoku, 0) !== 1) {
                /*
                 * Go one step back if sudoku doesn't have unique solution
                 * anymore
                 */
                sudoku[row][col] = cache;
            }
            else {
                i++;
            }
        }
    }
    return sudoku;
}

function createArray(rows, cols) {
    const array = new Array(rows);
    for (let i = 0; i < cols; i++) {
        array[i] = new Array(cols);
    }
    return array;
}

function addNumber(sudoku) {
    /*
     * Find random position to add number
     */
    let row = Math.floor(Math.random() * 9);
    let col = Math.floor(Math.random() * 9);

    /*
     * Add random, but legal number
     */
    sudoku[row][col] = legalNumbers(sudoku, row, col);
    return sudoku;
}

function solveSudoku(sudoku, count) {
    for(let i = 0; i < GRID_SIZE; i++) {
        for (let j = 0; j < GRID_SIZE; j++) {
            /*
             * Only empty fields will be changd
             */
            if(sudoku[i][j] === 0) {
                /*
                 * Try all numbers between 1 and 9
                 */
                for(let n = 1; n <= GRID_SIZE && count < 2; n++) {
                    /*
                     * Is number n safe?
                     */
                    if(checkRow(sudoku, i, n) && checkColumn(sudoku, j, n) && checkBox(sudoku, i, j, n)) {
                        sudoku[i][j] = n;
                        let cache = solveSudoku(sudoku, count);
                        /*
                         * new solution found
                         */
                        if(cache > count) {
                            count = cache;
                            /*
                             * Save new solution
                             */
                            for(let k = 0; k < GRID_SIZE; k++) {
                                for(let l = 0; l < GRID_SIZE; l++) {
                                    if(sudoku[k][l] !== 0) {
                                        solution[k][l] = sudoku[k][l];
                                    }
                                }
                            }
                            sudoku[i][j] = 0;
                        }
                        /*
                         * Not a solution, go one step back
                         */
                        else {
                            sudoku[i][j] = 0;
                        }

                    }
                }
                /*
                 * No other solution found
                 */
                return count;
            }
        }
    }
    /*
     * found another solution
     */
    return count + 1;
}

function showSudoku(elements, sudoku) {
    let k = 0;
    for(let i = 0; i < GRID_SIZE; i++) {
        for(let j = 0; j < GRID_SIZE; j++) {
            if(sudoku[i][j] > 0) {
                elements[k].value = sudoku[i][j];
                elements[k].setAttribute('readonly', 'true');
                elements[k].classList.remove('focus');
                elements[k].classList.add('bold');
            }
            else {
                elements[k].value = '';
            }
            k++;
        }
    }
}


/*
 * Helper functions
 */

function checkRow(sudoku, row, n) {
    for(let i = 0; i < GRID_SIZE; i++) {
        if(sudoku[row][i] === n) {
            return false;
        }
    }
    return true;
}

function checkColumn(sudoku, col, n) {
    for(let i = 0; i < GRID_SIZE; i++) {
        if(sudoku[i][col] === n) {
            return false;
        }
    }
    return true;
}

function checkBox(sudoku, row, col, n) {
    row = row - row % 3;
    col = col - col % 3;
    for(let i = row; i < row + 3; i++) {
        for(let j = col; j < col + 3; j++) {
            if(sudoku[i][j] === n) {
                return false;
            }
        }
    }
    return true;
}

function legalNumbers(sudoku, row, col) {
    let array = [];
    for(let i = 1; i <= 9; i++) {
        if(checkRow(sudoku, row, i) && checkColumn(sudoku, col, i) && checkBox(sudoku, row, col, i)) {
            array.push(i);
            break;
        }
    }
    return array[Math.floor(Math.random() * array.length)];
}

function isNumber(elem, e) {
    if(elem.value.length !== 0) {
        return false;
    }
    let id = e.key;
    let string = '123456789';
    for(let i = 0; i < string.length; i++) {
        if(string.charAt(i) === id) {
            return true;
        }
    }
    return false;
}
代码语言:javascript
复制
    Sudoku
    
        /*
         * Font sizes
         */
        html {
            font-size: 18px;
        }

        p {
            font-size: 1em;
        }

        h1 {
            font-size: 1.8em;
        }

        h3 {
            font-size: 1.42em;
        }

        /*
         * 'Fork me on GitHub'-image
         */
        #fork {
            position: absolute;
            width: 160px;
            height: 160px;
            top: 0;
            border: 0;
            right: 0;
        }

        /*
         * Main style
         */
        body {
            font-family: 'open-sans',sans-serif;
            font-weight: normal;
            line-height: 1.65;
            margin-top: 30px;
        }

        .inner {
            width: 350px;
            margin: 0 auto;
        }

        /*
         * Sudoku field container
         */
        .sudoku {
            line-height: 34px;
            text-align: center;
        }

        .mainContainer {
            display: inline-block;
        }

        .container {
            line-height: 36px;
        }

        h1 {
            font-family: 'balooregular', serif;
            font-weight: normal;
            color: #00410B;
        }

        /*
         * Input fields main style
         */
        input {
            border: none;
            text-align: center;
            font-size: 1em;
            width: 30px;
            height: 30px;
            float: left;
            border-right: 2px solid black;
            border-bottom: 2px solid black;
        }

        /*
         * Don't show arrow buttons inside input fields
         */
        input::-webkit-outer-spin-button,
        input::-webkit-inner-spin-button {
            display: none;
        }

        input[type=number] {
            -moz-appearance:textfield;
        }

        input:focus, button:focus {
            outline: none;
        }

        /*
         * Input field additional border styles
         */
        .left {
            border-left: 2px solid black;
        }

        .right {
            border-right: 2px solid red;
        }

        .top {
            border-top: 2px solid black;
        }

        .bottom {
            border-bottom: 2px solid red;
        }


        /*
         * Given numbers will be bold
         */
        .bold {
            font-weight: bold;
            font-size: 1em;
        }

        /*
         * Fields that user has to fill out
         * will be LightGray on focus
         */
        .focus:focus {
            background: LightGray;
        }

        /*
         * Style of buttons
         */
        button {
            border: 1px solid black;
            border-radius: 5px;
            padding: 10px;
            width: 40%;
            text-align: center;
            transition: 0.3s ease;
            margin: 20px 5% 30px;
            float: left;
        }

        button:hover {
            color: #FFFFF0;
            background: #00410B;
        }

        /*
         * links
         */
        a {
            color: #2B823A;
            text-decoration: none;
        }

        a:hover {
            text-decoration: underline;
        }

        /*
         * Responsive
         */

        @media (max-width: 650px) {
            #fork {
                display: none;
            }
        }

        @media (max-width: 350px) {
            html {
                font-size: 16px;
            }

            .inner {
                width: 100%;
            }
        }

        @media (max-width: 330px) {
            .sudoku {
                line-height: 24px;
                text-align: center;
            }

            .container {
                line-height: 26px;
            }

            input {
                border: none;
                text-align: center;
                font-size: 1em;
                width: 20px;
                height: 20px;
                float: left;
                border-right: 2px solid black;
                border-bottom: 2px solid black;
            }
        }

    


    
    
        Sudoku
        
            
                
                    

                
                

                

                

                

                

                

                

                

            
        

        SubmitNew Game

问题

欢迎所有建议。html文件只是一个极小的工作示例,因此它实际上不值得审查。

EN

回答 2

Code Review用户

回答已采纳

发布于 2020-11-23 17:50:59

Always使用 const when (可能)--仅在需要重新分配变量名的想要警告其他读者使用代码时才使用let。如果不重新分配某些内容,请向读者表明,他们不必担心使用const而不是let

一个函数的Anonymous回调包装器是多余的,至少在几乎所有情况下都是如此--只需将单个函数作为回调传递即可(参见下面)。

Prefer DOMContentLoaded over load - load等待所有资源完全完成下载,包括可能的重量级图像。最好是在DOM被填充后立即使页面具有交互性,而不是等待子资源完成。

代码语言:javascript
复制
window.addEventListener('DOMContentLoaded', initialize);

或者,由于您使用的是jQuery,您也可以这样做:

代码语言:javascript
复制
$(initialize);

jQuery还是普通DOM方法?目前,您只为一个目的使用jQuery --调用.text,设置元素的文本。这很奇怪: jQuery是一个很大的库,可以简单地使用textContent来设置元素文本,而不需要库。如果您确实想将它变成一个jQuery项目(我不建议这样做,因为它目前似乎还没有完成任何有用的事情),那么无论何时使用DOM操作,使用jQuery在风格上都会更加一致。

textIsVisible 不执行任何引用4次,但总是分配给它,永远不要从中读取-最好完全删除它。

Prefer数组方法和迭代器在需要手动迭代且没有抽象的for循环上。例如,考虑到:

代码语言:javascript
复制
let elements = document.getElementsByTagName('input');
for(let i = 0; i < elements.length; i++) {
    elements[i].classList.add('focus');
    // do lots of other stuff with elements[i]

除非您需要在其他地方使用索引,而不是引用elements[i],否则用迭代器编写干的和更抽象的代码就更有意义了。它也更短:

代码语言:javascript
复制
for (const element of elements) {
    element.classList.add('focus');
    // do lots of other stuff with element

Aleksei的回答也展示了如何在代码的其他部分中使用数组方法。

Condense类名,因为单元格总是有两个类中的一个,即focusbold,可以考虑只使用一个类来切换--也许是focus。例如,而不是:

代码语言:javascript
复制
.bold {
    font-weight: bold;
    font-size: 1em;
}

使用

代码语言:javascript
复制
.container > input {
    font-weight: bold;
    font-size: 1em;
}

其规则被下面的.container > .focus规则覆盖。

不要将一个变量重新分配到您已经有一个引用的同一个对象。这就是:

代码语言:javascript
复制
sudoku = addNumber(sudoku);

应该是

代码语言:javascript
复制
addNumber(sudoku);

Use注释来描述为什么,或者什么如果 what不是明显的-例如:

代码语言:javascript
复制
// Go one step back if sudoku doesn't have unique solution anymore

是好的,但是

代码语言:javascript
复制
//Solve sudoku
solveSudoku(sudoku, 0);

并不能传达任何有用的信息。

solveSudoku有些混乱:它返回一个调用者经常不使用的值,并将解决方案重新分配到一个与调用方完全断开连接的变量中。例如:

代码语言:javascript
复制
let sudoku = generate();
solveSudoku(sudoku, 0);
showSudoku(elements, sudoku);

solveSudoku的目的是神秘的,直到你阅读这个函数来找出它的副作用是什么。函数是最容易理解的一目了然,当它们是纯粹的,或至少不是太不纯。在这里,考虑从solveSudoku返回值并使用它,例如:

代码语言:javascript
复制
const sudoku = generate();
solution = solveSudoku(sudoku, 0).solution; // with `numUniqueSolutions` as the other property in the object
showSudoku(elements, sudoku);

乍一看,这更有意义。您可以更进一步,完全避免重新分配solution变量,但是这需要一些重要的重构。

上述方法还使solveSudoku返回的值类型更加清晰:numUniqueSolutions (从当前的solveSudoku实现看,这一点并不明显)。

票数 4
EN

Code Review用户

发布于 2020-11-23 15:57:57

我建议用函数式编写代码,而不是过多地使用循环。这将使您的代码更加简洁(您可以将解决方案的代码减少一半)和可读性。我重写了你的几个函数,让你明白我的意思:

代码语言:javascript
复制
const range = (from, to) => [...Array(to - from + 1).keys()].map((i) => i + from);

const checkRow = (sudoku, row, n) => range(0, GRID_SIZE - 1).every((x) => sudoku[row][x] !== n);
const checkColumn = (sudoku, col, n) => range(0, GRID_SIZE - 1).every((x) => sudoku[x][col] !== n);

const combine = (arr1, arr2) => arr1.reduce((result, x) => result.concat(arr2.map((y) => [x, y])), []);

const boxCells = (row, col) => {
    row = row - row % 3;
    col = col - col % 3;
    return combine(range(row, row + 2), range(col, col + 2));
}

const checkBox = (sudoku, row, col, n) => boxCells(row, col).every(([r, c]) => sudoku[r][c] !== n);

const legalNumbers = (sudoku, row, col) => {
    const valid = (x) => checkRow(sudoku, row, x) && checkColumn(sudoku, col, x) && checkBox(sudoku, row, col, x);
    return range(1, 9).find(valid);
}

const isNumber = (elem, { key }) => elem.value === '' && '123456789'.split('').includes(key);
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/252518

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档