【ルービックキューブのタイマーを作る】第9回 キューブの展開図にスクランブル情報を適用させる回

プロジェクト【ルービックキューブのタイマーを作る】の最終回となります。ここまで閲覧して頂いた方、本当にありがとうございます。今回はコレ!

スクランブルに合わせて、展開図のカラーリングを変化させようという作業です。
目次
一旦かるく現在の仕様を・・・
作業前の仕様を確認しておく。
スクランブル文字列(F’ R2 B・・・)
ホーム画面のload時に、API経由で新しいスクランブル情報を生成し、APIが返却したスクランブル文字列をHTML上に表示。(timer.jsで処理)
キューブ展開図
キューブの展開図の形になるように、HTML上で表示しているのみ。カラーリングは、「cube-white」「cube-orange」などのclass名を付けることでCSSを当てている。
作業の流れ
- API側で初期状態のカラーリング情報を生成し、レスポンスに含める。
- HTMLに直接書いている「cube-white」等のclassを削除する。API経由で取得したカラー情報を用いて各マスに対してJavaScriptから「cube-white」「cube-orange」などのclassを付与する。これにより現在と同様の、回転前の配色VIEWを再現。
- API側でスクランブル文字列に対応するカラー情報を生成する処理に変更する。
APIでカラー情報を返す
とりあえず、初期状態のカラーリングをAPIを使って表現できるようにやってみます。
app/Services/CubeService.php
工程3でscrambleToColorArrayに回転処理を追記する予定ですが、現在は初期状態を返す関数として定義。
/** * スクランブルデータ(数値配列)をカラー情報の配列にする * @param $scramble * @return array */ public function scrambleToColorArray($scramble): array { // 初期状態のカラーリング情報 $colors_info = $this->initColorArray(); // ここで回転処理を記述する return $colors_info; } /** * 初期状態のカラー情報を取得 * @return array */ public function initColorArray(): array { $ret = array(); $colors = [ 0 => 'white', 1 => 'orange', 2 => 'green', 3 => 'red', 4 => 'blue', 5 => 'yellow' ]; for ($i = 0; $i < 6; $i++) { for ($j = 0; $j < 9; $j++) { $key = $i * 9 + $j; $ret[$key] = $colors[$i]; } } return $ret; }
app/Http/Controllers/CubesApiController.php
作成した情報をAPIのレスポンスに含めます。
/** * [API] スクランブル情報を取得 * @param CubeService $cs * @return array[] * @throws \Random\RandomException */ public function scramble(CubeService $cs): array { $scramble_info = $cs->scramble(); $scramble_text = implode(' ', $cs->scrambleToTextArray($scramble_info)); $scramble_colors = $cs->scrambleToColorArray($scramble_info); return [ 'scramble' => [ 'text' => $scramble_text, 'colors' => $scramble_colors, ] ]; }
APIのレスポンスを見てみる
JSON.parseした後の値をログ出力してみると・・

スクランブルの”text”に加えて、”colors”という情報が追加されています。地味な実装ですが、配列の中身は、文字列で色の名前を入れました。
ちなみに今回のスクランブルビューを作るにあたって、↓こんな感じにインデックスをふったものをイメージしています。

APIのカラー情報を適用する
resources/views/index.blade.php
上で示したインデックス通りに、各マスに「cubeview-cell-{{$key}}」というclassを付与した。
<div id="cubeview" class=""> <?php $key = 0; ?> @foreach(array_keys($colors) as $face) <div class="surface {{$face}}"> <div class="wrapper"> @for($i = 1; $i < 10; $i++) <div id="cubeview-cell-{{$key}}" class="cube cube-{{$i}}"></div> <?php $key++; ?> @endfor </div> </div> @endforeach </div>
resources/js/timer.js
工程1で作成したAPIレスポンスを用いて、json.scramble.colors[i]の値(whiteやorange等)が、cubeview-cell-{i}のクラス(cube-whiteやcube-orange)となるように実装。
scramble: function () { var xhr = new XMLHttpRequest(); xhr.open('GET', '/api/scramble'); xhr.send(); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { const json = JSON.parse(xhr.responseText); document.getElementById('scramble').textContent = json.scramble.text; // 追加 for(let i = 0; i < 54; i++){ document.getElementById('cubeview-cell-' + i).classList.add('cube-' + json.scramble.colors[i]); } } } } }
現状を確認
timer.jsを実装する前と後を比較すると、分かると思うので確認すると・・・
timer.js実装前

timer.js実装後

できました!
まずは1回転分の展開図をつくる
いけてる実装が思いついてないので、一番地味で申し訳ないですが、一旦手書きで回転を加えてみる。
まずは、R一回分を試してみる。回転記号についてはこちら(TORIBO)
app/Services/CubeService.php
まず回転データを一部修正する。以前のデータは、$timesの変数が、0,1,2の値をとっていたが、回転回数をあわせるために、1,2,3の値をとるように変更した。1が順方向1回、2が順方向2回、3は逆方向1回だが、順方向に3回回しても同じなので、3としておく。
/** * ランダムで回転情報(方向,回数)を取得 * 方向 0:U, 1:D, 2:F, 3:B, 4:R, 5:R * 回数 1:順方向1, 2:順方向2(2), 3:逆方向1(') * @return array * @throws \Random\RandomException */ public function roll(): array { // 方向(R, U, L, D, F, Bの6パターン) $roll = random_int(0, 5); // 回数(U, U2, U'の3パターン) $times = random_int(1, 3); return array($roll, $times); }
めちゃくちゃベタ書きの実装ですが、今回は動けば良いということで、一旦Rの回転を表現してみた。変数operationの定義は、インデックスXのマスがインデックスYに移動するという意味。1個の動作につき、20マスのカラーが移動する。(↓の例では、Rの回転のみしか記述していない。)
/** * カラーの回転処理(1記号分)を処理する * @param $colors * @param $roll * @param $times * @return array */ public function cubeRotate($colors, $roll, $times) { $cube = $colors; $operation = array(); switch ($roll) { case 4: $operation = [ 20 => 2, 23 => 5, 26 => 8, 2 => 42, 5 => 39, 8 => 36, 42 => 47, 39 => 50, 36 => 53, 47 => 20, 50 => 23, 53 => 26, 27 => 29, 28 => 32, 29 => 35, 32 => 34, 35 => 33, 34 => 30, 33 => 27, 30 => 28 ]; break; default: break; } for ($i = 0; $i < $times; $i++) { $cube_previous = $cube; foreach($operation as $position_origin => $position_new){ $cube[$position_new] = $cube_previous[$position_origin]; } } return $cube; }
擬似的に回転を表現してみる
/** * スクランブルデータ(数値配列)をカラー情報の配列にする * @param $scramble * @return array */ public function scrambleToColorArray($scramble): array { // 初期状態のカラーリング情報 $colors_info = $this->initColorArray(); // R1回分の回転を擬似的に作成 $scramble = [ [ 'roll' => 4, 'times' => 1 ] ]; foreach ($scramble as $value) { $colors_info = $this->cubeRotate($colors_info, $value['roll'], $value['times']); } return $colors_info; }
これでAPIの出力が変化したはずなので、画面を見てみる。

回転したぁ!嬉しい・・。
R以外の回転も定義する
実はR回転以外の残り5回転に対して、$operationをベタ書きで定義しただけなので、リンクを貼るのみとします。
https://github.com/supilog/cube/blob/v1.0.9/app/Services/CubeService.php
最終確認
できあがったものがこちらです。何度かリロードしています。
「で、実際あってんの?これw」って思いますよね。では実際にスクランブルの文字列通りにまわしてみて、比べてみた。

これは・・・合ってそう・・合ってますね!やったぜ!完成しました!w
まとめ
ここまで、本記事を閲覧して頂いた方、大変ありがとうございました。ソースコードベースで大した説明もせずに載せているだけなので不親切な記事ではありますが、お付き合いいただき恐縮です。
まだまだ細かい改善点はあるものの、プロジェクト開始前に計画したプランは本日完成させることが出来たので、ここで一区切りです。ふぅ〜。やっぱり展開図があると引き締まってみえます。(キリッ
肝心のルービックキューブはというと、50秒弱くらいの記録をキープしています。最後に一発勝負でタイム計測して終わります。(謎のイベント
ではいきます。はいっ。

うんっ。平均っ!!ありがとうございました。
今回のソースコード
本日の実装が完了した状態のタグが「v1.0.9」です。
https://github.com/supilog/cube/tree/v1.0.9
- app/Http/Controllers/CubesApiController.php
- app/Http/Controllers/CubesController.php
- app/Services/CubeService.php
- resources/js/timer.js
- resources/views/index.blade.php