環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【ルービックキューブのタイマーを作る】第3回 スクランブルを表示する回

【ルービックキューブのタイマーを作る】第3回 スクランブルを表示する回

今回はスクランブルを表示してみる。

スクランブルとは?

ルービックキューブを遊ぶ前に、まずバラバラにする。その作業をスクランブルと言う。アプリ上で表示予定の↓この部分が、スクランブルの表示です。

スクランブル表示があると、毎回スクランブル作業に困ることがないし、手癖と関係なくランダムに作業出来る。また、タイムを保存する時に、どういうスタート状態だったか記録できるので、振り返る時にも便利です。

スクランブル表示の記号は?

記号はどのように回転させるかを表している。回転記号は、他にもあるのだが、スクランブルに使用するのは、以下の記号のみとする!

回転面の記号

  • U – UP面
  • D – DOWN面
  • B – BACK面
  • F – FRONT面
  • L – LEFT面
  • R – RIGHT面

「’」は逆回転

例えば、「U」はUP面の順方向に1回まわす(90度回転)、「U’」はUP面の逆方向に1回まわす(90度回転)を表す。

「2」は180度回転

例えば、「U2」はUP面の順方向に2回まわす(180度回転)、「D’2」はDOWN面の逆方向に2回まわす(180度回転)を表す。

仕様

同じ面の記号を連続しない

同じ面の回転を連続して行うのは禁止し、N-1回目の回転とN回目の回転が異なる面であることとする。例えば、N-1回目が「U」で、N回目「U’2」の場合、U + U’2 → U’ となるので、そもそも一度に記述出来る為、そのような記述の意味がないことになる。

前々回と前回が向かい合う面の場合、前々回と同じ回転方向にはならない

N-2回目とN-1回目が向かい合う回転の場合、N回目の回転は、N-2回目の回転と異なる面とする。例えば、N-2回目が「U2」で、N-1回目が「D」の場合、N回目はU面の回転記号にしない。

ある一定回数回すことを目標とし、ランダム具合は考慮しない

本当は、ある程度ランダムな状態であることを担保したいところだが、そこまで複雑なチェックをしたくないので、一定回数を決めて、それ以上回転したらOKとする。低い確率ではあるが、全然混ざっていないということもあり得るが、許容する。

実装

JSで実装しても良かったのですが、今回はPHP側で生成することにします。

app/Services/CubeService.php

こちらが処理内容です。

<?php

namespace App\Services;

class CubeService
{

    /**
     * スクランブルデータを取得する(数値配列)
     * @return array
     * @throws \Random\RandomException
     */
    public function scramble(): array
    {
        $ret = array();
        $length = 0;
        while ($length < config('cube.scramble.length')) {
            list($next_roll, $next_times) = $this->roll();
            $index = count($ret);
            // 前回と同じ回転方向なら無視
            if (!empty($ret[$index - 1])) {
                if ($ret[$index - 1]['roll'] == $next_roll) {
                    continue;
                }
            }
            // 前々回と前回が向かい合う面の場合、前々回と同じ回転方向なら無視
            if (!empty($ret[$index - 2])) {
                if (floor($ret[$index - 2]['roll'] / 2) == floor($ret[$index - 1]['roll'] / 2)) {
                    if ($ret[$index - 2]['roll'] == $next_roll) {
                        continue;
                    }
                }
            }
            $ret[$index]['roll'] = $next_roll;
            $ret[$index]['times'] = $next_times;
            $length += ($next_times == 1) ? 2 : 1;
        }
        return $ret;
    }

    /**
     * ランダムで回転情報(方向,回数)を取得
     * 方向 0:U, 1:D, 2:F, 3:B, 4:R, 5:L
     * 回数 0:順方向1, 1:順方向2(2), 2:逆方向1(')
     * @return array
     * @throws \Random\RandomException
     */
    public function roll()
    {
        // 方向(R, U, L, D, F, Bの6パターン)
        $roll = random_int(0, 5);
        // 回数(U, U2, U'の3パターン)
        $times = random_int(0, 2);
        return array($roll, $times);
    }

    /**
     * スクランブルデータ(数値配列)を記号文字の配列にする
     * @param $scramble
     * @return array
     */
    public function scrambleToTextArray($scramble)
    {
        $ret = array();
        foreach ($scramble as $data) {
            $text = '';
            switch ($data['roll']) {
                case 0:
                    $text = 'U';
                    break;
                case 1:
                    $text = 'D';
                    break;
                case 2:
                    $text = 'F';
                    break;
                case 3:
                    $text = 'B';
                    break;
                case 4:
                    $text = 'R';
                    break;
                case 5:
                    $text = 'L';
                    break;
                default:
                    break;
            }

            switch ($data['times']) {
                case 1:
                    $text .= '2';
                    break;
                case 2:
                    $text .= '\'';
                    break;
                default:
                    break;
            }
            array_push($ret, $text);
        }
        return $ret;
    }
}

前々回と前回が向かい合う面の場合」の判定が、一番難所ではありますが、このクラスでは、各回転方向を「0:U, 1:D, 2:F, 3:B, 4:R, 5:L」という数値で管理しており、UとD、FとB、RとLがそれぞれ向かい合う面となる。2で割った時の整数部分の数値を比較することで、向かい合う面の判定をしている。

(例)2(F)と3(B)は2で割った時の整数値が1

if (floor($ret[$index - 2]['roll'] / 2) == floor($ret[$index - 1]['roll'] / 2)) {

app/Http/Controllers/CubesController.php

先ほど作成したクラスを呼び出して、スクランブルをテンプレート側に送っています。

    public function index(CubeService $cs)
    {
        $scramble = $cs->scramble();
        $scramble_text_array = $cs->scrambleToTextArray($scramble);
        $scramble_text = implode(' ', $scramble_text_array);
        $data = [
            'scramble_text' => $scramble_text
        ];
        return view('index', $data);
    }

resources/views/index.blade.php

表示。

            <div id="scramble" class="sm:flex sm:justify-center text-4xl">{{ $scramble_text }}</div>

確認

表示を確認してみます。

表示されてますね。ブラウザリロードをしてみます。

更新されてますね。無事にスクランブルのテキストを生成して表示することに成功しました!!

まとめ

スクランブルの生成処理が完成しましたっ!

実はスクランブルを表示するタイミングはページがロードされたタイミングだけでなく、もう1箇所あるんです。それは、ルービックキューブのタイマーをストップした時。次のためのスクランブルが表示されるわけです。

となると、JSで扱わざるを得ないので、いつかのタイミングで今回の処理をAPI化する必要があると思います。それはまた、別のおはなし。

さて次回からは、今回作成した情報を元に、キューブ展開図のカラーリングがスクランブル情報通りに なるように開発していきたいと思う。と思ったのですが、先延ばしにすると、修正箇所が大きくなっていきそうなので、次回は「API化」をやろうと思います。

ソースコード

HTML&JSファイル

本日の実装が完了した状態のタグが「v1.0.3」です。

https://github.com/supilog/cube/blob/v1.0.3/app/Services/CubeService.php

リンク

「ルービックキューブのタイマーを作る」シリーズ