0

So I'm making a unique sudoku puzzle generator and while creating the uniqueness, I am using a trial and error script with Math.floor(Math.Random()*X) to get a random tile out of the 81 tiles to remove the number to the solution. I made it so that after removing X amount of tiles with numbers, it tries to solve it by checking all the possibilities. If it can't, then that means that there's multiple/no solution(s) and that it needs to generate a different puzzle whilst still using the same solution. It can manage to create a unique puzzle, but at some rare occasions it won't and will create a stack overflow since it's calling itself too many times.

I basically want a way to bypass the stack overflow.

I have looked up many solutions online, but none gave the result I want. The closest that I've gotten it to work the way I want was with a recursive async try{}catch(){}finally{} function but I couldn't get it to stop when it actually found a solution or to print out the error whenever the stack overflow occurred.

async function fCreatePuzzle(solution) {
    let refere = []
    puzzleTries++
    for (let i = 0; i <= 25 + difficulty - 1; i++) {
      refere[i] = getRandomLetterAndNumber(refere)
    }
    for (let i = 0; i <= refere.length - 1; i++) {
      containddd[refere[i]].Value = null
    }
    if (fTestSolve(solution) == false) {
      for (key in containddd) {
        fChangeSudokuValAdmin(key, containddd[key].Value)
      }
    } else {
      if (puzzleTries >= 5500) {
        LoadModalText.innerText = "Could not load puzzle with " + difficulty.toString() + " as the difficulty."
        console.log("after " + puzzleTries + " attempts, we could not make a puzzle with " + difficulty.toString() + " as the difficulty. ")
        difficulty = difficulty >= 0 && difficulty - 2 >= 0 && difficulty - 2 || 0
        console.log("Lowering difficulty to " + (difficulty).toString() + " to try to create a 1 solution puzzle")
        setTimeout(function(){LoadModalText.innerText = "Lowering difficulty to " + (difficulty).toString() + " to prevent overflow errors."},2000)
        puzzleTries = 0
        setTimeout(function(){LoadModalText.innerText = "Loading Puzzle..."},1750)
      }
      resetTest(solution)
      fCreatePuzzle(solution)
    }
    return puzzleTries
  }
  function InitialiseGame() {
    loadingModal.show();
    Spinner.classList.add("spinner-border","spinner-border-sm")
    LoadModalText.innerText = "Generating Sudoku Solution..."
    setTimeout(function(){
      Stime = new Date()
      True_Solution = setupSudokuSolution()
      LoadModalText.innerText = "Generating Sudoku Puzzle..."
      async function run(){
        try {
           return await fCreatePuzzle(True_Solution)
        } catch (e) {
          console.log(e)
        } finally {
          
          run()
        }
      }
      let what = run()
      number_Chosen = null
      LoadModalText.innerText = "Puzzle Generated Successfully!"
      Spinner.classList.remove("spinner-border","spinner-border-sm")
      setTimeout(function(){loadingModal.hide()},2000)
    },200)
    
  }
  • You should take a look at [try...catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) but a better solution will be to only generate a random value from the remaining possible values. – Titus Mar 22 '23 at 16:51
  • Having a look at someone else's work actually made me rethink the process and now I managed to make it work! thanks again for the link! W3schools and other sources didn't help out as much as that documentation tbh! @Titus – hervix 20985 Mar 22 '23 at 17:11

1 Answers1

0

Recursion is a functional heritage and so using it with functional style yields the best results. That means avoiding things like mutation, variable reassignments, and other side effects. It also means breaking a problem down into isolated parts, and composing them together to construct the solution.

There is no for or while in functional style. The natural looping mechanism is recursion. There is no i++ or i = i + ... in functional style. You do not mutate existing values, you create new ones.

Once you see functional style in practice, it makes a lot of sense. Functions take arguments and construct new values using those arguments. There's nothing more to it. Once you break these rules, you are due for a headache like the one you are experiencing now.

function createPuzzle(puzzle, cellsToRemove) {
  // no more cells to remove, return puzzle
  if (cellsToRemove == 0)
    return puzzle
  // remove cell and recur
  else
    return createPuzzle(removeCell(puzzle, 5000), cellsToRemove - 1)
}
const solution = createSolution()
const difficulty = …
const myPuzzle = createPuzzle(solution, 25 + difficulty)

As we saw, createPuzzle is a high-level function, depending on smaller functions to accomplish its goal with ease. We will write those now -

function removeCell(puzzle, attempts) {
  if (attempts == 0)
    throw Error("could not generate puzzle") // no more attempts
  const newPuzzle = /* remove cell from puzzle */
  if (isSolveable(newPuzzle))
    return newPuzzle                        // solveable
  else
    return removeCell(puzzle, attempts - 1) // retry
}

And on we go. removeCell depends on a isSolveable function to do its job. Go ahead and write that now -

function isSolveable(puzzle) {
  /* ... */
  return /* true or false */
}

There's a few pieces for you to fill in there. You will continue to use these simple disciplines, passing arguments to functions, avoiding mutation of existing data, and constructing new values. Once you are there, if you are still overflowing the stack, there are a variety of techniques available to you.

Any functional program can be made stack-safe for infinite recursion. If you want to jump ahead, see my 3-part series on writing a micro evaluator that abstracts function calls in JavaScript.

Mulan
  • 129,518
  • 31
  • 228
  • 259