在香草JS中执行脚本时更新HTML元素的内容

时间:2018-09-19 21:20:27

标签: javascript html asynchronous

我正在编写简单的Sudoku求解器作为练习,我认为我将通过在9x9网格上逐步显示它来显示整个过程。

我正在使用带有嵌套div的HTML表来显示游戏板(可能不相关),并使用递归函数来解决预置的Sudoku。我的解决方案现在处于“意大利面条”状态,因此下面我为您提供了我所拥有的伪代码:

function fillBoard() {
  for(let i = 0; i < 9; i++) {
    for(let j = 0; j < 9; j++) {
      const cell = document.querySelector(`div[data-row="${i}"][data-column="${j}"]`);
      cell.innerHTML = gameState.solution[i][j];
    }
  }
}

function solve(row, column) {
  /* simplified, it's working :) */
  for(let guess = 1; guess < 10; guess++) {
    this.solution[row][column] = guess; <--- SHOW THIS STEP TO USER
    let conflicts = checkConflicts(row, column)           
    if(!conflicts) {
      let emptyCell = this.findNextEmptyCell(row, column);
      if(emptyCell) {
        let result = this.solve(emptyCell.i, emptyCell.j);
        if(!result) continue;
        else return true;
      }
      return true;
    }            
    else continue;
  }
}

我尝试将fillBoard()函数调用放入solve()内,但这显然没有用,因为我只得到了已解决网格形式的最终结果。我也尝试过使用setInterval(fillBoard, 100),但是solve()函数的执行速度太快。

如何通过在每次solve()调用之后更新HTML来实现整个求解过程的“增量”显示?

我正在尝试获得以下内容:https://www.youtube.com/watch?v=ChjQRIhH414,但我是从左至右,从上至下填充板子

当前解决方案:Codepen

2 个答案:

答案 0 :(得分:1)

您将希望放慢html渲染过程,这会向我建议动画。您可以研究这种方法,并且可以根据需要进行调整。没有完整的示例,我无法确定是否运行它。作为响应,我将为您提供一些伪代码,以测试它的开始方式,并希望它将为您指明正确的方向:

function fillBoard(i=0, j=0) {
  const cell = document.querySelector(`div[data-row="${i}"][data-column="${j}"]`);
  cell.innerHTML = gameState.solution[i][j];

  j++;
  if( j >= 9) { j = 0; i++; }
  if(i < 9)
  requestAnimationFrame(function() { fillBoard(i,j) });
}

如果需要,可以在给定的延迟上用setTimeout替换requestAnimationFrame。您可以将其设置为1-2秒,或者以其他方式开始查看它是否能为您提供所需的结果。

答案 1 :(得分:0)

未优化但有效的示例,方法是遵循上述注释中的@loctrice建议。仍在寻找解决此问题的其他方法。

console.clear();

let states = [];
let x = 0;

document.addEventListener("DOMContentLoaded", function() {
  const start = document.getElementById("start");
  const check = document.getElementById("check");
  const sudoku = document.getElementById("sudoku");

  function fillBoard(index) {
    console.log(`Displaying ${index}/${states.length}`);
    for (let i = 0; i < 9; i++) {
      for (let j = 0; j < 9; j++) {
        const cell = document.querySelector(
          `div[data-row="${i}"][data-column="${j}"]`
        );
        cell.innerHTML =
          states[index][i][j] == 0 ? "" : states[index][i][j];        
      }
    }
  }

  class Sudoku {
    checkRow(row) {
      for (let i = 0; i < 9; i++) {
        let number = this.solution[row][i];
        if (!number) continue;

        for (let j = 0; j < 9; j++) {
          if (i == j) continue;
          const challenge = this.solution[row][j];
          if (number == challenge) return number;
        }
      }
      return false;
    }

    checkColumn(column) {
      for (let i = 0; i < 9; i++) {
        let number = this.solution[i][column];
        if (!number) continue;

        for (let j = 0; j < 9; j++) {
          if (i == j) continue;
          const challenge = this.solution[j][column];
          if (number == challenge) return number;
        }
      }
      return false;
    }

    checkBox(box) {
      const rowModifier = Math.floor(box / 3) * 3;
      const colModifier = (box % 3) * 3;
      for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
          let number = this.solution[i + rowModifier][j + colModifier];
          if (!number) continue;

          for (let x = 0; x < 3; x++) {
            for (let y = 0; y < 3; y++) {
              if (x == i && y == j) continue;
              const challenge = this.solution[x + rowModifier][y + colModifier];
              if (number == challenge) return number;
            }
          }
        }
      }
      return false;
    }

    solve(row, column, array) {
      for (let guess = 1; guess < 11; guess++) {
        if (guess == 10) {
          this.solution[row][column] = 0;
          return false;
        }
        this.solution[row][column] = guess; // <=== SHOW ENTIRE BOARD HERE
        let state = this.solution.map(a => [...a]);
        array.push(state);
        const rowError = this.checkRow(row);
        const columnError = this.checkColumn(column);
        const boxError = this.checkBox(
          3 * Math.floor(row / 3) + Math.floor(column / 3)
        );
        if (!rowError && !columnError && !boxError) {
          // find next empty cell
          let emptyCell = this.findNextEmptyCell(row, column);
          if (emptyCell) {
            let result = this.solve(emptyCell.i, emptyCell.j, array);
            if (!result) continue;
            else return true;
          }
          return true;
        } else continue;
      }
    }

    findNextEmptyCell(row, column) {
      for (let i = row; i < 9; i++) {
        if (column == 8) column = 0;
        for (let j = column; j < 9; j++) {
          if (this.solution[i][j]) continue;
          else return { i: i, j: j };
        }
        column = 0;
      }
      return false;
    }

    generateBox(boxNumber) {
      let numbers = [];
      for (let i = 0; i < 9; i++) {
        numbers.push(i);
      }

      for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
          const length = numbers.length;
          const index = Math.floor(Math.random() * length);
          const number = numbers.splice(index, 1);
          const row = i + boxNumber * 3 / 4;
          const col = j + boxNumber * 3 / 4;
          this.solution[row][col] = parseInt(number) + 1;
        }
      }
    }

    initialize() {
      this.solution = [];

      for (let i = 0; i < 9; i++) {
        this.solution.push([]);
        for (let j = 0; j < 9; j++) {
          this.solution[i][j] = 0;
        }
      }

      for (let i = 0; i < 3; i++) {
        this.generateBox(i * 4);
      }
    }
  }

  let gameState = new Sudoku();
  gameState.initialize();
  
  start.onclick = function() {
    gameState.solve(0, 3, states);
    window.setInterval(function() {if(x < states.length) fillBoard(x++);}, 15);
  };

  check.onclick = function() {
    for (let i = 0; i < 9; i++) {
      let error = gameState.checkRow(i);
      if (error) {
        for (let j = 0; j < 9; j++) {
          const cell = document.querySelector(
            `div[data-row="${i}"][data-column="${j}"]`
          );
          cell.classList.add("incorrect-area");
          if (cell.innerHTML == error) cell.classList.add("incorrect");
        }
        return;
      }

      error = gameState.checkColumn(i);
      if (error) {
        const cells = document.querySelectorAll(`div[data-column="${i}"]`);
        cells.forEach(c => {
          c.classList.add("incorrect-area");
          if (c.innerHTML == error) c.classList.add("incorrect");
        });
        return;
      }

      error = gameState.checkBox(i);
      if (error) {
        const cells = document.querySelectorAll(`div[data-box="${i}"]`);
        cells.forEach(c => {
          c.classList.add("incorrect-area");
          if (c.innerHTML == error) c.classList.add("incorrect");
        });
        return;
      }
    }
  };

  for (let i = 0; i < 9; i++) {
    const row = document.createElement("tr");
    row.classList.add("row");

    for (let j = 0; j < 9; j++) {
      let td = document.createElement("td");
      let cell = document.createElement("div");
      cell.classList.add("cell");

      cell.dataset.row = i;
      cell.dataset.column = j;

      let a = Math.floor(i / 3);
      let b = Math.floor(j / 3);
      cell.dataset.box = 3 * a + b;

      cell.innerHTML =
        gameState.solution[i][j] == "0" ? "" : gameState.solution[i][j];

      if (!cell.innerHTML)
        cell.onclick = function(e) {
          const row = e.target.dataset.row;
          const col = e.target.dataset.column;

          gameState.solution[row][col] =
            ++gameState.solution[row][col] > 9
              ? 0
              : gameState.solution[row][col];
          cell.innerHTML =
            gameState.solution[row][col] == "0"
              ? ""
              : gameState.solution[row][col];

          document
            .querySelectorAll("div.cell")
            .forEach(c =>
              c.classList.remove("correct", "incorrect", "incorrect-area")
            );
        };

      td.appendChild(cell);
      row.appendChild(td);
    }

    sudoku.appendChild(row);
  }
});
.incorrect-area {
    background-color: #A55 !important;
    border-color: #F00 !important;
}

.correct {
    background-color: #1A1 !important;
    border-color: #0F0 !important;
}

.incorrect {
    background-color: #A11 !important;
    border-color: #F00 !important;
}

div[data-box='1'], 
div[data-box='3'], 
div[data-box='5'], 
div[data-box='7'] {
    background-color: #444;
}

.button {
    display: inline-block;
    min-height: 30px;
    width: 120px;
    background-color: white;
    font-size: 32px;
    cursor: pointer;
}

#buttons {
    text-align: center;
}

div {
    padding: 0px;
}

body {
    background-color: #000;
}

#game {
    width: 500px;
    margin-left: auto;
    margin-right: auto;
    margin-top: 0px;
}

#sudoku {
    width: 500px;
    height: 500px;
    margin-left: auto;
    margin-right: auto;
    background-color: #111;
    border: dashed 1px white;
    color: white;
}

.row {
    border: 1px solid yellow;
}

.cell {
    cursor: default;
    height: 40px;
    width: 40px;
    padding: 0;
    margin: 0;
    border: solid white 1px;
    text-align: center;
    font-weight: bold;
    display: table-cell;
    vertical-align: middle;
    user-select: none;
}

.cell:hover {
    background-color: #765;
    transform: scale(1.3);
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>SudoQ</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
    <script src="main.js"></script>
</head>
<body>
    <div id="game">
        <div id="buttons">
            <div id="start" class="button">SOLVE</div>
            <div id="check" class="button">CHECK</div>
        </div>
        <table id="sudoku">
        </table>
    </div>    
</body>
</html>