Programmes de Sudoku

Programme en JavaScript pour la manipulation de Sudoku

const startTime = new Date();
const GRID_SIZE=9;

function checkGrid(grid, position, number) {

    // Check hline
    let hlineOk = true;
    for (let i = 0; i < GRID_SIZE; i++) {
        hlineOk = hlineOk && (grid[position.y][i] !== number);
    }

    // Check vline
    let vlineOk = true; 
    for (let i = 0; i < GRID_SIZE; i++) {
        vlineOk = vlineOk && (grid[i][position.x] !== number);
    }

    // Check Square
    let squareOk = true;
    const squareXStart = Math.trunc(position.x / 3) * 3;
    const squareYStart = Math.trunc(position.y / 3) * 3;

    for (let x = 0; x < 3; x++) {
        for (let y = 0; y < 3; y++) {
            squareOk = squareOk && (grid[squareYStart+y][squareXStart+x] !== number);
        }
    }

    return squareOk && vlineOk && hlineOk;
}

function holesCompare(hole1, hole2) {
    return (hole1.possibilities.length - hole2.possibilities.length);
}

function solver(grid) {

    const workGrid = JSON.parse(JSON.stringify(grid));
    let gridModified = false;
    const holes = [];

    // Step 1 : search each hole
    for (let x = 0; x < GRID_SIZE; x++) {
        for (let y = 0; y < GRID_SIZE; y++) {
            if (grid[y][x] === null) {
                holes.push({x, y, possibilities: []});
            }
        }
    }

    // Step 1bis : Nothing to do ? Exit (for recursion)
    if (holes.length == 0) {
        return grid;
    }

    // NB : From that point, work only with workGrid

    // Step 2 : for each hole, enumerate possibilities
    for (let i = 0; i < holes.length; i++) {
        for (let number = 1; number <= 9; number++) {
            if (checkGrid(workGrid, holes[i], number)) {
                holes[i].possibilities.push(number);
            }
        }
        // If one hole has no possitibility > The grid is a failure
        if (holes[i].possibilities.length === 0) {
            return null;
        }
    }

    // Step 3 : Fill hole with only 1 possibility
    for (let i = 0; i < holes.length; i++) {
        if (holes[i].possibilities.length === 1) {
            if (checkGrid(workGrid, holes[i], holes[i].possibilities[0])) {
                workGrid[holes[i].y][holes[i].x] = holes[i].possibilities[0];
                gridModified = true;
            }
            else {
                // 2 obvious possibility exlude themselves
                return null;
            }
        }
    }

    // Step 4 : Repeat until no more obvious hole
    if (gridModified) {
        return solver(workGrid);
    }

    // Order holes on possibilities length
    holes.sort(holesCompare);

    // Try one possibility and recurse
    for (let i = 0; i < holes.length; i++) {
        const tryGrid = JSON.parse(JSON.stringify(workGrid));

        for (let possibility = 0; possibility < holes[i].possibilities.length; possibility++) {
            if (checkGrid(tryGrid, holes[i], holes[i].possibilities[possibility])) {
                tryGrid[holes[i].y][holes[i].x] = holes[i].possibilities[possibility];
                
                let finalGrid = solver(tryGrid);
                if (finalGrid != null) {
                    // It's over
                    return finalGrid;
                }
                else {
                    tryGrid[holes[i].y][holes[i].x] = null;
                }
                // Try next possibility
            }
        }

        // no possibility fits for this hole !!
        return null;
    }

    // Should never be there
    throw `Should never end function here for grid ${JSON.stringify(workGrid)}`;
}

function giveMeOneHint(grid) {
    const finalGrid = solver(grid);
    const holes = [];

    if (finalGrid === null) {
        return null;
    }

    // Step 1 : search each hole
    for (let x = 0; x < GRID_SIZE; x++) {
        for (let y = 0; y < GRID_SIZE; y++) {
            if (grid[y][x] === null) {
                holes.push({x, y});
            }
        }
    }
    
    // Select a hole that will be given as an hint
    const hintHole = Math.trunc(Math.random() * holes.length);

    return {x: holes[hintHole].x, y: holes[hintHole].y, number: finalGrid[holes[hintHole].y][holes[hintHole].x]};
}

// Permet de créer plus facilement une grille de Sudoku à partir d'une chaine contenant tous les chiffres à la suite (et un autre caractère que 1-9 pour les trous)
function stringToGrid(chaine) {
    const grid = [];
    let pos = 0;

    if (chaine.length != GRID_SIZE*GRID_SIZE) {
        return null;
    }

    for (let x = 0; x < GRID_SIZE; x++) {
        let line = [];
        for (let y = 0; y < GRID_SIZE; y++) {
            const num = parseInt(chaine.charAt(pos++));
            if (num > 0) line.push(num);
            else line.push(null);
        }
        grid.push(line);
    }

    return grid;
}

function generateGrid(numToFill) {
    const grid = [];
    let pos = 0;

    // Empty Grid
    for (let x = 0; x < GRID_SIZE; x++) {
        let line = [];
        for (let y = 0; y < GRID_SIZE; y++) {
            line.push(null);
        }
        grid.push(line);
    }

    let numbers = 0;
    while (numbers < numToFill) {

        if (numbers < 25) {
            const x = Math.floor(Math.random() * GRID_SIZE);
            const y = Math.floor(Math.random() * GRID_SIZE);
            const number = 1 + Math.floor(Math.random() * GRID_SIZE);

            if ((grid[y][x] === null) && (checkGrid(grid, {x, y}, number))) {
                grid[y][x] = number;
                numbers++
            }
        }
        else {
            const hint = giveMeOneHint(grid);
            if (hint !== null) {
                grid[hint.y][hint.x] = hint.number;
                numbers++;
            }
        }
    }

    // Vérification
    if (solver(grid)) {
        return grid;
    }
    else {
        throw 'Grid is broken';
    }
}

exports.checkGrid = checkGrid;
exports.solver = solver;    
exports.giveMeOneHint = giveMeOneHint;    
exports.stringToGrid = stringToGrid;    
exports.generateGrid = generateGrid;
exports.GRID_SIZE = GRID_SIZE;