KATAMINOを解いているときの表示 (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

フィールド上のピースの表示を行います。また、KATAMINOを解いているときのピースの選択エリアやボタンの部分も変更します。

フィールドに置かれているピースの表示

前回、表示に必要な情報をstate.placedPiecesに格納したので、今回は表示していきます。 placedPiecesの各オブジェクトには、pieceIdspinIdoffset(左上のマスからの距離)が格納されています。

f:id:yucatio:20191014165139p:plain

これをcssで表現するには、topleftプロパティを使います。 x方向にずらすのはtopプロパティを使用します。 y方向にずらすにはleftプロパティを使用します。 offsetにはずらすマス目の数が入っているので、実際にtopleftに指定する値は、 それぞれ、x * (1マスのピクセル数(今回は25px))y * (1マスのピクセル数(今回は25px))になります。 また、今回はフィールドの画像の左上からマス目の左上まで距離があるので、そのぶんのピクセル数も加算する必要があります。フィールドの周りの余白は上左26pxです。

f:id:yucatio:20191014165206p:plain

1マスの幅やフィールドの余白の数値はconfigにまとめておきましょう。

js/config.js

// 追加ここから
const config = {
  cellSize: 25,
  fieldOffset: {top: 26, left: 26},
}
// 追加ここまで

フィールドにピースを表示する関数display.updateFieldPieceを実装します。 placedPiecesそれぞれについてpieceIdspinIdから対応する画像を取得したあと、top

updateFieldPieceはフィールドが更新されるたびに呼ばれるので、最初に全てのピースを非表示しておく必要があります。

js/display.js

const display = {
  // 前略

  updateFieldPieces : ({placedPieces}) => {
    $(".katamino-piece").hide()

    placedPieces.forEach((piece) => {
      $("#piece_" + piece.pieceId + "_" + piece.spinId
      ).css("top", piece.offset.x * config.cellSize + config.fieldOffset.top
      ).css("left", piece.offset.y * config.cellSize + config.fieldOffset.left
      ).show()
    })
  },
}

stateMangerupdateFieldPieceを登録します。

js/stateManger.js

const stateManager = {
  // 中略
  setPlacedPieces: (placedPieces) => {
    state.placedPieces = placedPieces

    // console.log("state.placedPieces", state.placedPieces) は削除してもよい
    // 追加
    display.updateFieldPieces(state)
  },
}

ここまでの実行結果

実行結果です。解いている過程が表示されました。画像になったので見やすくなりました。

f:id:yucatio:20191014175137p:plain

ピースの選択部分とボタンの挙動

ピースの選択部分と、ボタンの表示の挙動は下記のリンク先に書いてある通り、以下のような動作をします。

  • ピースの選択部分 : ピースを動かせないようにする
  • ボタン : スタートボタンを押せない状態で表示する

yucatio.hatenablog.com

ピースの選択部分の更新

ピースの選択部分のピースは、ピースの選択中(selectPiece)の時のみドラッグ可能です。ドラッグ要素のdraggable関数に""enanble"または"disable"を指定することでドラッグ可能かどうかを切り替えます。

js/display.js

const display = {
  // 中略

  updateDraggablePieces: ({solverState}) => {
    $(".draggable-piece").draggable(solverState === "selectPiece" ? "enable" : "disable")
  },
}

stateManager.setSolverStateに作成した関数を登録します。

js/stateManager

const stateManager = {
  setSolverState: (solverState) => {
    state.solverState = solverState

    // console.log("state.solverState", state.solverState) は削除してもよい
    // 追加
    display.updateDraggablePieces(state)
  },

  // 中略
}

ボタンの更新

解いている最中のボタンは、スタートボタンを押せない状態(disabled)にします。スタートボタン要素にprop("disabled", ...) を追加します。

js/display.js

const display = {
  // 仮引数を変更
  updateStartButtons: ({solverState, targetPieces}) => {
    // prop("disabled", ...) を追加
    $("#start-button").prop(
      "disabled", solverState === "solving"
    ).toggle(
      targetPieces.length >= 3
    )
    $("#more-piece-button").toggle(
      targetPieces.length < 3
    )
  },
  // 後略
}

solverStateが変更された際にボタンの再描画を行うようにします。

js/stateManager

const stateManager = {
  // 前略

  setSolverState: (solverState) => {
    state.solverState = solverState

    display.updateDraggablePieces(state)
    // 追加
    display.updateStartButtons(state)
  },

  // 後略
}

実行結果

実行結果です。解いている最中はピースの移動が出来なくなりました(画像ではわかりませんが)。また、スタートボタンが押せなくなっています。

f:id:yucatio:20191014175156p:plain

以上でKATAMINOを解いているときの表示ができました。


★次回の記事

★目次

yucatio.hatenablog.com

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

使うピースの選択を実装する (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

プログラミングをしていきます。まずは使うピースの選択です。

状態の追加

選択中のピースをstateに追加します。 初期値はpieceIdが1, 2, 9, 5, 10ですので、これを追加します。

js/state.js

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

state.targetPiecesの初期値は、初期化処理でhtml要素から取得した方がよいのですが、今回は簡易的にstateに直接書き込みました。

actionの登録

ピースがドロップされたときの動作を実装します。各エリアにドロップされたときに使うピースを更新します。

ドロップされたときの動作はdroppable関数のdropプロパティに記載します。使わないピースのエリア#unused-piece-droppableにドロップされたときは、ドロップされたピースを使うピースから削除するaction.removeFromTargetPieces(まだ定義していません)を呼び出します。同様に使うピースのエリア(#used-piece-droppable)にドロップされたときは、ドロップされたピースを使うピースに追加するaction.addToTargetPieces(まだ定義していません)を呼び出します。

ピースのIDは、 <span id="piece_0" class="draggable-piece m-2 p-0" data-piece-id="0">のように書いたhtmlタグの、data-piece-id="0"の部分から取得します。取得した際は文字列型なので、数値型に変換します。

js/main.js

// 前略

const initializer = {
  setEvent: () => {
    $(".draggable-piece").draggable({
      revert: "invalid"
    })

    $("#unused-piece-droppable").droppable({
      hoverClass: "bg-light",
      accept: ".draggable-piece",
      // 追加ここから
      drop : ((e, ui) => {
        const pieceId = ui.draggable.data("piece-id")
        action.removeFromTargetPieces(parseInt(pieceId, 10))
      }),
      // 追加ここまで
    })

    $("#used-piece-droppable").droppable({
      hoverClass: "hover",
      accept: ".draggable-piece",
      // 追加ここから
      drop : ((e, ui) => {
        const pieceId = ui.draggable.data("piece-id")
        action.addToTargetPieces(parseInt(pieceId, 10))
      }),
      // 追加ここまで
    })
  },
}

actionの実装

ドロップされたピースを使うピースから削除するaction.removeFromTargetPiecesとドロップされたピースを使うピースに追加するaction.addToTargetPiecesを実装します。

先にaction.removeFromTargetPiecesを実装します。受け取ったpieceIdが、使うピース(state.targetPieces)に含まれているか判定します。ピースが含まれていれば、そのピースの番号を使うピースのリストから削除します。新しいリストをstateManagerに渡します。 受け取ったpieceIdが、使うピースに含まれているかはindexOfの値が0以上かどうかで判定します。選択したピースのリストからの削除にはfilterを使用しています。 stateManager.setTargetPiecesはまだ実装していません。

ja/action.js

// 追加ここから
const action = {
  removeFromTargetPieces: (pieceId) => {
    if (state.targetPieces.indexOf(pieceId) >= 0) {
      const newPieces = state.targetPieces.filter(id => id !== pieceId)
      stateManager.setTargetPieces(newPieces)
    }
  },
}
// 追加ここまで

次に、action.addToTargetPiecesを実装します。受け取ったpieceIdが、使うピース(state.targetPieces)に含まれているか判定し、含まれいなければ、pieceIdを追加した配列をstateManager.setTargetPiecesに渡します。(この関数はまだ定義していません)

js/action.js

const action = {
  // 中略
  // 追加ここから
  addToTargetPieces: (pieceId) => {
    if (state.targetPieces.indexOf(pieceId) < 0) {
      stateManager.setTargetPieces([...state.targetPieces, pieceId])
    }
  },
  // 追加ここまで
}

stateManagerの実装

stateManager.setTargetPiecesを実装します。stateを書き換えます。(stateManagerのみがstateを書き換えることができます。) 書き換えたあとは画面を更新するのですが、一旦ここでコンソールに表示してここまでの動作を確認しましょう。

js/stateManager.js

// 追加ここから
const stateManager = {
  setTargetPieces: (targetPieces) => {
    state.targetPieces = targetPieces

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

ここまでの実行結果

実行結果です。"使うピース"の中にあるピースがstate.targetPiecesと同期しているのが分かります。

使わないピースを使うピースのエリアにドロップする↓ 6番のピースをドロップすると、targetPiecesの末尾に番号が追加されました。

f:id:yucatio:20191013131857p:plain

使うピースを使わないピースにのエリアにドロップする↓ 5番ののピースをドロップすると、targetPiecesから番号が削除されました。

f:id:yucatio:20191013131918p:plain

ボタンの表示の切り替え

ボタンの表示を切り替えます。選択されたピースの個数が3未満の場合には"つかうピースをもっとおいてください"のボタンを表示します。3以上の場合はスタートボタンを表示します。ボタンの表示の切り替えはtoggle関数で行います。

js/display.js

const display = {
  // show プロパティは削除

  // 追加ここから
  updateStartButtons: ({targetPieces}) => {
    $("#start-button").toggle(
      targetPieces.length >= 3
    )
    $("#more-piece-button").toggle(
      targetPieces.length < 3
    )
  },
  // 追加ここまで
}

使うピースが更新されたときに、ボタンの更新を呼び出すようにstateManagerを更新します。

js/stateManager.js

const stateManager = {
  setTargetPieces: (targetPieces) => {
    state.targetPieces = targetPieces

    // console.log("state.targetPieces", state.targetPieces) は削除してもよい
    // 追加
    display.updateStartButtons(state)
  },
}

実行結果

実行結果です。

使うピースが3個以上の場合↓

f:id:yucatio:20191013132123p:plain

使うピースが3個未満の場合↓

f:id:yucatio:20191013132140p:plain

以上で使うピースの選択を実装ができました。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

画面の状態を考える (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

画面の状態遷移について考えます。画面の状態とは、"ピースの選択中"や"解いている"という状態です。

"ピースの選択中"と"解いている"、"解けた"、"解けなかった"の状態遷移は以下のようになります。

f:id:yucatio:20191012221340p:plain

今回は一時停止ができるので、その状態を追加します。

f:id:yucatio:20191012221354p:plain

各エリアの状態は以下の通りになります。

  • ピースの選択エリア
画面の状態 パーツの状態
ピースの選択中 ピースを動かせるようにする
解いている ピースを動かせないようにする。フィールドに置いてあるピースは非表示にする
解けた ピースを動かせないようにする。フィールドに置いてあるピースは非表示にする
解けなかった ピースを動かせないようにする
一時停止中 ピースを動かせないようにする
  • ボタン
画面の状態 パーツの状態
ピースの選択中 ピースが3つ以上選択されている場合はスタートボタン、そうでない場合はもっとピースをおくボタンを表示する
解いている スタートボタンを押せない状態で表示する
解けた ピースを選び直すボタンを表示する
解けなかった ピースを選び直すボタンを表示する
一時停止中 ピースを選び直すボタンを表示する
  • フィールド
画面の状態 パーツの状態
ピースの選択中 空のフィールドを表示する
解いている 解いている過程を表示する
解けた 解けた組み合わせを表示する
解けなかった 空のフィールドを表示する
一時停止中 解いている過程を表示する

画面の状態ごとにどのようなパーツを表示すれば分かりやすくなりました。

初期状態はピースの選択のなので、これをstate.jsに追加します。

js/state.js

const state = {
  // One of "selectPiece", "solving", "solvedSuccess", "solvedFailed", "pause"
  solverState: "selectPiece",
}

以上で画面の状態を考えることができました。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

プログラムの構造を考える (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

プログラムの内部設計をします。今回はjQueryを使用しますが、(jQueryのサンプルでよく見るような)コールバックを多用するとコードの見通しが悪くなるので、今回はReact+Reduxのプログラム構成を参考にして設計してみます。

React+Reduxでは、プログラムの状態はstateオブジェクトに保存します。描画を行うオブジェクト(display)は、stateの状態のみを参照して画面出力を行います。stateの更新はreducerのみが行います。今回のプログラムではreducerという名前ではなく、stateManagerという名前にしています。

ユーザの入力によってactionが呼ばれます。actionstateManagerを呼び出します。

大まかな構造はこのようになります。

f:id:yucatio:20191012113811p:plain

stateManagerを呼び出すことができるのはactionのみです。display呼び出すことができるのはstateManagerのみです。 STEP1で作成したsolverオブジェクトは、displayを直接呼び出していましたが、actionを通じて呼び出すように変更します。

上記の他に、設定を保持するconfigオブジェクトを追加します。

プログラムファイルの一覧は以下です。 katamino-arr.js, util.jsの変更はありません。solver.jsは表示のための処理を追加します。

ファイル名 役割 備考
main.js 初期設定を行う STEP1の内容を全て置き換える
state.js 状態を保持する 新規ファイル
action.js ユーザの入力とフィールドの状態変化を受け取る 新規ファイル
stateManager.js 状態の更新と画面の更新を指示する 新規ファイル
display.js 画面のパーツを描画する STEP1の内容を全て置き換える
katamino-arr.js KATAMINOのスピン情報を格納する
solver.js KATAMINOを解くプログラム
config.js 設定値を保持する 新規ファイル
util.js ユーティリティ関数

プログラムの関係はこのようになります。

新規ファイルは空ファイルを作成しておきましょう。 state.jsaction.jsstateManager.jsconfig.jsjsフォルダの下に作成します。

f:id:yucatio:20191012114250p:plain

index.htmlでこれらのファイルを読みこんでおきます。読み込み順に気をつけてください。

index.html

<!-- 前略 -->

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
    <!-- 追加ここから -->
    <script src="js/config.js"></script>
    <script src="js/util.js"></script>
    <script src="js/state.js"></script>
    <script src="js/display.js"></script>
    <script src="js/stateManager.js"></script>
    <script src="js/katamino-arr.js"></script>
    <script src="js/solver.js"></script>
    <script src="js/action.js"></script>
    <!-- 追加ここまで -->
    <script src="js/main.js"></script>
  </body>
</html>

以上でプログラムの大まかな設計とプログラムを書く準備がととのいました。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

一時停止・再開ボタン、速度調節用スライダーの用意 (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

一時停止・再開ボタンの追加

フイールドのcardコンポーネントのフッターに一時停止・再開のボタンを追加します。両方ともページが読み込まれたときには押せないボタンなのでdisabled属性を付与しています。

index.html

<!-- 前略 -->
      <div class="card my-2">
        <div class="card-body">
          <div id="katamino-field" class="mx-auto p-0">
            <img src="img/field/field.jpg" width="352" height="177"/>

            <!-- 中略 -->
          </div><!-- end of katamino-field -->
        </div><!-- end of card-body for katamino-field -->
        <!-- 追加ここから -->
        <div class="card-footer">
          <button id="pause-button" type="button" class="btn btn-outline-secondary" disabled>いちじていし</button>
          <button id="resume-button" type="button" class="btn btn-outline-secondary" disabled>さいかい</button>
        </div><!-- end of card-footer for katamino-field -->
        <!-- 追加ここまで -->
      </div><!-- end of card for katamino-field -->
    </div><!-- end of container -->
<!-- 後略 -->

ここまでの実行結果

実行結果です。フッターにボタンが表示されました。

f:id:yucatio:20191011085427p:plain

速度調節用パーツの追加

速度調整用のスライダーを追加します。スライダーはinput要素にtype="range"を指定することで作成できます。

スライダーはcardのフッターの右側に配置します。ボタンを左側に置いたまま、スライダーを右側に置きたいので、flexの例を参考にd-flexflex-grow-1クラスを使用します。

card-footerd-flexクラスを追加します。一時停止・再開ボタンをdivで囲み、flex-grow-1クラスを付与して、スライダーを右へ移動させました。スライダーの親のdivにはalign-self-centerクラスを追加して、上下方向中央に配置されるようにしています。

index.html

<!-- 前略 -->
        </div><!-- end of card-body for katamino-field -->
        <div class="card-footer d-flex">  <!-- d-flexを追加 -->
          <div class="flex-grow-1"> <!-- divを追加 -->
            <button id="pause-button" type="button" class="btn btn-outline-secondary" disabled>いちじていし</button>
            <button id="resume-button" type="button" class="btn btn-outline-secondary" disabled>さいかい</button>
          </div> <!-- /divを追加 -->
          <!-- 追加ここから -->
          <div class="align-self-center">
            <label class="text-secondary">はやさ: はやい <input id="speed-range" type="range" name="speed" min="0" max="4" value="3"> おそい</label>
          </div>
          <!-- 追加ここまで -->
        </div><!-- end of card-footer for katamino-field -->
      </div><!-- end of card for katamino-field -->
    </div><!-- end of container -->
<!-- 後略 -->

実行結果

実行結果です。ボタンは左側、スライダーは右側に配置されました。

f:id:yucatio:20191011085440p:plain

以上で一時停止・再開ボタンと速度調節用パーツの用意ができました。 ここまでで画面のパーツが一通りそろいました。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com

KATAMINOフィールドとピースの用意 (KATAMINOを解くプログラムを作成する)

★前回の記事

yucatio.hatenablog.com

KTAMINOのフィールドを配置します。 今回はKATAMINOを置くマス目(フィールド)に画像を用います。

f:id:yucatio:20191009185236j:plain

画像はこちらからダウンロードできます。ダウンロードしたら、img/fieldに配置します。

KATAMINO-SOLVER/KATAMINO-SOLVER-main/img/field at master · yucatio/KATAMINO-SOLVER · GitHub

マス目の大きさは1辺50px、画像の左上から左上のマスまでは52pxです。今回は50%に縮小して表示するので、1辺は25pxで画像の左上から左上のマスまでは26pxです。

f:id:yucatio:20191010082945p:plain

フィールドの配置

前回作成したボタンの下に、cardコンポーネントを作成し、中央にフィールドを表示します。

index.html

<!-- 前略 -->
      <button type="button" id="reset-button" class="btn btn-danger btn-lg btn-block my-3">ピースをえらびなおす</button>

      <!-- 追加ここから -->
      <div class="card my-2">
        <div class="card-body">
          <div id="katamino-field" class="mx-auto p-0">
            <img src="img/field/field.jpg" width="352" height="177"/>
          </div><!-- end of katamino-field -->
        </div><!-- end of card-body for katamino-field -->
      </div><!-- end of card for katamino-field -->
      <!-- 追加ここまで -->

    </div><!-- end of container -->
<!-- 後略 -->

main.cssに、katamino-fieldの設定を追加します。widthはフィールドの画像の幅と同じです。

css/main.css

#katamino-field {
  width: 352px;
}

ここまでの実行結果

実行結果です。フィールドの画像がcardの中央に表示されました。

f:id:yucatio:20191010090055p:plain

スピンの配置

フィールドにピースを表示するために、全てのピースのスピンを読み込んでおきます。classkatamino-pieceを指定します。idは、piece_{pieceId}_{spinId}にします。

<!-- 前略 -->
      <div class="card my-2">
        <div class="card-body">
          <div id="katamino-field" class="mx-auto p-0">
            <img src="img/field/field.jpg" width="352" height="177"/>

            <!-- 追加ここから -->
            <img id="piece_0_0" class="katamino-piece" src="img/piece/piece_0_0.png" width="125px" />
            <img id="piece_0_1" class="katamino-piece" src="img/piece/piece_0_1.png" width="25px" />

            <img id="piece_1_0" class="katamino-piece" src="img/piece/piece_1_0.png" width="100px" />
            <img id="piece_1_1" class="katamino-piece" src="img/piece/piece_1_1.png" width="50px" />
            <img id="piece_1_2" class="katamino-piece" src="img/piece/piece_1_2.png" width="100px" />
            <img id="piece_1_3" class="katamino-piece" src="img/piece/piece_1_3.png" width="50px" />
            <img id="piece_1_4" class="katamino-piece" src="img/piece/piece_1_4.png" width="100px" />
            <img id="piece_1_5" class="katamino-piece" src="img/piece/piece_1_5.png" width="50px" />
            <img id="piece_1_6" class="katamino-piece" src="img/piece/piece_1_6.png" width="100px" />
            <img id="piece_1_7" class="katamino-piece" src="img/piece/piece_1_7.png" width="50px" />

            <img id="piece_2_0" class="katamino-piece" src="img/piece/piece_2_0.png" width="100px" />
            <img id="piece_2_1" class="katamino-piece" src="img/piece/piece_2_1.png" width="50px" />
            <img id="piece_2_2" class="katamino-piece" src="img/piece/piece_2_2.png" width="100px" />
            <img id="piece_2_3" class="katamino-piece" src="img/piece/piece_2_3.png" width="50px" />
            <img id="piece_2_4" class="katamino-piece" src="img/piece/piece_2_4.png" width="100px" />
            <img id="piece_2_5" class="katamino-piece" src="img/piece/piece_2_5.png" width="50px" />
            <img id="piece_2_6" class="katamino-piece" src="img/piece/piece_2_6.png" width="100px" />
            <img id="piece_2_7" class="katamino-piece" src="img/piece/piece_2_7.png" width="50px" />

            <img id="piece_3_0" class="katamino-piece" src="img/piece/piece_3_0.png" width="100px" />
            <img id="piece_3_1" class="katamino-piece" src="img/piece/piece_3_1.png" width="50px" />
            <img id="piece_3_2" class="katamino-piece" src="img/piece/piece_3_2.png" width="100px" />
            <img id="piece_3_3" class="katamino-piece" src="img/piece/piece_3_3.png" width="50px" />
            <img id="piece_3_4" class="katamino-piece" src="img/piece/piece_3_4.png" width="100px" />
            <img id="piece_3_5" class="katamino-piece" src="img/piece/piece_3_5.png" width="50px" />
            <img id="piece_3_6" class="katamino-piece" src="img/piece/piece_3_6.png" width="100px" />
            <img id="piece_3_7" class="katamino-piece" src="img/piece/piece_3_7.png" width="50px" />

            <img id="piece_4_0" class="katamino-piece" src="img/piece/piece_4_0.png" width="75px" />
            <img id="piece_4_1" class="katamino-piece" src="img/piece/piece_4_1.png" width="75px" />
            <img id="piece_4_2" class="katamino-piece" src="img/piece/piece_4_2.png" width="75px" />
            <img id="piece_4_3" class="katamino-piece" src="img/piece/piece_4_3.png" width="75px" />

            <img id="piece_5_0" class="katamino-piece" src="img/piece/piece_5_0.png" width="75px" />
            <img id="piece_5_1" class="katamino-piece" src="img/piece/piece_5_1.png" width="50px" />
            <img id="piece_5_2" class="katamino-piece" src="img/piece/piece_5_2.png" width="75px" />
            <img id="piece_5_3" class="katamino-piece" src="img/piece/piece_5_3.png" width="50px" />
            <img id="piece_5_4" class="katamino-piece" src="img/piece/piece_5_4.png" width="75px" />
            <img id="piece_5_5" class="katamino-piece" src="img/piece/piece_5_5.png" width="50px" />
            <img id="piece_5_6" class="katamino-piece" src="img/piece/piece_5_6.png" width="75px" />
            <img id="piece_5_7" class="katamino-piece" src="img/piece/piece_5_7.png" width="50px" />

            <img id="piece_6_0" class="katamino-piece" src="img/piece/piece_6_0.png" width="75px" />
            <img id="piece_6_1" class="katamino-piece" src="img/piece/piece_6_1.png" width="50px" />
            <img id="piece_6_2" class="katamino-piece" src="img/piece/piece_6_2.png" width="75px" />
            <img id="piece_6_3" class="katamino-piece" src="img/piece/piece_6_3.png" width="50px" />

            <img id="piece_7_0" class="katamino-piece" src="img/piece/piece_7_0.png" width="75px" />
            <img id="piece_7_1" class="katamino-piece" src="img/piece/piece_7_1.png" width="75px" />
            <img id="piece_7_2" class="katamino-piece" src="img/piece/piece_7_2.png" width="75px" />
            <img id="piece_7_3" class="katamino-piece" src="img/piece/piece_7_3.png" width="75px" />

            <img id="piece_8_0" class="katamino-piece" src="img/piece/piece_8_0.png" width="75px" />
            <img id="piece_8_1" class="katamino-piece" src="img/piece/piece_8_1.png" width="75px" />
            <img id="piece_8_2" class="katamino-piece" src="img/piece/piece_8_2.png" width="75px" />
            <img id="piece_8_3" class="katamino-piece" src="img/piece/piece_8_3.png" width="75px" />
            <img id="piece_8_4" class="katamino-piece" src="img/piece/piece_8_4.png" width="75px" />
            <img id="piece_8_5" class="katamino-piece" src="img/piece/piece_8_5.png" width="75px" />
            <img id="piece_8_6" class="katamino-piece" src="img/piece/piece_8_6.png" width="75px" />
            <img id="piece_8_7" class="katamino-piece" src="img/piece/piece_8_7.png" width="75px" />

            <img id="piece_9_0" class="katamino-piece" src="img/piece/piece_9_0.png" width="75px" />
            <img id="piece_9_1" class="katamino-piece" src="img/piece/piece_9_1.png" width="75px" />
            <img id="piece_9_2" class="katamino-piece" src="img/piece/piece_9_2.png" width="75px" />
            <img id="piece_9_3" class="katamino-piece" src="img/piece/piece_9_3.png" width="75px" />

            <img id="piece_10_0" class="katamino-piece" src="img/piece/piece_10_0.png" width="75px" />
            <img id="piece_10_1" class="katamino-piece" src="img/piece/piece_10_1.png" width="75px" />
            <img id="piece_10_2" class="katamino-piece" src="img/piece/piece_10_2.png" width="75px" />
            <img id="piece_10_3" class="katamino-piece" src="img/piece/piece_10_3.png" width="75px" />

            <img id="piece_11_0" class="katamino-piece" src="img/piece/piece_11_0.png" width="75px" />
            <!-- 追加ここまで -->
          </div><!-- end of katamino-field -->
        </div><!-- end of card-body for katamino-field -->
      </div><!-- end of card for katamino-field -->
    </div><!-- end of container -->
<!-- 後略 -->

全てのスピンが表示されていることを確認してください。

f:id:yucatio:20191010090117p:plain

確認ができたら非表示にしておきます。

css/main.css

.katamino-piece {
  display: none;
}

絶対位置へ配置するための準備

先ほど追加したピースは、この後でフィールドの左上を起点とした位置に配置します。そのため、position属性を追加します。 詳しくはCSS-positionを参照してください。

css/main.css

#katamino-field {
   /* 追加 */
  position: relative;
  width: 352px;
}

.katamino-piece {
  display: none;
  /* 追加 */
  position: absolute;
}

このような設定にすると、各ピースの位置をフィールド左上からの相対位置で指定することができます。

f:id:yucatio:20191010091120p:plain

以上でKATAMINOフィールドとピースの用意ができました。


★次回の記事

yucatio.hatenablog.com

★目次

yucatio.hatenablog.com