JavaScriptのsetTimeoutを使用して途中経過を表示する (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

前回までで解けた場合のフィールドを表示しました。今回は途中の経過も表示します。 ピースが置けた時にフィールドを更新します。

途中経過の表示方法

途中経過を表示します。ピースが置けたときに画面を更新します。 画面を一定時間ごとに更新したいので、処理を待たせるコードを入れたいのですが、 JavaScriptにはsleepのような関数はありません。 代わりに、setTimeout( WindowOrWorkerGlobalScope.setTimeout() - Web API | MDN ) を使用します。setTImeoutの引数に、現在実行しているsolver.solve関数を指定し、繰り返し処理を行います。 そのため、whileによるループは不要になります。

実装

はじめに、フィールドの再描画を行うための準備をします。 現在display.js内で、テーブルをhtmlの最後の要素として追加していますが、divの内容を書き換えるように変更します。

index.html

<!DOCTYPE html>
<html>
  <head>
    <!-- 中略 -->
  </head>
  <body>
    <!-- 追加ここから -->
    <div id="katamino-field">
      <table id="katamino-table">
        <tr>
          <td>
          </td>
        </tr>
      </table>
    </div>
    <!-- 追加ここまで -->

    <!-- 中略 -->
  </body>
</html>

次に、katamino-fieldの中の、katamino-tableを置き換えるように変更します。

js/display.js

const display = {
  show : (kataminoField) => {
    const table = document.createElement("table")
    table.setAttribute("id","katamino-table")

    kataminoField.forEach((fieldRow) => {
        // 中略
    })
    // 変更ここから
    const old = document.getElementById("katamino-table")
    document.getElementById("katamino-field").replaceChild(table, old)
    // 変更ここまで
  },
}

準備が整ったので、js/solver.jsを、setTimeoutを使用するように書き換えます。

solver.solveのはじめに、スタックが空かどうか判定するロジックを入れています。

ピースがフィールドの外か、すでにピースが置かれているので置けない場合は、次の処理をすぐ実行するように、 setTimeout(solver.solve, 0)を実行します。continuereturnに変更します。

ピースが置けたときはdisplay.show(nextField)を実行し、表示を更新します。また、関数の一番最後でsetTimeout(solver.solve, 300)を実行して次のループを300ミリ秒後に行うように設定します。

KATAMINOが完成した場合の、breakreturnに変更します。

js/solver.js

const solver = {
  // 中略
  solve : () => {
    // 追加ここから
    if (solver.solverStack.length <= 0) {
      console.log("解けなかった")
      return
    }
    // 追加ここまで

//    while (solver.solverStack.length > 0) { 削除
    const {kataminoField, minEmpty, pieceId, spinId, spin, unPlacedPiece} = solver.solverStack.pop()

    console.log("pieceId", pieceId)
    console.log("spinId", spinId)

    const offset = {x: minEmpty.x, y: minEmpty.y - spin[0].y}

    if (! solver.isAllEmpty(kataminoField, spin, offset)) {
      // フィールドの外か、すでにピースが置かれている
      console.log("フィールドの外か、すでにピースが置かれている")
      setTimeout(solver.solve, 0)  // 追加
      return  // 変更
    }

    console.log("ピースが置ける")

    const nextField = util.copyArrayOfArray(kataminoField)
    solver.placeSpin(nextField, spin, offset, pieceId)
    console.log("nextField", nextField)

    const nextUnPlaced = unPlacedPiece.filter(id => id !== pieceId)
    console.log("nextUnPlaced", nextUnPlaced)

    display.show(nextField)  // 追加

    if (nextUnPlaced.length <= 0) {
      console.log("完成")
      // display.show(nextField) は削除
      return  // 変更
    }

    const nextEmpty = solver.findNextEmpty(nextField, minEmpty)
    console.log("nextEmpty", nextEmpty)

    nextUnPlaced.forEach((nextPieceId) => {
      KATAMINO_ARR[nextPieceId].forEach((nextSpin, nextSpinId) => {
        solver.solverStack.push({kataminoField: nextField, minEmpty: nextEmpty, pieceId:nextPieceId, spinId:nextSpinId, spin: nextSpin, unPlacedPiece: nextUnPlaced,})
      })
    })

    setTimeout(solver.solve, 300)  // 追加

//    }  削除
  },
}

動作確認

実行結果です。ピースを置いていく過程がアニメーションされました。

f:id:yucatio:20190805082330p:plain


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com