KATAMINOを解くのをスタートさせる (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

スタートボタンを押したときの挙動をプログラミングしていきます。

actionの登録

スタートボタンへactionを登録します。スタートボタンがクリックされたときにaction.startSolveを呼びます。この関数はすぐ下で実装します。

js/main.js

// 前略

const initializer = {
  setEvent: () => {
    // 中略

    // 追加ここから
    $("#start-button").on("click", () => {
      action.startSolve()
    })
    // 追加ここまで
  }, 
}

actionの実装

action.startSolveを実装します。KATAMINOを解くのをスタートさせ、状態をsolving(解いている)に変更します。

js/action.js

const action = {
  // 中略

  // 追加ここから
  startSolve: () => {
    solver.init(state.targetPieces)

    solver.solve()

    stateManager.setSolverState("solving")
  },
  // 追加ここまで
}

画面の状態の変更

stateManager.setSolverStateを定義します。

js/stateManager.js

const stateManager = {
  // 中略

  // 追加ここから
  setSolverState: (solverState) => {
    state.solverState = solverState
    
    // TODO displayの呼び出し
    console.log("state.solverState", state.solverState)
  },
  // 追加ここまで
}

表示するピースのデータ形式の変更

STEP1では、kataminoFieldを描画用関数に渡し、kataminoFieldのマスをテーブルの1セルに対応させて描画していました。STEP2ではそれをやめ、スピンの画像を表示する方法に変更します。そのためには、ピースのIDとスピンのID、位置を渡す必要があります。位置としてoffsetを渡します。offsetはピースの左上が、フィールドの左上からどのくらい離れているかを表します。

f:id:yucatio:20191014140006p:plain

フィールドに置かれているピースのIDとスピンのID、オフセットを格納した配列(placedPieces)をスタックに追加します。 ピース新たに置かれたら、配列の最後にそのピースのIDとスピンのID、オフセットを追加します。

js/solver.js

const solver = {
  // 中略
  solverStack : [], 

  init : (targetPiece) => {
    const kataminoField = new Array(5).fill().map(() => (
      new Array(targetPiece.length).fill(-1)
    }

    solver.solverStack  = []
    const minEmpty = {x:0, y:0}
    // 追加
    const placedPieces = []

    targetPiece.forEach((pieceId) => {
      KATAMINO_ARR[pieceId].forEach((spin, spinId) => {
        // placedPiecesを追加
        solver.solverStack.push({kataminoField, minEmpty, pieceId, spinId, spin, unPlacedPiece: targetPiece, placedPieces,})
      })
    })
  },

  solve : () => {
    // 中略

    // placedPiecesを追加
    const {kataminoField, minEmpty, pieceId, spinId, spin, unPlacedPiece, placedPieces,} = solver.solverStack.pop()

    // 中略

    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)

    // 追加ここから
    const nextPlacedPieces = [...placedPieces, {pieceId, spinId, offset}]
    console.log("nextPlacedPieces", nextPlacedPieces)
    // 追加ここまで

    // 表示用オブジェクトとしてnextPlacedPiecesを渡すように次で変更する
    display.show(nextField)

    // 中略

    nextUnPlaced.forEach((nextPieceId) => {
      KATAMINO_ARR[nextPieceId].forEach((nextSpin, nextSpinId) => {
        // placedPieces: nextPlacedPiecesを追加
        solver.solverStack.push({kataminoField: nextField, minEmpty: nextEmpty, pieceId:nextPieceId, spinId:nextSpinId, spin: nextSpin, unPlacedPiece: nextUnPlaced, placedPieces: nextPlacedPieces,})
      })
    })
    setTimeout(solver.solve, 300)
  },
  // 後略
}

表示用コールバックの作成と呼び出し

STEP1で作成したsolverは、solve関数の中でdisplay.showを呼び出していましたが、プログラムの構成の変更にともない呼び出し方を変更します。 stateManagerの呼び出しはactionから行う必要があるので、solverにコールバックを渡します。フィールドに置かれたピースが変更された場合にはsolverからactionのメソッドを呼び出すようにします。(stateManager.setPlacedPiecesはまだ定義していません)

js/action.js

const action = {
  // 中略
  startSolve: () => {
    solver.init(state.targetPieces)

    // 変更ここから
    solver.solve({
      onUpdatePieces: (placedPieces) => stateManager.setPlacedPieces(placedPieces),
    })
    // 変更ここまで

    stateManager.setSolverState("solving")
  },
}

solve関数を変更してフィールドが変更された時にコールバックを呼び出します。

js/solver.js

const solver = {
  // 前略
  solve : (options) => {  // 変更
    // 追加
    const {onUpdatePieces = (placedPieces)=>{}} = options

    // 中略

    const nextPlacedPieces = [...placedPieces, {pieceId, spinId, offset}]
    console.log("nextPlacedPieces", nextPlacedPieces)

    // display.show(nextField) を削除
    // 追加
    onUpdatePieces(nextPlacedPieces)

    // 中略
  },
  // 後略
}

solve関数の引数が追加されたので、setTimeoutで呼び出しているsolver.solveにも引数を追加します。そのままでは引数は与えられないので、無名関数でラップします。

js/solver.js

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

    // 中略

    if (! solver.hasAllFiveTimesCells(nextField, nextEmpty)){
      console.log("フィールドが5の倍数以外で分断されている")
      // 変更
      setTimeout(() => solver.solve(options), 300)
      return
    }

    // 中略

    // 変更
    setTimeout(() => solver.solve(options), 300)
  },
  // 中略
}

stateへのフィールドに置かれているピースの追加

フィールドに置かれているピース(placedPieces)は描画に必要となるため、stateに登録します。

js/state.js

const state = {
  // One of "selectPiece", "solving", "solvedSuccess", "solvedFailed", "pause"
  solverState: "selectPiece",
  targetPieces: [1, 2, 9, 5, 10],
  // 追加
  placedPieces: [],
}

フィールドに置かれているピースの更新

placedPiecesを更新するsetPlacedPieces関数をstateManagerに作成します。中身は仮実装です。

js/stateManager.js

const stateManager = {
  // 中略

  // 追加ここから
  setPlacedPieces: (placedPieces) => {
    state.placedPieces = placedPieces

    // TODO displayの呼び出し
    console.log("state.placedPieces", state.placedPieces)
  },
  // 追加ここまで
}

実行結果

ここまでの実行結果です。 ピースが置かれたときに、stateManager.setPlacedPiecesが呼ばれ、フィールドに置かれているピースが表示されることが確認できました。

f:id:yucatio:20191014142958p:plain

ページが長くなってしまったので、一旦ここで区切ります。ピースの表示は次回行います。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com